Teaser 3231: In his prime
From The Sunday Times, 25th August 2024 [link] [link]
Once, on my grandson’s birthday, I asked him the following five questions:
1. How many days are there in this month?
2. How many Mondays are there in this month?
3. How many “prime” days (i.e. 2nd, 3rd, 5th, …) are there in this month?
4. How many of those prime days are Saturdays?
5. How many letters are there when you spell the month?The total of the five answers was a prime number.
Then I asked him the same questions the next day. No answer was the same as for the day before, but again the total of the five answers was prime.
When I first asked the questions what was the month and day of the week?
As set this puzzle has multiple solutions. However there is a unique answer for the birthday of the grandson (month and day of month).
[teaser3231]













Jim Randell 1:30 am on 25 August 2024 Permalink |
Assuming the grandson answers each of the questions correctly, if the answer to each question must be different from that of the previous day, then the next day must be in a different month to the birthday.
This Python program works backwards, considering the answers for pairs of adjacent months, until it finds the most recent viable solution. (Assuming the system locale is set to find appropriate day and month names).
It runs in 68ms. (Internal runtime is 2.8ms).
from datetime import (date, timedelta) from enigma import (Record, repeat, inc, first, icount, primes, unpack, tuples, printf) # increment of 1 day day1 = timedelta(days=1) # function to check the weekday of a date weekday_is = lambda n: (lambda d, n=n: d.isoweekday() == n) # answer the questions def results(y, m): # days in the month ds = first(repeat(inc(day1), date(y, m, 1)), count=(lambda d, m=m: d.month == m)) # 1. number of days in the month r1 = len(ds) # 2. number of Mondays r2 = icount(ds, weekday_is(1)) # 3. number of prime days pds = list(d for d in ds if d.day in primes) r3 = len(pds) # 4. number of prime Saturdays r4 = icount(pds, weekday_is(6)) # 5. number of letters in the month name r5 = len(ds[0].strftime('%B')) return (r1, r2, r3, r4, r5) # generate result values going back in time def generate(y, m): prev_month = unpack(lambda y, m: (y, m - 1) if m > 1 else (y - 1, 12)) for (y, m) in repeat(prev_month, (y, m)): if y < 1753: break rs = results(y, m) t = sum(rs) yield Record(y=y, m=m, rs=rs, t=t, tprime=(t in primes)) # consider adjacent months for (next-day, birthday) for (d1, d2) in tuples(generate(2024, 8), 2): # both totals are prime and no answers are the same if d1.tprime and d2.tprime and not any(x == y for (x, y) in zip(d1.rs, d2.rs)): # output solution nd = date(d1.y, d1.m, 1) bd = nd - day1 printf("birthday = {bd} ({dow}) {d2.rs}; next day = {nd} {d1.rs}", dow=bd.strftime('%A')) break # only need the most recent solutionSolution: The first time the questions were asked was on a Monday in February.
Actually on Monday, 29th February 2016.
The answers to the questions are: (29, 5, 10, 1, 8) giving a total of 53.
The next day is Tuesday, 1st March 2016.
And the answers to the questions are: (31, 4, 11, 2, 5) also giving a total of 53.
If we go further back the dates: Friday, 29th February 2008 and Saturday, 1st March 2008 also work.
And in fact all viable dates are Monday/Friday 29th February, and Tuesday/Saturday 1st March. So there are two possible answers to the puzzle.
The published solution is: “February, Monday or Friday (apologies for the multiple answers)”.
LikeLike
Jim Randell 10:27 am on 25 August 2024 Permalink |
> [Frits suggests that solutions earlier than the most recent give the same answer]
@Frits: Are you sure?
LikeLike
Jim Randell 7:26 am on 26 August 2024 Permalink |
I interpreted the condition “no answer was the same as for the day before” to mean that each answer given on the second day was different from the answer given to the same question on the previous day (and I think this is the most reasonable interpretation).
However, if we take this condition to mean that there were no values given as answers on the second day that had also been given as answers on the first day, then we do find a unique solution to the puzzle.
Here is my program adapted for this interpretation. (The condition at line 38 is changed).
from datetime import (date, timedelta) from enigma import (Record, repeat, inc, first, icount, primes, unpack, tuples, intersect, printf) # increment of 1 day day1 = timedelta(days=1) # function to check the weekday of a date weekday_is = lambda n: (lambda d, n=n: d.isoweekday() == n) # answer the questions def results(y, m): # days in the month ds = first(repeat(inc(day1), date(y, m, 1)), count=(lambda d, m=m: d.month == m)) # 1. number of days in the month r1 = len(ds) # 2. number of Mondays r2 = icount(ds, weekday_is(1)) # 3. number of prime days pds = list(d for d in ds if d.day in primes) r3 = len(pds) # 4. number of prime Saturdays r4 = icount(pds, weekday_is(6)) # 5. number of letters in the month name r5 = len(ds[0].strftime('%B')) return (r1, r2, r3, r4, r5) # generate result values going back in time def generate(y, m): prev_month = unpack(lambda y, m: (y, m - 1) if m > 1 else (y - 1, 12)) for (y, m) in repeat(prev_month, (y, m)): if y < 1753: break rs = results(y, m) t = sum(rs) yield Record(y=y, m=m, rs=rs, t=t, tprime=(t in primes)) # consider adjacent months for (next-day, birthday) for (d1, d2) in tuples(generate(2024, 8), 2): # both totals are prime and no answers are the same if d1.tprime and d2.tprime and not intersect([d1.rs, d2.rs]): # output solution nd = date(d1.y, d1.m, 1) bd = nd - day1 printf("birthday = {bd} ({dow}) {d2.rs}; next day = {nd} {d1.rs}", dow=bd.strftime('%A'))Note: Apparently this is not the interpretation intended by the setter. They just didn’t realise that there were multiple solutions to the puzzle.
LikeLike
Ruud 5:05 pm on 26 August 2024 Permalink |
That’s an interesting interpretation that indeed leads to a unique solution.
In my code just change
if all(answer1 != answer2 for answer1, answer2 in zip(counts1, counts2)):
into
if set(counts1) & set(counts2) == set():
to get the unique result.
LikeLike
Frits 12:02 pm on 28 August 2024 Permalink |
@Jim, please add a comment to explain the hardcoded 1753 value
LikeLike
Jim Randell 12:09 pm on 28 August 2024 Permalink |
@Frits: See [ https://enigmaticcode.wordpress.com/2021/12/10/enigma-911-unlucky-for-some/#comment-11516 ]
LikeLike
Ruud 1:13 pm on 25 August 2024 Permalink |
It is obvious that the birthday must be at the end of a month to make the answer to the month name different. So we can just search by year/month. As the grandchild still has a living grandfather, I assume that the grandchild must be born after 1939. So I just check for all years between 1940 and 2024 and all months.
It turns out that there are 6 possible solutions in this timeframe. And the month is unique, but the day of the week is ambiguous (there are two possibilities).
import calendar def is_prime(n): if n < 2: return False if n == 2: return True if not n & 1: return False for x in range(3, int(n**0.5) + 1, 2): if n % x == 0: return False return True def counts(year, month): number_of_days = calendar.monthrange(year, month)[1] number_of_mondays = 0 number_of_prime_days = 0 number_of_prime_saturdays = 0 weekday = calendar.weekday(year, month, 1) for day in range(1, number_of_days + 1): number_of_mondays += weekday == 0 number_of_prime_days += is_prime(day) number_of_prime_saturdays += weekday == 5 and is_prime(day) weekday = (weekday + 1) % 7 number_of_letters = len(calendar.month_name[month]) return [number_of_days, number_of_mondays, number_of_prime_days, number_of_prime_saturdays, number_of_letters] for year in range(2024, 1940, -1): for month in range(1, 13): counts1 = counts(year, month) if is_prime(sum(counts1)): next_month = (month % 12) + 1 next_year = year + (month == 12) counts2 = counts(next_year, next_month) if is_prime(sum(counts2)): if all(answer1 != answer2 for answer1, answer2 in zip(counts1, counts2)): day = calendar.monthrange(year, month)[1] print(f"{calendar.month_name[month]:10} {calendar.day_name[calendar.weekday(year,month,day)]} ({year:4}-{month:02}-{day:02})")LikeLike