Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
"""
https://fivethirtyeight.com/features/when-will-the-arithmetic-anarchists-attack/
From Eric Veneto, mathematical madmen are on the loose:
The year is 2000, and an arithmetical anarchist group has an idea. For the next
100 years, it will vandalize a famous landmark whenever the year (in two-digit
form, for example this year is “18”) is the product of the month and date (i.e.
month × date = year, in the MM/DD/YY format).
A few questions about the lawless ensuing century: How many attacks will happen
between the beginning of 2001 and the end of 2099? What year will see the most
vandalism? The least? What will be the longest gap between attacks?
Run this script with:
```
$ python3 arithmetical-anarchist.py
```
Expected Output:
How many attacks will happen between the beginning of 2001 and the end of 2099?
212
What year will see the most vandalism?
2024 had 7 attacks.
What year will see the least vandalism?
2037,2041,2043,2047,2053,2058,2059,2061,2062,2067,2071,2073,2074,2079,2082,2083,2086,2089,2094,2097 had zero attacks.
2001,2013,2017,2019,2023,2029,2031,2034,2038,2039,2046,2049,2051,2057,2065,2068,2069,2076,2085,2087,2091,2092,2093,2095,2098 had one attack.
What will be the longest gap between attacks?
1096 days between 3/19/2057 and 3/20/2060.
"""
# Setup our date range.
years = range(1,100)
months = range(1,13)
days_in_months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
# Keep track of all attack events and longest gap between attacks.
events = {}
gap_start = "1/1/2001"
gap_end = "1/1/2001"
gap_len = 0
days_since_attack = 0
# Convert m d y integers to a date string.
def format_attack_date(d, m, y):
return "{:d}/{:d}/{:s}".format(m, d, format_year(y))
# Convert a 1 or 2 digit year to a 4 digit year string.
def format_year(y):
if y < 10:
return "200" + str(y)
else:
return "20" + str(y)
# Brute force all days in all years in all months
for y in years:
for m in months:
num_days = days_in_months[m-1] # Watch out for off-by-one errors
# Look out for leap years
if m == 2 and (y % 4) == 0:
num_days = 29
for d in range(1, num_days+1):
if d * m == y:
# There's an attack on this day
# See if this beats the record for longest gap between attacks
if days_since_attack > gap_len:
gap_start = last_attack
gap_end = format_attack_date(d, m, y)
gap_len = days_since_attack
# Keep track of the last attack (which is now this one)
last_attack = format_attack_date(d, m, y)
# Attack happened today, so reset days_since_attack
days_since_attack = 0
# Store this attack in our events table
if y not in events:
events[y] = {}
if m not in events[y]:
events[y][m] = []
events[y][m].append(d)
else:
# No attack today
days_since_attack += 1
# Calculate the best and worst years
worst_years = []
worst_count = 0
best_years = []
one_years = []
event_total = 0
for y in years:
events_this_year = 0
if y in events.keys():
# At least one attack happened this year
for m in events[y].keys():
# Add this month's attacks to the year total
events_this_year += len(events[y][m])
else:
# No attacks happened this year
best_years.append(format_year(y))
# Does this year tie with the current worst year?
if events_this_year == worst_count:
worst_years.append(format_year(y))
# Is this year the new worst year?
if events_this_year > worst_count:
worst_count = events_this_year
worst_years = [format_year(y)]
# If there was only one attack this year, keep track of it
if events_this_year == 1:
one_years.append(format_year(y))
# Add up all the events this year
event_total += events_this_year
print("How many attacks will happen between the beginning of 2001 and the end of 2099?")
print(event_total)
print()
print("What year will see the most vandalism?")
print("{:s} had {:d} attacks.".format(",".join(worst_years), worst_count) )
print()
# This question was a bit ambiguous to me. There are years where no attacks take place,
# so are those the years with the least? Or are we only looking for years where an
# attack took place? I fudged and lists all years with zero or one attack.
print("What year will see the least vandalism?")
print("{:s} had zero attacks.".format(",".join(best_years)) )
print("{:s} had one attack.".format(",".join(one_years)) )
print()
print("What will be the longest gap between attacks?")
print("{:d} days between {:s} and {:s}.".format(gap_len, gap_start, gap_end) )
print()
@tmsgh

This comment has been minimized.

Show comment
Hide comment
@tmsgh

tmsgh Apr 14, 2018

Here is a shorter version:

# https://fivethirtyeight.com/features/when-will-the-arithmetic-anarchists-attack/
from statistics import mode;t=[31,28,31,30,31,30,31,31,30,31,30,31];a=[[d,m,y] for y in range(1,100) for m in range(1,13) for d in range(1,32) if d<=(t[m-1]+(not y%4 and m==2))];b=[n for n in a if n[2]==n[0]*n[1]];c=[n[2] for n in b];d=[n[0] for n in enumerate(a) if n[1] in b];print(len(b)," attacks; most attacks in",mode(c),"; safest years=",[n for n in range(1,100) if n not in c],"; longest gap = ",max([d[n+1]-d[n] for n in range(len(d)-1)])-1," days")

tmsgh commented Apr 14, 2018

Here is a shorter version:

# https://fivethirtyeight.com/features/when-will-the-arithmetic-anarchists-attack/
from statistics import mode;t=[31,28,31,30,31,30,31,31,30,31,30,31];a=[[d,m,y] for y in range(1,100) for m in range(1,13) for d in range(1,32) if d<=(t[m-1]+(not y%4 and m==2))];b=[n for n in a if n[2]==n[0]*n[1]];c=[n[2] for n in b];d=[n[0] for n in enumerate(a) if n[1] in b];print(len(b)," attacks; most attacks in",mode(c),"; safest years=",[n for n in range(1,100) if n not in c],"; longest gap = ",max([d[n+1]-d[n] for n in range(len(d)-1)])-1," days")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment