FiveThirtyEight’s April 6th Riddler Express

Every week, the statistical analysis website FiveThirtyEight publishes some riddles. In this week’s riddle, titled When Will The Arithmetic Anarchists Attack?, the challenge is to figure how many dates between 1/1/2000 and 12/31/2099, using the date format MM/DD/YY, meet the following condition:

MM * DD == YY

For example, for the dates January 1, 2001 and February 12, 2024:

01 * 01 == 01 # 01/01/01
02 * 12 == 24 # 02/12/24

Sticking with their presentation of the problem, which you should definitely read as it’s far more creative than my summary of it, we will call each match an “attack”.

The specific questions are:

  1. How many attacks will happen between the beginning of 2001 and the end of 2099?
  2. What year will see the most vandalism?
  3. What year will see the least vandalism?
  4. What will be the longest gap between attacks?

I used to Python with a pandas DataFrame to figure this out.

The Setup

from datetime import date, timedelta
from collections import defaultdict
import numpy as np
import pandas as pd

day = date(2000,1,1)
lastday = date(2099,12,31)

attacks = defaultdict(int)

while day <= lastday:
    m = day.month
    d = day.day
    y = day.year - 2000
    if m*d == y:
        attacks[day.year] += 1
    day = day + timedelta(days=1)
    
df = pd.DataFrame.from_dict(attacks, orient="index")
df.columns = ["Attacks"]

Exploring the DataFrame

The head and the tail of the DataFrame look like this:

Total Number of Attacks

To get the total number of attacks, we just use the DataFrame's sum() method:

df["Attacks"].sum()

Answer: 212

Years with most and least vandalism

To get the years with the most and least vandalism we just use the DataFrame's max() and min() methods:

rows_with_most_attacks = df.loc[df['Attacks'] == df["Attacks"].max()]
rows_with_fewest_attacks = df.loc[df['Attacks'] == df["Attacks"].min()]

The max is clear. One year, 2024, had 7 attacks, more than in any other year.
Max: 7 attacks in 2024
The years with the least number of attacks is slightly more problematic. We code using min() gives us 25 years with only one attack, but there were also years with no attacks at all. We see that by looping through the index of the DataFrame:

years_with_no_attacks = []
for year in range(2000,2100):
    if year not in df.index:
        years_with_no_attacks.append(year)

That gives us a list of 21 years with no attacks: 2000, 2037, 2041, 2043, 2047, 2053, 2058, 2059, 2061, 2062, 2067, 2071, 2073, 2074, 2079, 2082, 2083, 2086, 2089, 2094, 2097. Note that if 2058 were a leap year, there would have been an attack on February 29 of that year, but it isn't.
So, I think the answer is:
Min: 0 attacks in 21 different years

The longest gap between attacks

To figure out the longest gap between two attacks, I went back to the while loop in the setup:

longest_gap = timedelta(days=0)
day = date(2001,1,2)
prev_attack = date(2001,1,1)
lastday = date(2099,12,31)
while day <= lastday:
    m = day.month
    d = day.day
    y = day.year - 2000
    if m*d == y:
        gap = day - prev_attack
        if gap > longest_gap:
            gap_start = prev_attack
            gap_end = day
            longest_gap = gap
        prev_attack = day
    day = day + timedelta(days=1)

print(gap_start, gap_end, longest_gap)

This shows us that the longest gap is 1097 days between 03/19/2057 and 03/20/2060. Those were some safe years for famous landmarks.

Notice any mistakes or ideas for improving the code, let me know.


Related Training: Python

About Webucator

Webucator provides instructor-led training to students throughout the US and Canada. We have trained over 90,000 students from over 16,000 organizations on technologies such as Microsoft ASP.NET, Microsoft Office, Azure, Windows, Java, Adobe, Python, SQL, JavaScript, Angular and much more. Check out our complete course catalog.