Categories
Programming What I’m Up To

My solution to Advent of Code 2020’s Day 1 challenge, in Python

 

December has arrived, and so has the great programming exercise known as the Advent of Code!

Think of it as an Advent calendar, but chocolates (or cheese, or wine), you’re presented with a new programming puzzle every day between the start of December and Christmas Day, in which you try to save Santa’s mission. You can use whatever programming language you want, and you don’t need to be an expert — as the site says, “just a little programming knowledge and some problem solving skills will get you pretty far.”

Advent of Code started in 2015, and has been taking place every year ever since. The 2020 edition began on Tuesday, December 1st at 12:00 midnight Eastern time (UTC-5).

Not only do I plan on participating in this year’s Advent of Code, but I might even use a couple of the challenges in the Python class I’m currently teaching on behalf of Computer Coach.

You have to sign in to play

In order to take on Advent of Code’s challenges, you have to sign in using an account from one of these popular “federated ID” services:

  • Github
  • Google
  • Twitter
  • Reddit

This is for a couple of reasons:

  • Signing in makes it easier for the site to keep track of your progress. Advent of Code is structures so that you must successfully complete challenge n before taking on challenge (n+1).
  • While everyone has to solve the same problem, each user gets their own (presumably) unique data set.

Once you’ve signed in, you can start on the first challenge…

Spoiler alert!

Please be warned: If you want to try solving the challenge on your own and without any help, stop reading now! The remainder of this post will be all about my solution to both parts of the Day 1 challenge.

The Day 1 challenge, part one

Here’s the text from part one of the challenge:

Day 1: Report Repair

After saving Christmas five years in a row, you’ve decided to take a vacation at a nice resort on a tropical island. Surely, Christmas will go on without you.

The tropical island has its own currency and is entirely cash-only. The gold coins used there have a little picture of a starfish; the locals just call them stars. None of the currency exchanges seem to have heard of them, but somehow, you’ll need to find fifty of these coins by the time you arrive so you can pay the deposit on your room.

To save your vacation, you need to get all fifty stars by December 25th.

Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck!

Before you leave, the Elves in accounting just need you to fix your expense report (your puzzle input); apparently, something isn’t quite adding up.

Specifically, they need you to find the two entries that sum to 2020 and then multiply those two numbers together.

For example, suppose your expense report contained the following:

1721
979
366
299
675
1456

In this list, the two entries that sum to 2020 are 1721 and 299. Multiplying them together produces 1721 * 299 = 514579, so the correct answer is 514579.

Of course, your expense report is much larger. Find the two entries that sum to 2020; what do you get if you multiply them together?

Here are the expense numbers that were provided for my account:

1140
1736
1711
1803
1825
1268
1651
2007
1923
1661
1788
1876
2003
1752
1988
1955
1568
1478
1699
1717
1828
1636
1387
1870
1658
1572
1703
1185
1569
1515
1142
1407
1587
1608
1827
1546
1808
1937
1815
1957
1401
1763
1970
1960
1853
1987
1865
1567
1664
1961
1771
1846
1971
1416
1897
633
1708
1606
515
1397
1873
1374
1969
1918
1170
1660
1494
1764
2002
1938
1396
1926
1714
1659
1805
1593
1899
1850
1644
1877
1561
1895
1985
1353
395
1919
1522
1745
1721
901
1765
1939
2009
1949
1852
1792
1749
1675
1883
1240
1868
1615
1693
1720
1388
1325
1337
867
1751
1408
1715
1942
1706
1894
1260
1945
1700
1148
1373
351
1790
1861
1755
1155
1622
1743
1872
1979
1262
1789
1305
1311
1729
1929
823
1623
2005
1932
1814
1909
1728
1592
1712
1363
1338
1804
1402
1198
264
1117
1791
1419
1229
1924
1838
1785
1982
1683
1950
1199
1984
1830
1921
1980
1834
1341
1282
1989
1854
1395
1847
1900
1913
1777
1779
1333
1800
1966
1543
1882
1375
1811
1673
1679
889
1670
1879
1312
1741
1772
1663
1776
1642
1674
1472
1580
1264
1738
1999
1637

I decided to use a Jupyter notebook running a Python kernel solve the problem.

Importing the data

My first step was to copy the numbers above, paste them into a triple-quoted string, and assign that string to the variable raw_input:

raw_input = """1140
1736
1711
1803
1825
1268
1651
2007
1923
1661
1788
1876
2003
1752
1988
1955
1568
1478
1699
1717
1828
1636
1387
1870
1658
1572
1703
1185
1569
1515
1142
1407
1587
1608
1827
1546
1808
1937
1815
1957
1401
1763
1970
1960
1853
1987
1865
1567
1664
1961
1771
1846
1971
1416
1897
633
1708
1606
515
1397
1873
1374
1969
1918
1170
1660
1494
1764
2002
1938
1396
1926
1714
1659
1805
1593
1899
1850
1644
1877
1561
1895
1985
1353
395
1919
1522
1745
1721
901
1765
1939
2009
1949
1852
1792
1749
1675
1883
1240
1868
1615
1693
1720
1388
1325
1337
867
1751
1408
1715
1942
1706
1894
1260
1945
1700
1148
1373
351
1790
1861
1755
1155
1622
1743
1872
1979
1262
1789
1305
1311
1729
1929
823
1623
2005
1932
1814
1909
1728
1592
1712
1363
1338
1804
1402
1198
264
1117
1791
1419
1229
1924
1838
1785
1982
1683
1950
1199
1984
1830
1921
1980
1834
1341
1282
1989
1854
1395
1847
1900
1913
1777
1779
1333
1800
1966
1543
1882
1375
1811
1673
1679
889
1670
1879
1312
1741
1772
1663
1776
1642
1674
1472
1580
1264
1738
1999
1637"""

Now that I had the data in a string, I could split the string into an array, using the newline character as the delimiter. I named the array split_input:

>>> split_input = raw_input.split("\n")
>>> split_input
['1140', '1736', '1711', '1803', '1825', '1268', '1651', '2007', '1923', '1661', '1788', '1876', '2003', '1752', '1988', '1955', '1568', '1478', '1699', '1717', '1828', '1636', '1387', '1870', '1658', '1572', '1703', '1185', '1569', '1515', '1142', '1407', '1587', '1608', '1827', '1546', '1808', '1937', '1815', '1957', '1401', '1763', '1970', '1960', '1853', '1987', '1865', '1567', '1664', '1961', '1771', '1846', '1971', '1416', '1897', '633', '1708', '1606', '515', '1397', '1873', '1374', '1969', '1918', '1170', '1660', '1494', '1764', '2002', '1938', '1396', '1926', '1714', '1659', '1805', '1593', '1899', '1850', '1644', '1877', '1561', '1895', '1985', '1353', '395', '1919', '1522', '1745', '1721', '901', '1765', '1939', '2009', '1949', '1852', '1792', '1749', '1675', '1883', '1240', '1868', '1615', '1693', '1720', '1388', '1325', '1337', '867', '1751', '1408', '1715', '1942', '1706', '1894', '1260', '1945', '1700', '1148', '1373', '351', '1790', '1861', '1755', '1155', '1622', '1743', '1872', '1979', '1262', '1789', '1305', '1311', '1729', '1929', '823', '1623', '2005', '1932', '1814', '1909', '1728', '1592', '1712', '1363', '1338', '1804', '1402', '1198', '264', '1117', '1791', '1419', '1229', '1924', '1838', '1785', '1982', '1683', '1950', '1199', '1984', '1830', '1921', '1980', '1834', '1341', '1282', '1989', '1854', '1395', '1847', '1900', '1913', '1777', '1779', '1333', '1800', '1966', '1543', '1882', '1375', '1811', '1673', '1679', '889', '1670', '1879', '1312', '1741', '1772', '1663', '1776', '1642', '1674', '1472', '1580', '1264', '1738', '1999', '1637']

split_input is an array of strings which needed to be converted into integer values.

In many other languages, I’d do this by using the map function to apply a “convert a string to its integer value” function to every item in the array, creating a resulting array called expenses. Here’s the Python version of that approach:

>>> expenses = list(map(int, split_input))
>>> expenses
[1140, 1736, 1711, 1803, 1825, 1268, 1651, 2007, 1923, 1661, 1788, 1876, 2003, 1752, 1988, 1955, 1568, 1478, 1699, 1717, 1828, 1636, 1387, 1870, 1658, 1572, 1703, 1185, 1569, 1515, 1142, 1407, 1587, 1608, 1827, 1546, 1808, 1937, 1815, 1957, 1401, 1763, 1970, 1960, 1853, 1987, 1865, 1567, 1664, 1961, 1771, 1846, 1971, 1416, 1897, 633, 1708, 1606, 515, 1397, 1873, 1374, 1969, 1918, 1170, 1660, 1494, 1764, 2002, 1938, 1396, 1926, 1714, 1659, 1805, 1593, 1899, 1850, 1644, 1877, 1561, 1895, 1985, 1353, 395, 1919, 1522, 1745, 1721, 901, 1765, 1939, 2009, 1949, 1852, 1792, 1749, 1675, 1883, 1240, 1868, 1615, 1693, 1720, 1388, 1325, 1337, 867, 1751, 1408, 1715, 1942, 1706, 1894, 1260, 1945, 1700, 1148, 1373, 351, 1790, 1861, 1755, 1155, 1622, 1743, 1872, 1979, 1262, 1789, 1305, 1311, 1729, 1929, 823, 1623, 2005, 1932, 1814, 1909, 1728, 1592, 1712, 1363, 1338, 1804, 1402, 1198, 264, 1117, 1791, 1419, 1229, 1924, 1838, 1785, 1982, 1683, 1950, 1199, 1984, 1830, 1921, 1980, 1834, 1341, 1282, 1989, 1854, 1395, 1847, 1900, 1913, 1777, 1779, 1333, 1800, 1966, 1543, 1882, 1375, 1811, 1673, 1679, 889, 1670, 1879, 1312, 1741, 1772, 1663, 1776, 1642, 1674, 1472, 1580, 1264, 1738, 1999, 1637]

It works, but from a Python programming point of view, it just doesn’t feel right.

The Pythonic approach would involve using a list comprehension instead of map (and then using the resulting iterator into a list). It just seems more readable:

>>> expenses = [int(string) for string in split_input]
>>> expenses
[1140, 1736, 1711, 1803, 1825, 1268, 1651, 2007, 1923, 1661, 1788, 1876, 2003, 1752, 1988, 1955, 1568, 1478, 1699, 1717, 1828, 1636, 1387, 1870, 1658, 1572, 1703, 1185, 1569, 1515, 1142, 1407, 1587, 1608, 1827, 1546, 1808, 1937, 1815, 1957, 1401, 1763, 1970, 1960, 1853, 1987, 1865, 1567, 1664, 1961, 1771, 1846, 1971, 1416, 1897, 633, 1708, 1606, 515, 1397, 1873, 1374, 1969, 1918, 1170, 1660, 1494, 1764, 2002, 1938, 1396, 1926, 1714, 1659, 1805, 1593, 1899, 1850, 1644, 1877, 1561, 1895, 1985, 1353, 395, 1919, 1522, 1745, 1721, 901, 1765, 1939, 2009, 1949, 1852, 1792, 1749, 1675, 1883, 1240, 1868, 1615, 1693, 1720, 1388, 1325, 1337, 867, 1751, 1408, 1715, 1942, 1706, 1894, 1260, 1945, 1700, 1148, 1373, 351, 1790, 1861, 1755, 1155, 1622, 1743, 1872, 1979, 1262, 1789, 1305, 1311, 1729, 1929, 823, 1623, 2005, 1932, 1814, 1909, 1728, 1592, 1712, 1363, 1338, 1804, 1402, 1198, 264, 1117, 1791, 1419, 1229, 1924, 1838, 1785, 1982, 1683, 1950, 1199, 1984, 1830, 1921, 1980, 1834, 1341, 1282, 1989, 1854, 1395, 1847, 1900, 1913, 1777, 1779, 1333, 1800, 1966, 1543, 1882, 1375, 1811, 1673, 1679, 889, 1670, 1879, 1312, 1741, 1772, 1663, 1776, 1642, 1674, 1472, 1580, 1264, 1738, 1999, 1637]

Now that I had the expenses in a Python list (that’s Pythonese for “array”), I could work with them.

Combinations to the rescue!

Once again, the goal of the challenge was to find the two numbers in the expense report whose sum was 2020.

To solve this problem, we need a way to generate all the possible combinations of two numbers taken from the list. I could write this code, but Python’s itertools module has a combinations() method that can do just that.

Here’s a quick demo of combinations() in action. Given a list containing a small number of integers, it generates a list of the possible 2-number combinations you can get from the list, without repetition (that is, a number can’t appear more than once in any combination):

>>> from itertools import *
>>> simple_list = [1, 3, 5, 7, 9]
>>> list(combinations(simple_list, 2))
[(1, 3), (1, 5), (1, 7), (1, 9), (3, 5), (3, 7), (3, 9), (5, 7), (5, 9), (7, 9)]

itertools also has a combinations_with_replacement() method. Rather than tell you what it does, let me show you:

>>> list(combinations_with_replacement(simple_list, 2))
[(1, 1), (1, 3), (1, 5), (1, 7), (1, 9), (3, 3), (3, 5), (3, 7), (3, 9), (5, 5), (5, 7), (5, 9), (7, 7), (7, 9), (9, 9)]

With that in mind, I used combinations() to generate a list of all the possible two-number combinations in expenses, which I assigned to a variable named all_expense_pairs:

>>> all_expense_pairs = list(combinations(expenses, 2))
>>> len(all_expense_pairs)
19900

Now that we have all the possible two-number combinations from the expense report, we can try to find the one(s) whose numbers add up to 2020.

Any time you’re in a situation where you need to find values in an array that match some criteria, you should think about applying a filter() function. I did just that: I used a filter() to extract a list of only those pairs summed to 2020…

def sums_to_2020(values):
    return sum(values) == 2020

>>> result = list(filter(sums_to_2020, all_expense_pairs))
>>> result
[(1387, 633)]

The resulting list had one tuple, (1387, 633), whose values sum to 2020. I entered the product of these two numbers — 877971 — and completed the first challenge.

The Day 1 challenge, part two

Here’s the text from part two:

The Elves in accounting are thankful for your help; one of them even offers you a starfish coin they had left over from a past vacation. They offer you a second one if you can find three numbers in your expense report that meet the same criteria.

Using the above example again, the three entries that sum to 2020 are 979366, and 675. Multiplying them together produces the answer, 241861950.

In your expense report, what is the product of the three entries that sum to 2020?

Had I solved the problem from first principles, the solution might have taken a lot of extra work. Thanks to the use of itertools.combinations(), the solution for part two took three lines of code:

>>> all_expense_triplets = list(combinations(expenses, 3))
>>> result2 = list(filter(sums_to_2020, all_expense_triplets))
>>> result2
[(867, 264, 889)]

Once again, the resulting list had one tuple, (867, 264, 889), and its values, added up, were 2020. I entered the product of these three numbers — 203481432 — and completed the second challenge.

Feeling simultaneously proud and soiled

Thanks to Python (and remembering that it had a library that could do combinations and permutations),  I made a personal best in solving the Day 1 puzzles. I’m pretty pleased, but at the same time, I did so little work that it feels as if I’ve cheated. I may have to try solving the problem from first principles — if I have the time.

Other days’ solutions:

Here are my solutions for other days in Advent of Code 2020:

Categories
Programming

The Advent of Code is coming in a few days!

We’re only a few days from December, which means it will soon be time for the great programming exercise known as the Advent of Code!

Think of it as an Advent calendar, but chocolates (or cheese, or wine), you’re presented with a new programming puzzle every day between the start of December and Christmas Day, in which you try to save Santa’s mission. You can use whatever programming language you want, and you don’t need to be an expert — as the site says, “just a little programming knowledge and some problem solving skills will get you pretty far.”

Advent of Code started in 2015, and has been taking place every year ever since. The 2020 edition begins on Tuesday, December 1st at 12:00 midnight Eastern time (UTC-5).

Not only do I plan on participating in this year’s Advent of Code, but I might even use a couple of the challenges in the Python class I’m currently teaching on behalf of Computer Coach.

Solving Advent of Code 2019’s day one challenge

Here’s the premise of the 2019 Advent of Code’s challenges: Santa is stuck at the edge of the solar system, and you have to rescue him. Each day’s challenge, which has two parts, gets you closer to bringing him home and saving Christmas.

Day one challenge, part one

Here’s the first part of day one’s challenge:

The Elves quickly load you into a spacecraft and prepare to launch.

At the first Go / No Go poll, every Elf is Go until the Fuel Counter-Upper. They haven’t determined the amount of fuel required yet.

Fuel required to launch a given module is based on its mass. Specifically, to find the fuel required for a module, take its mass, divide by three, round down, and subtract 2.

For example:

  • For a mass of 12, divide by 3 and round down to get 4, then subtract 2 to get 2.
  • For a mass of 14, dividing by 3 and rounding down still yields 4, so the fuel required is also 2.
  • For a mass of 1969, the fuel required is 654.
  • For a mass of 100756, the fuel required is 33583.

The Fuel Counter-Upper needs to know the total fuel requirement. To find it, individually calculate the fuel needed for the mass of each module (your puzzle input), then add together all the fuel values.

What is the sum of the fuel requirements for all of the modules on your spacecraft?

While the problems in the Advent of Code are the same for every participant, the data for each participant is different (there’s a sign-up process, which gives you an account, your own progress tracker, and your own data). This prevents participants from simply sharing the solution.

Here are the module masses that were provided for my account:

134492
88713
84405
148193
95951
63545
137840
65558
124836
95431
77622
91864
108677
116871
119496
97172
86115
105704
68613
77114
114013
52766
57048
80814
73888
58253
135934
97409
112439
98262
116047
57456
124261
83006
101495
133449
111372
56146
87818
92209
149259
124559
141838
147988
65703
125566
59650
139564
92430
126307
120406
147383
84362
51529
146366
131840
53270
71886
118767
104311
126181
76964
129430
95489
91098
54133
110057
107276
118226
96104
135382
85152
61697
143417
148879
126846
130205
111170
86687
113729
123330
56976
148470
66028
129715
75686
74964
148258
72669
88809
78173
92699
124806
67217
139066
136002
135730
145708
142054
135772

I decided to use the Python REPL to tackle this problem.

