Chapter 254
How Many More Palindrome Dates Will You See?
The Riddler for February 7, 2020, the week of the palindromic date 02/02/2020. The Express counts the palindrome dates still to come this century; the Classic counts the distinct values hiding inside a string of ambiguous absolute-value bars.
Riddler Express
Writing dates as MM/DD/YYYY, a palindrome reads the same forwards and backwards (ignoring the slashes); is one. How many more palindromic dates will there be this century?
The Riddler, FiveThirtyEight, February 7, 2020(original post)
Solution
The eight digits of MM/DD/YYYY are , and a palindrome demands , , , . This century the year is , so and . That fixes the day immediately: so every palindrome date this century falls on the nd, and the month is the year’s last two digits reversed, . Running through the years and keeping only those for which is a real month to gives exactly twelve palindrome dates this century: Four of them fall on or before (the two in and , the one in , and itself). That leaves still to come: and then one per decade from to .
The computation
Encode the calendar directly: walk every real date from through , write it as the eight-digit string MM DD YYYY, and count those that read the same reversed and fall after .
from datetime import date, timedelta
d, end, count = date(2020, 1, 1), date(2099, 12, 31), 0
while d <= end:
s = f"{d.month:02d}{d.day:02d}{d.year:04d}"
if s == s[::-1] and d > date(2020, 2, 2):
count += 1
d += timedelta(days=1)
print(count) # 8
The brute-force calendar count is , as boxed.
Riddler Classic
Because absolute-value bars look the same whether they open or close, an expression like is ambiguous: it can mean or . Treating all the bars as indistinguishable, how many different values can have?
The Riddler, FiveThirtyEight, February 7, 2020(original post)
Solution
The expression has ten vertical bars. An interpretation is a way to pair them into opening and closing bars, and because no bar can close before it opens, the legal pairings are exactly the balanced arrangements of five open and five close symbols, counted by the fifth Catalan number . Each pairing yields one arithmetic value, so there are at most values; duplicates can only lower the count.
To turn a pairing into a number, read an opening bar as “ (begin)” and a closing bar as “end .” A signed number sitting just before a group multiplies it, and a signed number sitting just after a finished group is added (it carries its own sign). The two readings of the small example confirm the rule: , and . Evaluating all pairings of the full expression, exactly three pairs of them collide (the repeated values are , and ), so the readings produce only distinct values. The Catalan count is the ceiling, and these three coincidences are all that separate the answer from it.
The computation
Enumerate the balanced bar-pairings, build the expression each one defines by the open/close, multiply/add rules above, evaluate, and count the distinct results.
nums = [-(k + 1) for k in range(9)] # signed numbers between the 10 bars
def matchings(n): # all balanced '('/')' bar labellings
out = []
def rec(i, stack, roles):
if i == n:
if not stack: out.append(roles[:])
return
roles.append('('); rec(i + 1, stack + [i], roles); roles.pop()
if stack:
roles.append(')'); rec(i + 1, stack[:-1], roles); roles.pop()
rec(0, [], [])
return out
def value(roles):
parts, prev = [], None # prev kind: 'open', 'close', 'num'
for b in range(10):
kind = 'open' if roles[b] == '(' else 'close'
if prev in ('num', 'close') and kind == 'open': parts.append('*')
parts.append('abs(' if kind == 'open' else ')')
prev = kind
if b < 9:
if prev == 'close': parts.append('+') # signed number is added
parts.append(str(nums[b])); prev = 'num'
return eval(''.join(parts))
vals = {value(r) for r in matchings(10)}
print(len(matchings(10)), len(vals)) # 42 39
There are pairings but only distinct values, as boxed.