This edition of Simple Tricks contains a random collection of bits and pieces. First, some cute bits from the interweb, then some more-or-less one-off bits I whipped up for a project and which were just barely useful enough to keep. (If for no other reason than to publish them here.)
I’ll end with a pair of simple Python functions, one to list the contents of an existing ZIP file, and one to create a new one from files in a subdirectory. The latter can be the basis for a more sophisticated archive function.
There has always been justifiable concern among people responsible for hiring programmers about how to judge good programmers from bad programmers. These days, there is also concern about unqualified people faking interviews.
As an aside: I’ve never understood this from either side of the equation (hirers or applicants). It seems — and my experiences before I retired confirms — programming isn’t something you can successfully fake, at least not for long. Programming is hard. Unqualified programmers typically stand out like the proverbial sore thumbs. That said, it’s also my experience that a lot of people claim they can program when they can’t — perhaps from a strong Dunning-Kruger effect.
In any event, hirers look for ways to weed out the unqualified. Until the advent of “Ai”, a common test was to ask the applicant to write a “FizzBuzz” program in whatever language applied. If the goal is merely demonstrating coding ability, then let the applicant pick whatever language they like. Their choice might be interesting. If proficiency in a specific language is important, then the code should be in that language.
In any event, the test here is to model Fizz Buzz, a game used to teach children division. In the game, players rotate turns counting up by one from one, but with some extra rules. If the number is evenly divisible by…
- … 3, say “fizz” instead (of the number).
- … 5, say “buzz” instead.
- … both 3 and 5, say “fizzbuzz” instead.
For applicant-screening, the ask is for code that emits a Fizz Buzz sequence for the number 1 to 100. Any decent coder ought to be able to do this in minutes — and indeed, part of the test is that it’s timed. Anything over ten minutes is suspect.
The rise of “Ai” means that any applicant faking it and interviewing online (as is increasingly common from what I hear) can easily find working code. Cheating this way is even easier if the interview is via email. So, now the search is for ways to test applicants (hoping for applicans) that “Ai” isn’t good with. FWIW, hiring is tough. I’ve been in the position of screening prospective coders, and I think the only solution is actually talking to them about the work they’ve done.
Which is neither here nor there. Actually, it’s there, not here. The above was all context for something mentioned in a thread discussing the screening issue. The comment was to the effect that Real Programmers™, upon hearing about something like the Fizz Buzz test can’t help but immediately test themselves.
And I (literally) laughed out loud, because by that point in reading about the screening issue I’d stumbled over a reference to Fizz Buzz (which I’d never heard of), followed a link to find out what it was, and … immediately tested myself to see how long it took to whip up some fizzbuzz code:
002| “””Fizz-Buzz Quiz, basic approach.”””
003|
004| for n in range(1,101):
005| if n%3 == 0:
006| if n%5 == 0:
007| print(f'{n:3d}: “FizzBuzz”‘)
008| continue
009|
010| print(f'{n:3d}: “Fizz”‘)
011| continue
012|
013| if n%5 == 0:
014| print(f'{n:3d}: “Buzz”‘)
015| continue
016|
017| print(f'{n:3d}: “{n}”‘)
018| print()
019|
020| if __name__ == ‘__main__’:
021| print()
022| fizz_buzz_1()
023| print(‘Done.’)
024|
This is what we might call the “obvious” version. The above — or something very similar — could be written in just about any (high-level) language. The process is pretty simple: Iterate through the numbers from 1 to 100 and test each for divisibility (a common method uses modulo division). There are four conditions to recognize:
nis divisible by 3 (lines #5 to #11) — print “Fizz” (line #10)nis divisible by 3 and 5 (lines #6 to #8) — print “FizzBuzz” (line #7)nis divisible by 5 only (lines #13 to #15) — print “Buzz” (line #14)nisn’t divisible by 3 or 5 — just print the number (line #17)
When run, this prints:
1: "1" 2: "2" 3: "Fizz" 4: "4" 5: "Buzz" 6: "Fizz" 7: "7" 8: "8" 9: "Fizz" 10: "Buzz" 11: "11" 12: "Fizz" 13: "13" 14: "14" 15: "FizzBuzz" 16: "16" 17: "17" 18: "Fizz" […] 90: "FizzBuzz" 91: "91" 92: "92" 93: "Fizz" 94: "94" 95: "Buzz" 96: "Fizz" 97: "97" 98: "98" 99: "Fizz" 100: "Buzz"
The code above took only a couple of minutes — three at the most (apparently, I’m worth at least a second look).
I thought I’d take a few more minutes to whip up a more Python-y version:
002| “””Fizz-Buzz Quiz, a Python approach.”””
003|
004| threes = [“Fizz” if n%3 == 0 else “” for n in range(1,101)]
005| fives = [“Buzz” if n%5 == 0 else “” for n in range(1,101)]
006| flags = [len(x3)+len(x5) for x3,x5 in zip(threes,fives)]
007|
008| for n,flg,x3,x5 in zip(range(1,101), flags, threes, fives):
009| output = f'{x3}{x5}’ if flg else str(n)
010| print(f'{n:3d}: “{output}”‘)
011| print()
012|
013| if __name__ == ‘__main__’:
014| print()
015| fizz_buzz_2()
016| print(‘Done.’)
017|
I admit: I’m a little in love with list comprehensions [see Simple Tricks #2 and Simple Tricks #3]. Lines #3 and #4 generate lists of “Fizz” and “Buzz” in the appropriate slots and empty strings in all others. Line #6 generates a list of numbers, each the sum of the lengths of the “Fizz” and “Buzz” strings for that index. That length is nonzero for all multiples of three and five but zero otherwise. It effectively acts as a switch to control the output.
Lines #8 to #11 combine the three generated lists with a list of index numbers and creates the appropriate output for each member of the combined lists. When run, this has the same output as the first version.
[For an interesting and fun tour through examples of FizzBuzz code a wide variety of languages, see the Rosetta Code page for FizzBuzz (especially the Python section).]
On Substack there is a short-form blogging feed, called Notes — it’s what you land on if you go to substack.com. One subscriber posts daily Python quizzes, and, for whatever reason, this one caught my eye:
002|
003| def memo_func (n, memo={}):
004| if n in memo: return memo[n]
005| if n <= 2: return 1
006| memo[n] = memo_func(n–1,memo) + memo_func(n–2,memo)
007| return memo[n]
008|
009| print(f'{num} => {memo_func(num)}’)
010|
011| if __name__ == ‘__main__’:
012| print()
013| python_quiz_1(7)
014| print(‘Done.’)
015|
The quiz simply asked, “What does this print when run?” Per the screening issue, with this code — or something similar — a useful question might be, “Do you recognize what the function does?” In particular, why is it called memo_func, and what is the purpose of that second parameter, memo? (And in fact, this code — and that last question — demonstrates a Python “gotcha” that often catches unwary coders.)
Here’s the answer to the first question. When run, this prints:
7 => 13 Done.
Experienced coders, especially those who’ve taken Computer Science classes, should quickly recognize the function as a Fibonacci sequence algorithm — line #5 and #6 are recognizable as the Fibonacci equation:
We can investigate the last question — the one touching on the “gotcha” — by adding some print statements and exercising the memo function repeatedly:
002|
003| def memo_fib (n, memo={}):
004| print(f’memo_fib({n}, {len(memo)})’)
005|
006| if n in memo:
007| print(f’memo[{n}] => {memo[n]}’)
008| return memo[n]
009| if n <= 2:
010| print(‘return 1’)
011| return 1
012|
013| memo[n] = memo_fib(n–1,memo) + memo_fib(n–2,memo)
014| print(f'{n} => {memo[n]}’)
015| return memo[n]
016|
017| print(‘#1’)
018| print(f’fib({num}) => {memo_fib(num)}\n’)
019| print(‘#2’)
020| print(f’fib({num+1}) => {memo_fib(num+1)}\n’)
021| print(‘#3’)
022| print(f’fib({num+2}) => {memo_fib(num+2)}\n’)
023| print()
024| print(‘#4’)
025| print(f’fib({num+3}) => {memo_fib(num+3, {})}\n’)
026| print(‘#5’)
027| print(f’fib({num+4}) => {memo_fib(num+4)}\n’)
028| return ()
029|
030| if __name__ == ‘__main__’:
031| print()
032| python_quiz_2(7)
033| print(‘Done.’)
034|
I’ve also changed the name of the inner function’s definition (line #3 to line #15) to memo_fib to reflect its nature. Four new print statements (lines #4, #7, #10, and #14) document the function’s input and outputs.
When run, this prints (breaking it down to each call of memo_fib):
01|#1 02|memo_fib(7, 0) 03|memo_fib(6, 0) 04|memo_fib(5, 0) 05|memo_fib(4, 0) 06|memo_fib(3, 0) 07|memo_fib(2, 0) 08|return 1 09|memo_fib(1, 0) 10|return 1 11|3 => 2 12|memo_fib(2, 1) 13|return 1 14|4 => 3 15|memo_fib(3, 2) 16|memo[3] => 2 17|5 => 5 18|memo_fib(4, 3) 19|memo[4] => 3 20|6 => 8 21|memo_fib(5, 4) 22|memo[5] => 5 23|7 => 13 24|fib(7) => 13
The first time, we’re passing num=7, so memo_fib recurses down to num=2 (line #7) and num=1 (line #9), both of which return 1 (lines #8 and #10). This allows num=3 to return 2 (line #11).
The call with num=4 needs the second recursion (num=2; line #12), which returns 1 (line #13), and memo_fib returns 3 (line #14). In the same fashion, the call num=5 needs num=3, and memo_fib returns 2 (line #16). Note that this is the first time a memo_fib call can return a value already stored in memo. Line #17 is the return value from num=5.
Just as with the above steps, num=6 needs the num=4 value from memo (lines #18 and #19) to return 8 in line #20. And num=7 needs the num=5 value (lines #21 and #22) to return 13 (line #23). That gives us our final result, 13 (line #24).
Now see what happens when we call memo_fib a second time with num=8:
26|#2 27|memo_fib(8, 5) 28|memo_fib(7, 5) 29|memo[7] => 13 30|memo_fib(6, 5) 31|memo[6] => 8 32|8 => 21 33|fib(8) => 21
This time, both the n-1 and n-2 values are immediately available in memo, so no further recursion is required.
The same thing happens the third time with num=9.
35|#3 36|memo_fib(9, 6) 37|memo_fib(8, 6) 38|memo[8] => 21 39|memo_fib(7, 6) 40|memo[7] => 13 41|9 => 34 42|fib(9) => 34
But the fourth time, with num=10, we also pass memo={}, which resets memo to an empty dictionary. This forces memo_fib to recurse again all the way down to n=2 and n=1, just like the first call above:
45|#4 46|memo_fib(10, 0) 47|memo_fib(9, 0) 48|memo_fib(8, 0) 49|memo_fib(7, 0) 50|memo_fib(6, 0) 51|memo_fib(5, 0) 52|memo_fib(4, 0) 53|memo_fib(3, 0) 54|memo_fib(2, 0) 55|return 1 56|memo_fib(1, 0) 57|return 1 58|3 => 2 59|memo_fib(2, 1) 60|return 1 61|4 => 3 62|memo_fib(3, 2) 63|memo[3] => 2 64|5 => 5 65|memo_fib(4, 3) 66|memo[4] => 3 67|6 => 8 68|memo_fib(5, 4) 69|memo[5] => 5 70|7 => 13 71|memo_fib(6, 5) 72|memo[6] => 8 73|8 => 21 74|memo_fib(7, 6) 75|memo[7] => 13 76|9 => 34 77|memo_fib(8, 7) 78|memo[8] => 21 79|10 => 55 80|fib(10) => 55
And, of course, the fifth time we’re back to using the stored values:
82|#5 83|memo_fib(11, 7) 84|memo_fib(10, 7) 85|memo_fib(9, 7) 86|memo[9] => 34 87|memo_fib(8, 7) 88|memo[8] => 21 89|10 => 55 90|memo_fib(9, 8) 91|memo[9] => 34 92|11 => 89 93|fib(11) => 89 94| 95|Done.
The gotcha is lies in using mutable object as the default value for a function parameter. It works out okay in this case but consider the following:
002|
003| def add_item_to_list (my_item, my_list=[]):
004| my_list.append(my_item)
005| return my_list
006|
007| grocery_list = add_item_to_list(‘bread’)
008| add_item_to_list(‘cheese’, grocery_list)
009| add_item_to_list(‘beans’, grocery_list)
010|
011| todo_list = add_item_to_list(‘dust’)
012| add_item_to_list(‘vacuum’, todo_list)
013|
014| print(f’Shopping: {grocery_list}’)
015| print()
016| print(f’Chores: {todo_list}’)
017| print()
018|
019| if __name__ == ‘__main__’:
020| print()
021| python_gotcha()
022| print(‘Done.’)
023|
The expectation is that the user is building two lists, one for shopping, one for chores. But when run, this prints:
Shopping: ['bread', 'cheese', 'beans', 'dust', 'vacuum'] Chores: ['bread', 'cheese', 'beans', 'dust', 'vacuum'] Done.
Which is not what was intended. The intent was to create a new list by using the empty list default for my_list, but a list is mutable, and the changes made by earlier calls added items to it.
To accomplish what the coder intended, write it like this:
002|
003| def add_item_to_list (my_item, my_list=None):
004| if my_list is None:
005| my_list = []
006| my_list.append(my_item)
007| return my_list
008|
009| grocery_list = add_item_to_list(‘bread’)
010| add_item_to_list(‘cheese’, grocery_list)
011| add_item_to_list(‘beans’, grocery_list)
012|
013| todo_list = add_item_to_list(‘dust’)
014| add_item_to_list(‘vacuum’, todo_list)
015|
016| print(f’Shopping: {grocery_list}’)
017| print()
018| print(f’Chores: {todo_list}’)
019| print()
020|
021| if __name__ == ‘__main__’:
022| print()
023| python_gotcha_solved()
024| print(‘Done.’)
025|
There is something of a rule about not changing passed parameters — it’s more important in some languages than in others. It isn’t really a problem in Python because arguments are passed by value (though those values can be references). But if one wanted to follow that rule, then write it like this:
002|
003| def add_item_to_list (my_item, my_list=None):
004| the_list = [] if my_list is None else my_list
005| the_list.append(my_item)
006| return the_list
007|
008| grocery_list = add_item_to_list(‘bread’)
009| add_item_to_list(‘cheese’, grocery_list)
010| add_item_to_list(‘beans’, grocery_list)
011|
012| todo_list = add_item_to_list(‘dust’)
013| add_item_to_list(‘vacuum’, todo_list)
014|
015| print(f’Shopping: {grocery_list}’)
016| print()
017| print(f’Chores: {todo_list}’)
018| print()
019|
020| if __name__ == ‘__main__’:
021| print()
022| python_gotcha_solved_pedantic_version()
023| print(‘Done.’)
024|
In either case, when run, the code works as expected and prints:
Shopping: ['bread', 'cheese', 'beans'] Chores: ['dust', 'vacuum'] Done.
Here’s a cute bit of code that uses a binary trick to draw the Sierpinski Triangle:
002| “””Draw a left-aligned Sierpinski Triangle.”””
003|
004| # We need N to be a power of two…
005| n = pow(2, level)
006|
007| result = []
008| for y in range(n):
009| buf = []
010|
011| for x in range(n):
012| buf.append(space if x & y else char)
013|
014| result.append(buf)
015|
016| for row in result:
017| print(”.join(row))
018|
019| return result
020|
021| if __name__ == ‘__main__’:
022| print()
023| sierpinski_triangle(5)
024| print(‘Done.’)
025|
The two loops use x and y to iterate over a square space where the length of a side is a power of two. The binary trick is in line #12 where we AND x and y and use that result to determine whether to draw a space or a character for the triangle.
When run (with the argument 5), this prints:
******************************** * * * * * * * * * * * * * * * * ** ** ** ** ** ** ** ** * * * * * * * * **** **** **** **** * * * * * * * * ** ** ** ** * * * * ******** ******** * * * * * * * * ** ** ** ** * * * * **** **** * * * * ** ** * * **************** * * * * * * * * ** ** ** ** * * * * **** **** * * * * ** ** * * ******** * * * * ** ** * * **** * * ** * Done.
No big deal, but I wanted to record the algorithm and share it.
A while back, I had a requirement to create a square (in 2D) or a cube (in 3D) of balls. (This was part of my Pool Balls Animations videos.) The requirement was, for any given number of balls, find the smallest square or cube that can contain them.
Here’s an algorithm for fitting N balls into the smallest possible square:
002| “””Given N, returns sides for a square that can hold them.”””
003| print(f’square({n:d})’)
004|
005| root = pow(n, 1/2) # square root
006| x = int(root)
007| # If the square root isn’t integer, round up…
008| if root != x:
009| x += 1
010|
011| print(f’square: ({x},{x}); {pow(x,2)}’)
012| print()
013| return (x, x)
014|
015| if __name__ == ‘__main__’:
016| print()
017| ns = list(range(9))
018| for n in range(3,13):
019| n2 = pow(n, 2)
020| ns.append(n2–1)
021| ns.append(n2)
022| ns.append(n2+1)
023|
024| print(‘Fit Square’)
025| for n in ns:
026| square_grid(n)
027| print()
028| print(‘Done.’)
029|
And here’s one that does the same for a cube:
002| “””Given N, returns sides for a cube that can hold them.”””
003| print(f’cubic({n:d})’)
004|
005| root = pow(n, 1/3) # cube root
006| x = int(root)
007| # If the cube root isn’t integer, round up…
008| if root != x:
009| x += 1
010|
011| print(f’cubic: ({x},{x},{x}); {pow(x,3)}’)
012| print()
013| return (x, x, x)
014|
015| if __name__ == ‘__main__’:
016| print()
017|
018| ns = list(range(10))
019| for n in range(3,13):
020| n3 = pow(n, 3)
021| ns.append(n3–1)
022| ns.append(n3)
023| ns.append(n3+1)
024|
025| print(‘Fit Cube’)
026| for n in ns:
027| cubic_grid(n)
028| print()
029| print(‘Done.’)
030|
The code is simple enough (and niche enough) that I won’t go into it. Again, I mostly wanted to document these fragments.
In a future post I plan to explore the itertools library module in detail, but here’s a simple use case. Recently, I needed to convert a list of items into a series of pairs. For instance, given “ABCDEF”, I want to generate “AB”, “CD”, “EF”. The pairwise function in itertools wasn’t what I needed; that returns “AB”, “BC”, “CD”, “DE”, “EF”. Except for the first and last items, all other items appear twice in the output. I only wanted single occurrences.
Another requirement was that “ABCDEFG” should generate “AB”, “CD”, “EF’, “G@”. That is, if the input sequence has an odd number of items, use a user-provided item to fill the last slot.
Here’s what I came up with:
002|
003| def generate_list_of_pairs (data, fillvalue=‘@’):
004| “””Split data into a list of pairs.”””
005| print(data)
006| print()
007|
008| even = [d for ix,d in enumerate(data) if (ix%2)==0]
009| odd = [d for ix,d in enumerate(data) if (ix%2)==1]
010| print(even)
011| print(odd)
012| print()
013|
014| pairs = zip_longest(even,odd, fillvalue=fillvalue)
015| for a,b in pairs:
016| print(f'{a}.{b}’)
017| print()
018|
019| return pairs
020|
021| if __name__ == ‘__main__’:
022| print()
023| data = [chr(ord(‘A’)+ix) for ix in range(25)]
024|
025| pairs = generate_list_of_pairs(data, ‘*’)
026| print(‘Done.’)
027|
The code should be fairly clear. Line #8 generates a list of all the items with even indexes; line #9 does the same for the odd indexes. Line #14 uses the itertools zip_longest function to combine the even and odd lists.
When run, this prints:
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y'] ['A', 'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y'] ['B', 'D', 'F', 'H', 'J', 'L', 'N', 'P', 'R', 'T', 'V', 'X'] A.B C.D E.F G.H I.J K.L M.N O.P Q.R S.T U.V W.X Y.* Done.
Lastly, as promised, here’s how you can list the contents of a ZIP file using Python:
002| import zipfile as zf
003|
004| FMT = ‘{ix:2d}: {sfs:>10s} {scs:>10} {pct:6.3f}% [{sct}] {fname}’
005|
006| def zip_compression_type (n:int) -> str:
007| if n == zf.ZIP_STORED: return ‘Stor’
008| if n == zf.ZIP_DEFLATED: return ‘Defl’
009| if n == zf.ZIP_BZIP2: return ‘BZip’
010| if n == zf.ZIP_LZMA: return ‘LZMA’
011| raise ValueError(f’Unknown compression type: {n}’)
012|
013| def ziplist (zname, zpath=r’C:\demo\hcc\python’):
014| ”’List contents of a ZIP file.”’
015| zipname = path.join(zpath, zname) if zpath else zname
016|
017| zipfile = zf.ZipFile(zipname)
018| print(f’opened: {zipname}’)
019| print()
020|
021| # List the filenames…
022| for ix,name in enumerate(zipfile.namelist(), start=1):
023| print(f'{ix:2d}: {name}’)
024| print()
025|
026| # List the details…
027| for ix,zi in enumerate(zipfile.infolist(), start=1):
028| args = dict(
029| ix = ix,
030| sfs = f'{zi.file_size:,}’,
031| scs = f'{zi.compress_size:,}’,
032| pct = 100.0 * (zi.compress_size / zi.file_size),
033| sct = zip_compression_type(zi.compress_type),
034| fname = zi.filename
035| )
036| print(FMT.format(**args))
037| print()
038|
039| if __name__ == ‘__main__’:
040| print()
041| ziplist(‘archive.zip’)
042| print(‘Done.’)
043|
It’s that simple, and the code should be fairly self-explanatory.
The zip_compression_type function lets us put text labels to the compression types. Once opened, we can access a list of filenames (only) through the namelist method or a detailed list through the infolist method.
When run, this prints:
opened: C:\demo\hcc\python\archive.zip 1: 2b-or-not2b.txt 2: chriscarol.txt 3: colors.png 4: configs.json 5: configuration.ini 6: example.json 7: example.xml 8: randnums.dat 9: setup.ini 10: vim.txt 1: 450 286 63.556% [Defl] 2b-or-not2b.txt 2: 5,596 2,649 47.337% [Defl] chriscarol.txt 3: 1,992 579 29.066% [Defl] colors.png 4: 675 205 30.370% [Defl] configs.json 5: 491 249 50.713% [Defl] configuration.ini 6: 1,040 378 36.346% [Defl] example.json 7: 1,373 468 34.086% [Defl] example.xml 8: 19,164 9,636 50.282% [Defl] randnums.dat 9: 1,099 537 48.863% [Defl] setup.ini 10: 334 209 62.575% [Defl] vim.txt Done.
Of course, the output depends on the ZIP file.
Lastly, we can create a new ZIP file with this code:
002| import zipfile as zf
003|
004| def zipper (fpath, zname=‘archive.zip’):
005| ”’Put all the files in a given directory into a ZIP file.”’
006|
007| # Get a list of files…
008| fnames = listdir(fpath)
009|
010| # Set ZIP parameters and filename…
011| props = dict(
012| mode=‘w’,
013| compression=zf.ZIP_DEFLATED,
014| compresslevel=9
015| )
016| zipname = path.join(fpath, zname)
017|
018| # Create (or overwrite!) a ZIP file…
019| with zf.ZipFile(zipname, **props) as archive:
020|
021| # Iterate over the list of files…
022| for fname in fnames:
023| # Ignore our own name in case old version exists…
024| if fname == zname:
025| continue
026|
027| # Need the full file path and name…
028| filename = path.join(fpath,fname)
029|
030| # Only store files; subdirectory recursion is extra…
031| if path.isfile(filename):
032| # Add file to archive…
033| archive.write(filename, fname)
034|
035| print(f’created: {zipname}’)
036| print()
037|
038| if __name__ == ‘__main__’:
039| print()
040| zipper(r’C:\demo\hcc\python’)
041| print(‘Done.’)
042|
This puts all files found in a given subdirectory into a ZIP file, which it puts in the same subdirectory. Note that it avoids putting itself into the archive. The presumption is that the ZIP file may already exist. Also note that this will overwrite any existing ZIP file by the same.
Recursing subdirectories or other features is left as a user exercise.
That’s it for this time.
∅
Link: Zip file containing all code fragments used in this post.
ATTENTION: The WordPress Reader strips the style information from posts, which can destroy certain important formatting elements. If you’re reading this in the Reader, I highly recommend (and urge) you to [A] stop using the Reader and [B] always read blog posts on their website.
This post is: Simple Python Tricks #17
Given another minute to polish, here’s my FizzBuzz code, final version:
002| """Fizz-Buzz Quiz, Python approach."""
003| rng = range(1,101)
004|
005| threes = ["Fizz" if n%3 == 0 else "" for n in rng]
006| fives = ["Buzz" if n%5 == 0 else "" for n in rng]
007| flags = [len(x3)+len(x5) for x3,x5 in zip(threes,fives)]
008|
009| for n,flg,x3,x5 in zip(rng, flags, threes, fives):
010| output = f'{x3}{x5}’ if flg else str(n)
011| print(f'{n:3d}: "{output}"’)
012| print()
013|
014| if __name__ == ‘__main__’:
015| print()
016| fizz_buzz_3()
017| print(‘Done.’)
018|
Because, remember, we try to never repeat ourselves.
Here’s an expansion on the
generate_list_of_pairsfunction that lets it split a list into chunks of any width (not just two):002|
003| def generate_list_of_chunks (data, width=2, fillvalue=‘@’):
004| """Split data into chunks of width."""
005| print(data)
006| print()
007|
008| threads = []
009| for w in range(width):
010| thread = [d for ix,d in enumerate(data) if (ix%width)==w]
011| threads.append(thread)
012| print(f'{w}: {thread}’)
013| print()
014| print()
015|
016| chunks = zip_longest(*threads, fillvalue=fillvalue)
017| for chunk in chunks:
018| print(‘.’.join(chunk))
019| print()
020|
021| return chunks
022|
023| if __name__ == ‘__main__’:
024| print()
025| data = [chr(ord(‘A’)+ix) for ix in range(26)]
026|
027| chunk = generate_list_of_chunks(data, 5, ‘*’)
028| print(‘Done.’)
029|
Which is something of a callback to the first Simple Tricks post.