My first step was to copy the numbers above, paste them into a triple-quoted string, and assign that string to the variable raw_input:

raw_input = """134492
88713
84405
148193
95951
63545
137840
65558
124836
95431
77622
91864
108677
116871
119496
97172
86115
105704
68613
77114
114013
52766
57048
80814
73888
58253
135934
97409
112439
98262
116047
57456
124261
83006
101495
133449
111372
56146
87818
92209
149259
124559
141838
147988
65703
125566
59650
139564
92430
126307
120406
147383
84362
51529
146366
131840
53270
71886
118767
104311
126181
76964
129430
95489
91098
54133
110057
107276
118226
96104
135382
85152
61697
143417
148879
126846
130205
111170
86687
113729
123330
56976
148470
66028
129715
75686
74964
148258
72669
88809
78173
92699
124806
67217
139066
136002
135730
145708
142054
135772"""

Now that I had the data in a string, I could split the string into an array, using the newline character as the delimiter. I named the array split_input:

>>> split_input = raw_input.split("\n")
>>> split_input
['134492', '88713', '84405', '148193', '95951', '63545', '137840', '65558', '124836', '95431', '77622', '91864', '108677', '116871', '119496', '97172', '86115', '105704', '68613', '77114', '114013', '52766', '57048', '80814', '73888', '58253', '135934', '97409', '112439', '98262', '116047', '57456', '124261', '83006', '101495', '133449', '111372', '56146', '87818', '92209', '149259', '124559', '141838', '147988', '65703', '125566', '59650', '139564', '92430', '126307', '120406', '147383', '84362', '51529', '146366', '131840', '53270', '71886', '118767', '104311', '126181', '76964', '129430', '95489', '91098', '54133', '110057', '107276', '118226', '96104', '135382', '85152', '61697', '143417', '148879', '126846', '130205', '111170', '86687', '113729', '123330', '56976', '148470', '66028', '129715', '75686', '74964', '148258', '72669', '88809', '78173', '92699', '124806', '67217', '139066', '136002', '135730', '145708', '142054', '135772']

split_input is an array of strings which needed to be converted into integer values.

In many other languages, I’d do this by using the map function to apply a “convert a string to its integer value” function to every item in the array, creating a resulting array called masses. Here’s the Python version of that approach:

>>> masses = list(map(int, split_input))
>>> masses
[134492, 88713, 84405, 148193, 95951, 63545, 137840, 65558, 124836, 95431, 77622, 91864, 108677, 116871, 119496, 97172, 86115, 105704, 68613, 77114, 114013, 52766, 57048, 80814, 73888, 58253, 135934, 97409, 112439, 98262, 116047, 57456, 124261, 83006, 101495, 133449, 111372, 56146, 87818, 92209, 149259, 124559, 141838, 147988, 65703, 125566, 59650, 139564, 92430, 126307, 120406, 147383, 84362, 51529, 146366, 131840, 53270, 71886, 118767, 104311, 126181, 76964, 129430, 95489, 91098, 54133, 110057, 107276, 118226, 96104, 135382, 85152, 61697, 143417, 148879, 126846, 130205, 111170, 86687, 113729, 123330, 56976, 148470, 66028, 129715, 75686, 74964, 148258, 72669, 88809, 78173, 92699, 124806, 67217, 139066, 136002, 135730, 145708, 142054, 135772]

It works, but from a Python programming point of view, it just doesn’t feel right.

The Pythonic approach would involve using a list comprehension instead of map (and then using the resulting iterator into a list). It just seems more readable:

>>> masses = [int(string) for string in split_input]
>>> masses
[134492, 88713, 84405, 148193, 95951, 63545, 137840, 65558, 124836, 95431, 77622, 91864, 108677, 116871, 119496, 97172, 86115, 105704, 68613, 77114, 114013, 52766, 57048, 80814, 73888, 58253, 135934, 97409, 112439, 98262, 116047, 57456, 124261, 83006, 101495, 133449, 111372, 56146, 87818, 92209, 149259, 124559, 141838, 147988, 65703, 125566, 59650, 139564, 92430, 126307, 120406, 147383, 84362, 51529, 146366, 131840, 53270, 71886, 118767, 104311, 126181, 76964, 129430, 95489, 91098, 54133, 110057, 107276, 118226, 96104, 135382, 85152, 61697, 143417, 148879, 126846, 130205, 111170, 86687, 113729, 123330, 56976, 148470, 66028, 129715, 75686, 74964, 148258, 72669, 88809, 78173, 92699, 124806, 67217, 139066, 136002, 135730, 145708, 142054, 135772]

Once I had the masses, I could then calculate the fuel requirements for each mass. This may be the only time I’ve ever made use of Python’s floor division operator, which performs an integer division, rounding down:

>>> fuel_requirements = list(map(lambda mass: mass // 3 - 2, masses))
>>> fuel_requirements
[44828, 29569, 28133, 49395, 31981, 21179, 45944, 21850, 41610, 31808, 25872, 30619, 36223, 38955, 39830, 32388, 28703, 35232, 22869, 25702, 38002, 17586, 19014, 26936, 24627, 19415, 45309, 32467, 37477, 32752, 38680, 19150, 41418, 27666, 33829, 44481, 37122, 18713, 29270, 30734, 49751, 41517, 47277, 49327, 21899, 41853, 19881, 46519, 30808, 42100, 40133, 49125, 28118, 17174, 48786, 43944, 17754, 23960, 39587, 34768, 42058, 25652, 43141, 31827, 30364, 18042, 36683, 35756, 39406, 32032, 45125, 28382, 20563, 47803, 49624, 42280, 43399, 37054, 28893, 37907, 41108, 18990, 49488, 22007, 43236, 25226, 24986, 49417, 24221, 29601, 26055, 30897, 41600, 22403, 46353, 45332, 45241, 48567, 47349, 45255]

The map/list/lambda approach works just fine, but once again, a list comprehension just seems more elegant to my eye:

>>> fuel_requirements = [mass // 3 - 2 for mass in masses]
>>> fuel_requirements
[44828, 29569, 28133, 49395, 31981, 21179, 45944, 21850, 41610, 31808, 25872, 30619, 36223, 38955, 39830, 32388, 28703, 35232, 22869, 25702, 38002, 17586, 19014, 26936, 24627, 19415, 45309, 32467, 37477, 32752, 38680, 19150, 41418, 27666, 33829, 44481, 37122, 18713, 29270, 30734, 49751, 41517, 47277, 49327, 21899, 41853, 19881, 46519, 30808, 42100, 40133, 49125, 28118, 17174, 48786, 43944, 17754, 23960, 39587, 34768, 42058, 25652, 43141, 31827, 30364, 18042, 36683, 35756, 39406, 32032, 45125, 28382, 20563, 47803, 49624, 42280, 43399, 37054, 28893, 37907, 41108, 18990, 49488, 22007, 43236, 25226, 24986, 49417, 24221, 29601, 26055, 30897, 41600, 22403, 46353, 45332, 45241, 48567, 47349, 45255]

And now that I had the fuel requirements for each module, all I had to do was get their sum:

>>> total_fuel = sum(fuel_requirements)
>>> total_fuel
3454942

I entered the value for total_fuel into the solution text field, and Advent of Code immediately told me that I had solved part one of the day one challenge! So far, so good.

Day one challenge, part two

Christine Darden, one of the “Hidden Figures” mathematicians who helped land the astronauts on the Moon.

Part two of the challenge was a refinement of part one:

During the second Go / No Go poll, the Elf in charge of the Rocket Equation Double-Checker stops the launch sequence. Apparently, you forgot to include additional fuel for the fuel you just added.

Fuel itself requires fuel just like a module – take its mass, divide by three, round down, and subtract 2. However, that fuel also requires fuel, and that fuel requires fuel, and so on. Any mass that would require negative fuel should instead be treated as if it requires zero fuel; the remaining mass, if any, is instead handled by wishing really hard, which has no mass and is outside the scope of this calculation.

So, for each module mass, calculate its fuel and add it to the total. Then, treat the fuel amount you just calculated as the input mass and repeat the process, continuing until a fuel requirement is zero or negative. For example:

  • A module of mass 14 requires 2 fuel. This fuel requires no further fuel (2 divided by 3 and rounded down is 0, which would call for a negative fuel), so the total fuel required is still just 2.
  • At first, a module of mass 1969 requires 654 fuel. Then, this fuel requires 216 more fuel (654 / 3 - 2). 216 then requires 70 more fuel, which requires 21 fuel, which requires 5 fuel, which requires no further fuel. So, the total fuel required for a module of mass 1969 is 654 + 216 + 70 + 21 + 5 = 966.
  • The fuel required by a module of mass 100756 and its fuel is: 33583 + 11192 + 3728 + 1240 + 411 + 135 + 43 + 12 + 2 = 50346.

What is the sum of the fuel requirements for all of the modules on your spacecraft when also taking into account the mass of the added fuel? (Calculate the fuel requirements for each module separately, then add them all up at the end.)

Upon reading this, my first thought was:

The trick to writing recursive functions is to solve the “escape” case first — that is, the case where you stop the recursion and just return a value.

For this problem, the “escape” case is when the repeated fuel calculation gives a result of 0 or less:

if result <= 0:
  return 0

Otherwise, take the result, and apply the fuel calculation to it again. That’s what gives us the recursive part. Here’s the resulting if statement:

if result <= 0: 
  return 0
else: 
  return result + fuel_required(result)

And finally, we have to handle the initial calculation. The end result is the fuel_required function:

def fuel_required(mass):
  result = mass // 3 - 2
  if result <= 0:
    return 0
  else:
    return result + fuel_required(result)

Now that we have the fuel_required function, we can apply it to every item in the masses array from part one:

>>> updated_fuel_requirements = [fuel_required(mass) for mass in masses]
>>> updated_fuel_requirements
[67212, 44325, 42171, 74062, 47941, 31741, 68888, 32746, 62387, 47682, 38780, 45902, 54306, 58402, 59715, 48553, 43027, 52822, 34277, 38525, 56974, 26354, 28495, 40375, 36913, 29094, 67934, 48670, 56187, 49098, 57991, 28698, 62098, 41470, 50716, 66693, 55654, 28043, 43877, 46073, 74596, 62245, 70886, 73962, 32822, 62751, 29795, 69750, 46184, 63121, 60171, 73658, 42148, 25734, 73150, 65887, 26606, 35910, 59351, 52123, 63058, 38449, 64681, 47710, 45516, 27037, 54994, 53606, 59081, 48018, 67657, 42543, 30817, 71674, 74407, 63393, 65068, 55551, 43311, 56833, 61632, 28459, 74203, 32983, 64824, 37810, 37450, 74096, 36304, 44373, 39054, 46318, 62371, 33576, 69500, 67968, 67832, 72820, 70993, 67853]

This yields the updated fuel requirements for each module. The final step was to get their sum:

>>> updated_total_fuel = sum(updated_fuel_requirements)
>>> updated_total_fuel
5179544

Entering the value of updated_total_fuel into the solution text field completed the day one challenge.

Categories
Programming

“My code isn’t working” is a great problems-and-solutions flowchart for Python programmers

Tap to view at full size.

Dr. Martin Jones, the author behind the book and site Python for Biologists, has come up with a chart to help Python programmers when their code doesn’t work and they can’t figure out why.

He writes:

A few times a year, I have the job of teaching a bunch of people who have never written code before how to program from scratch. The nature of programming being what it is, the same error crop up every time in a very predictable pattern. I usually encourage my students to go through a step-by-step troubleshooting process when trying to fix misbehaving code, in which we go through these common errors one by one and see if they could be causing the problem. Today, I decided to finally write this troubleshooting process down and turn it into a flowchart in non-threatening colours.

Behold, the “my code isn’t working” step-by-step troubleshooting guide! Follow the arrows to find the likely cause of your problem – if the first thing you reach doesn’t work, then back up and try again.

It’s intended for programmers who are new to Python, but even experienced Pythonistas sometimes get distracted and stuck on simple things. I’m keeping a copy handy.

You can tap on the image above to view it at full size, and there’s also a printable PDF version as well.

[ Thanks to my UC Baseline classmate Daniel Jimenez for the find! ]

Categories
Programming What I’m Up To

Computer Coach’s “Intro to Python Coding” course (taught by Yours Truly) starts tonight!

The online Intro to Python Coding course that I’m teaching on behalf of Tampa Bay’s own Computer Coach Training Center starts tonight at 6:00 p.m.. For the next five weeks, on Monday and Wednesday evenings from 6:00 to 10:00, I’ll be leading a class of Python learners through “code along with me” exercises in the Python programming language.

Photo: Joey deVilla points at a projected screen of code with co-presented Angela Don.
Dropping code science at BarCamp.

The format of the course will be pretty much the same as the one I use at Tampa iOS Meetup, where I lead the group through a “code along with me” exercise. I project what’s on my computer on the big screen, and everyone follows along, entering the code as I explain what’s happening.

Since Python has a REPL (Read-Evaluate-Print Loop), I can also have the class go through some exercises and try little coding challenges. It will be a “learn by doing” kind of class.

The main textbooks for the course (which will be provided to students) are Python Crash Course, 2nd Edition…

Book cover: “Python Crash Course, 2nd edition: A Hands-On, Project-Based Introduction to Programming”

…and Automate the Boring Stuff with Python, 2nd edition (which is free to read online):

Book cover: “Automate the Boring Stuff with Python, 2nd edition: Practical Programming for Total Beginners”In order to minimize confusion, we’ll all use the same tools in the course, namely the Anaconda Individual Edition distribution of Python 3.7 and associated tools…Logo: Anaconda…and Visual Studio Code:

Logo: Visual Studio CodeBoth are available free of charge, and run on macOS, Windows, and Linux.

It’ll be fun! Watch this space; I’ll post some snippets from the course as it progresses.

Interested in signing up? Visit Computer Coach’s site and speak to them. Don’t dawdle — it starts tonight!

Categories
Current Events Programming What I’m Up To

I’m teaching an online Python programming course!

Photo: Man’s hand on Mac laptop, with Python book on the side. Caption: “Intro to Python course / Starts this Monday!”

Graohic: Computer Coach Training Center logoI’ll be teaching a live online course on Python programming on behalf of Computer Coach Training Center starting Monday. Here are the details:

  • What: Intro to Python Coding course
  • When: Monday and Wednesday evenings, 6:00 – 10:00 p.m., starting Monday, July 13 and ending Wednesday, August 12 (6 weeks, twice a week)
  • Where: Online.
  • How much: $900 — and Computer Coach has grants that can cover the cost if you’re unemployed and based in the Tampa Bay area (contact them to see if you qualify)
  • What you’ll need:
    • A computer that was made sometime in the last ten years. My main computer is a 2014-era MacBook Pro, but I’ll be doing demonstrations on a 2012-era Lenovo ThinkPad running Linux Mint, a 2009-era Compaq laptop running Peppermint Linux, and a $35 Raspberry Pi.
    • An internet connection. This is an online course, after all.

To register for this course, visit this page and tap the Attend Online button. Someone from Computer Coach will contact you.

Screenshot: The Meetup page for the Python course, with the “Attend online” button highlighted.

The course description

Photo: Woman’s hands typing on Mac laptop.

This is an introduction to the Python programming language. Now in the top 10 programming languages according to the TIOBE Programming Language Index, it is versatile enough to have a wide array of uses, from simple scripting to powering Instagram, Spotify, Netflix, Dropbox, and more. Its combination of simplicity and vast scientific and math libraries have made it the preferred programming language for data science and machine learning. If you’re looking for a first programming language, Python is an excellent choice.

 

This is not a passive course! This isn’t the kind of course where the instructor lectures over slides while you take notes (or pretend to take notes while surfing the web or checking your social media feeds). In this course, you’ll be actively taking part in the learning process, entering code, experimenting, making mistakes, correcting those mistakes, and producing working applications. You will learn by doing. At the end of each session, you’ll have a collection of little Python programs that you wrote, and which you can use as the basis for your own work.

The course will start at the most basic level by walking you through the process of downloading and installing the necessary tools to start Python programming. From there, you’ll learn the building blocks of the Python programming language:

  • Control structures that determine what your programs do,
  • Data structures to store the information that your programs act on,
  • Functions and objects to organize your code, and
  • Using libraries as building blocks for your applications.

You’ll write all sorts of programs…

  • You’ll use Python in “immediate mode” to perform quick calculations (and you’ll sharpen your command-line skills in the process).
  • You’ll write scripts to simplify or automate tedious tasks.
  • You’ll build web applications.
  • And since it’s a networked, data-driven world where no application is an island, you’ll learn how to use Python to interact with web services and databases.

Better still, you’ll learn how to think like a programmer. You’ll learn how to look at a goal and learn how you could write a program to meet it, and how that program could be improved or enhanced. You’ll learn skills that will serve you well as you take up other programming languages, and even learn a little bit about the inner workings of computers, operating systems, and the internet.

 

Categories
Career Programming

Programmer interview challenge 2, part 4: Using “watchers” to play FizzBuzz “properly”

The Marvel Comics character known as “The Watcher” - “I am known as The WATCHER. My sworn task is to observe and chronicle great events within this sector of the universe. My curse is to always witness and never participate. I must be true to this duty, even to the brink of Armageddon!”
Marvel Comics’ Watcher. His people are sworn to observe, but not interfere in, everything that happens in the universe.

After reading the previous article on FizzBuzz solutions, Reginald “raganwald” Braithwaite, whom I know from my days as part of the Toronto tech scene, tweeted this:

JenniferPlusPlus agrees:

They both make a good point. If you’re playing the FizzBuzz game as the original children’s game and not as an exercise to prove that you can actually write a program, you’d do it like this:

  • Players sit in a circle, not unlike “the circle” in That ’70s Show.(Players don’t have to be high, unless it helps.)
  • The player designated to go first says the number 1, and each player afterwards counts one number in turn. The next player in the circle says 2, and so on.
  • However, for every third number, instead of calling out the number, the player whose turn it is should say “Fizz”.
  • …and for every fifth number, instead of calling out the number, the player whose turn it is should say “Buzz”.
  • The “Fizz” and “Buzz” rules, as the kids would say, stack. In other words, for every number that is both the third and fifth, the player needs to say “Fizz” followed by “Buzz”, or “FizzBuzz”.

So in the spirit of the original game, I’ve put together a FizzBuzz solution that uses “watchers” to keep track of  “every xth number”, with one watcher to keep track of when it’s time to say “Fizz”, and another for when it’s time to say “Buzz”. When it’s time to say “FizzBuzz”, they’ll work in tandem.

I created a class called WordWatcher, which can be summarized as shown below:

Here’s its code:

class WordWatcher:

  def __init__(self, interval, word):
    self.counter = 0
    self.time_for_word = False
    self.interval = interval
    self.word = word

  def observe_next_turn(self):
    self.counter += 1
    if self.counter == self.interval:
      self.counter = 0
      self.time_for_word = True
    else:
      self.time_for_word = False

  def speak(self):
    if self.time_for_word:
      return self.word
    else:
      return ""

Some notes about this code:

    • For those of you who aren’t familiar with Python’s approach to class methods, the first parameter for every method in a class is self. It’s the one parameter you don’t fill when calling a method, because Python calls it implicitly (seemingly in violation of Python’s general guideline that explicit is better than implicit). There’s a reason behind this, and it’s explained in this article: Understanding self in Python.
    • Also note that instance variables are declared and defined in the initializer method, __init__(), and any reference to them is always preceded by self.
    • The observe_next_turn() method is meant to be called as the fizzBuzz method proceeds to each new number. It updates the watcher’s internal counter and sets the time_for_word flag accordingly.
    • The speak() method outputs the watcher’s word if it’s time to say the word, or an empty string otherwise.

For FizzBuzz, we’ll need to create two watchers:

  1. One to keep watch for every third turn, at which point it should say “Fizz”, and
  2. one to keep watch for every third turn, at which point it should say “Buzz”.

With the WordWatcher class defined, we can create these two watchers like so:

fizz_watcher = WordWatcher(3, "Fizz")
buzz_watcher = WordWatcher(5, "Buzz")

It will become handy to have these two watchers in the same place. Since the “ha ha only serious” joke about Python is that everything is a list, let’s put them into a list:

word_watchers = [fizz_watcher, buzz_watcher]

Let’s define a fizzBuzz() function that makes use of this list of word watchers:

def fizzBuzz(word_watchers = [], first = 1, last = 100):
  final_result = ""

  for number in range(1, 101):
    current_result = ""

    if len(word_watchers) > 0:
      # This part might need some explaining
      _ = [word_watcher.observe_next_turn() for word_watcher in word_watchers]
      words = map(lambda word_watcher : word_watcher.speak(), word_watchers)
      current_result += functools.reduce(lambda total, next_element : total + next_element, list(words))
    
    if current_result == "":
      current_result = str(number)

    final_result += current_result

    if number < last:
      final_result += ", "
    else:
      final_result += "."

  return final_result

If you’ve been following the FizzBuzz series of articles, most of this code will be familiar. The part that might need explaining is the part with the comment “This part might need some explaining”.

Explaining the part that needs explaining

Let’s look at the first of the three lines of code in that part:

_ = [word_watcher.observe_next_turn() for word_watcher in word_watchers]
  • The _ on the left side of the = sign is a throwaway variable. It says “I don’t care about what you do on the other side of the = sign; only that you do something on the other side of the = sign”.
  • On the right side of the= sign is a list comprehension, which is Python’s “show, don’t tell” way of building lists. This list comprehension simply says “call the observe_next_turn() method of every object in the list”.

Let’s look at the next line:

words = map(lambda word_watcher : word_watcher.speak(), word_watchers)
  • This line creates a map that converts the watchers in the list into the words they should say for this turn. If the current turn means that it’s time for any one of them to speak, the watcher will be mapped to the word it’s supposed to say. Otherwise, it will be mapped to an empty string.

And now, the final line:

current_result += functools.reduce(lambda total, next_element : total + next_element, list(words))
  • For some reason, map() comes built into Python, but you have to import the functools library in order to use map()’s partner in crime, reduce(). Remember reduce() is a functional programming thingy that takes a collection of items, performs some kind of calculation on that collection, and returns a single value (which you might call a reduction of the collection).
  • The first argument that I’ve provided to reduce() is a lambda — a small function that isn’t given a name — that simply takes the current item in the list and adds it to the previous collected items. Applied over the entire list, it builds a “total”, which in this case is all the words output by the watchers’ speak() methods concatenated together.
  • The second argument is the words map converted into a list. This is the list that the reduce() method will operate on.

At the end of those three lines, current_result will contain one of the following:

  • The empty string
  • Fizz
  • Buzz
  • FizzBuzz

If current_result is still empty at this point, it means that it’s not time for any of the watchers’ words. If this is the case, the string version of the current number is concatenated to current_result:

if current_result == "":
      current_result += str(number)

Here’s the code in its entirety:

import functools

class WordWatcher:

  def __init__(self, interval, word):
    self.counter = 0
    self.time_for_word = False
    self.interval = interval
    self.word = word

  def observe_next_turn(self):
    self.counter += 1
    if self.counter == self.interval:
      self.counter = 0
      self.time_for_word = True
    else:
      self.time_for_word = False

  def speak(self):
    if self.time_for_word:
      return self.word
    else:
      return ""


def fizzBuzz(word_watchers = [], first = 1, last = 100):
  final_result = ""

  for number in range(1, 101):
    current_result = ""

    if len(word_watchers) > 0:
      _ = [word_watcher.observe_next_turn() for word_watcher in word_watchers]
      words = map(lambda word_watcher : word_watcher.speak(), word_watchers)
      current_result += functools.reduce(lambda total, next_element : total + next_element, list(words))
    
    if current_result == "":
      current_result += str(number)

    final_result += current_result

    if number < last:
      final_result += ", "
    else:
      final_result += "."

  return final_result

fizz_watcher = WordWatcher(3, "Fizz")
buzz_watcher = WordWatcher(5, "Buzz")
word_watchers = [fizz_watcher, buzz_watcher]
print(fizzBuzz(word_watchers))

And for completeness’ sake, here’s the test file:

import pytest
from fizzbuzz_with_watchers import fizzBuzz, WordWatcher

plain_1_to_100_result = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100."
fizzBuzz_1_to_100_result = "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26, Fizz, 28, 29, FizzBuzz, 31, 32, Fizz, 34, Buzz, Fizz, 37, 38, Fizz, Buzz, 41, Fizz, 43, 44, FizzBuzz, 46, 47, Fizz, 49, Buzz, Fizz, 52, 53, Fizz, Buzz, 56, Fizz, 58, 59, FizzBuzz, 61, 62, Fizz, 64, Buzz, Fizz, 67, 68, Fizz, Buzz, 71, Fizz, 73, 74, FizzBuzz, 76, 77, Fizz, 79, Buzz, Fizz, 82, 83, Fizz, Buzz, 86, Fizz, 88, 89, FizzBuzz, 91, 92, Fizz, 94, Buzz, Fizz, 97, 98, Fizz, Buzz."

def test_fizzBuzz_null():
  result = fizzBuzz()
  assert result == plain_1_to_100_result, f"The watcher solution returned the wrong result:\nExpected: {plain_1_to_100_result}\nActual: {result}."

def test_fizzBuzz_fizz_and_buzz():
  fizz_watcher = WordWatcher(3, "Fizz")
  buzz_watcher = WordWatcher(5, "Buzz")
  word_watchers = [fizz_watcher, buzz_watcher]
  result = fizzBuzz([fizz_watcher, buzz_watcher])
  assert result == fizzBuzz_1_to_100_result, f"The watcher solution returned the wrong result:\nExpected: {fizzBuzz1To100Result}\nActual: {result}."

You can download fizzbuzz_with_watchers.py and test_fizzbuzz_with_watchers.py here (2KB, zipped folder with 2 Python files).

That’s a lot of fuss for Fizzbuzz. Why did you do all that?

  1. Reginald asked me to, and I’ve known and respected him for ages, and JenniferPlusPlus seconded the request.
  2. Wait until you see what customers ask you to do.

Did any of this stuff fly over your head?

  1. Don’t feel bad. I had the same trouble when I first learned functional programming, and that was back in 1991, when the computers that ran functional language interpreters were in labs. I spent a lot of time in Queen’s University’s DEClab, which was full of machines that were cutting edge at the time made by a vendor that no longer exists. Computer time, as well as info on any kind of programming, never mind functional programming, was a lot harder to come by. (In case you were wondering, the language we learned was Miranda.)
  2. If you’ve never worked in Python, some of it can be quite weird. It does eventually make sense.
  3. Let me know, either via email or in the comments, if there’s anything you’d like me to cover in greater detail.

Do you have an alternate solution?

I’ve love to hear about it and present it here! Again, let me know via email or in the comments.

What’s next

An elegant JavaScript implementation.

Previously, in the “Programmer interview challenge” series

Categories
Career Programming

Programmer interview challenge 2, part 3: FizzBuzz, minus the modulo operator, plus grit

The modulo operator

The standard FizzBuzz solution relies on the modulo operator, which it uses to determine if a number is a multiple of 3 or 5.

If you have a math, computer science, or engineering background, the odds are good that you encountered the modulo operator in your studies, as your courses tended to take a mathematical approach to  programming. (Remember relational calculus from your intro to databases course?)

If you came into programming from some other field and really got into it because you have a knack for problem-solving, you might not be aware of modulo math. That doesn’t mean that you can’t come up with a FizzBuzz solution.

FizzBuzz minus the modulo operator

When you present the FizzBuzz challenge to a large enough group of programmers — typically a dozen or more — there will be a very determined person who will insist that you provide them with no hints whatsoever. It happens.

When that happens, there’s invariably someone who’s either never heard of the modulo operator (%, which returns the remainder of a division operation) or has forgotten it exists. I’ve also seen a competition comprising quick programming challenges where contestants were told to implement FizzBuzz, but without using modulo.

The more mathematically-inclined will use a method like this:

def multiple_of_n(factor, number):
  return math.floor(number / factor) == number / factor

multiple_of_n() determines if a number n is a multiple of a factor f if the result of n / f is the same as n / f with the fractional part removed.

Occasionally, you’ll run into programmers who are unaware that there are functions to remove the fractional part of a number, either through rounding or truncation. Some of them make up for their lack of math background with a combination of creativity and grit.

I’ve seen one solution that looked something like this:

def multiple_of_5(number):
  number_as_string = str(number)
  last_digit = number_as_string[-1]
  return last_digit in ['0', '5']

This function turns the given number into a string, isolates the rightmost character of that string, and then returns True if that character is “0” or “5”, which is true for the string form of numbers that are multiples of 5.

My reaction:

That was nothing compared to one method I saw that someone cobbled together to determine if a number was a multiple of 3. They remembered the old grade-school rule that if you add the digits of a number and the total is a multiple of 3, then the number is a multiple of 3.

Based on that, they wrote something like this:

def multiple_of_3(number):
  number_as_string = str(number)
  total_of_digits = 0
  for digit in number_as_string:
    total_of_digits += int(digit)
  return total_of_digits in [3, 6, 9, 12, 15, 18]

Again, this function starts by converting the given number into a string. It then iterates through that string character by character, turning each character into a number and adding it to a running total. It then checks to see if that total is in a list of multiples of 3.

Since the standard FizzBuzz challenge is supposed to be performed on the numbers 1 through 100, the largest multiple of 3 will be 99, and the sum of its digits will be 18. Hence the list of multiples of 3 starts with 3 and ending with 18.

My reaction:

But hey, it works!

FizzBuzz plus grit

I’ve seen a handful of people with bachelors’ and even masters’ degrees in computer science face the FizzBuzz test and completely fail to produce working code. I’ve been more impressed by the self-taught coders who, in spite of not knowing about the modulo operator, charge head-first into the problem and solve it. These non-modulo solutions might cause a mathematician to react like this…

…but I think that they’re a sign of grit, which is an important quality for a programmer. These people took what they knew, applied a little creativity, and solved a problem that they shouldn’t have been able to solve. They remind me of a line from aviation pioneer Igor Sikorsky:

According to the laws of aerodynamics, the bumblebee can’t fly, but the bumblebee doesn’t know the laws of aerodynamics, so it goes ahead and flies.

Sooner or later, if you’re working on applications that actually matter, you’re going to run into seemingly insurmountable problems. There will always be the fear that something is just too hard to do. Impostor syndrome may rear its ugly head. Developing grit — and yes, it can be developed — is important, and it’s a quality I look for when forming a team.

What’s next

Using “watchers” to play FizzBuzz “properly”. 

Previously, in the “Programmer interview challenge” series