# Enigmatic Code

Programming Enigma Puzzles

## Enigma 1455: Deck calendar

From New Scientist #2616, 11th August 2007

A standard pack of playing cards has 52 cards and (counting 1 point for an ace, 2 for a 2, and so on up to 13 for a king) it is worth a total of 364 points. So, on Sunday 1 January one year I took a pack and noted that the number of days yet to come in the year equalled the number of points in the pack.

Then the following Sunday and every Sunday for the rest of the year I discarded one card and counted the points left in the remaining pack. So, finally, after doing this on Sunday 31 December, there were no points left and no days left, and once again the number of days yet to come equalled the number of points left in the pack.

In fact the point count and day count were equal every fortnight from January 1 (and on some other occasions). On all the remaining Sundays the point count was either a palindrome or a perfect square or a perfect cube.

The week after discarding the king of clubs, I discarded the king of spades, and the week after discarding the king of diamonds, I discarded the king of hearts.

On which four dates did I discard a 6?

[enigma1455]

### 2 responses to “Enigma 1455: Deck calendar”

1. Jim Randell 4 March 2013 at 8:38 am

This Python program runs in 1.5s. It uses the `yield from ...` construct, so requires at least Python 3.3. I think it could be made to run quicker by folding the requirement for the pairs of kings to be discarded in adjacent weeks into the recursive solver, but it would make the code a little messier.

```from collections import defaultdict
from datetime import date, timedelta
from enigma import irange, is_square, is_cube, printf

def is_palindrome(n):
s = str(n)
return s == s[::-1]

# special numbers are either palindromes, squares, cubes
special = set(n for n in irange(1, 364) if is_palindrome(n) or is_square(n) or is_cube(n))

def check(s):
# check the kings
k = tuple(i for (i, v) in enumerate(s) if v == 13)
if not(k[0] + 1 == k[1] and k[2] + 1 == k[3]): return
# return the week numbers of the sixes
return tuple(i for (i, v) in enumerate(s) if v == 6)

def solve(s, r):
if r == 0:
yield s
return
t = 364 - sum(s)
if len(s) % 2 == 0:
# on even length point count is special
for n in irange(1, 13):
if s.count(n) == 4: continue
if t - n in special:
yield from solve(s + [n], r - 7)
else:
# on odd length point count and days remaining is the same
# (or equivalently: n = 14 - s[-1], as pairwise the numbers must sum 14)
n = t - r + 7
if s.count(n) < 4:
yield from solve(s + [n], r - 7)

# find possibile weeks for the sixes
r = defaultdict(int)
for s in solve([], 364):
x = check(s)
if x is None: continue
r[x] += 1
printf("[sixes={x} cards={s}]")

# output the results
for x, n in r.items():
# turn the week numbers into dates (2006 works as a template year)
d1 = date(2006, 1, 1)
printf("{d} [{n} solutions]", d=' / '.join((d1 + timedelta(i * 7)).strftime("%d %b") for i in x))
```

Solution: The sixes were discarded on 2nd April, 7th May, 11th June and 19th November.

There are sixteen possible sequences of discards which give the solution. The program above finds them all.

2. Brian Gladman 4 March 2013 at 12:05 pm

Here is my solution which is also recursive but steps forward two weeks at a time:

```from datetime import date, timedelta

# check for a number that is a palindrome
def is_pal(x):
s = str(x)
return s == s[::-1]

# check for a number that is a perfect root
def is_root(x, n):
return round(x ** (1.0 / n)) ** n == x

# numbers that are palindromes, square roots or cube roots
special = set(x for x in range(365)
if is_pal(x) or is_root(x, 2) or is_root(x, 3))

# step forward a fortnight at a time
def step(cards, days_left, card_seq):
if days_left:
# place the next two cards - any available cards unless we
# placed a king last time and hence place one again now
if not card_seq or card_seq[-1] != 13:
lo, hi = 1, 13
else:
lo, hi = 13, 14
# try each possible pair of cards for this fortnight
for c in range(lo, hi):
# if the two cards are available
if cards[c] and cards[14 - c]:
# if the card is a 7 then the intermediate card
# count and days left will match, otherwsie the
# intermediate days left has to be 'special'
if c == 7 or days_left - c in special:
# take the two cards out of the pack
cards[c] -= 1
cards[14 - c] -= 1
# and move onto the next fortnight
step(cards, days_left - 14, card_seq + [c, 14 - c])
cards[14 - c] += 1
cards[c] += 1
else:
# we have a solution - locate the four 6 cards
ix, s = -1, []
for i in range(4):
ix = card_seq.index(6, ix + 1)
# get the date the card was discarded as a string
d = date(2007, 1, 1) + timedelta(days = 7 * ix)
# compile the four dates
s += [d.strftime('%d %B')]
# output the solution
print(', '.join(s), card_seq)

# set a full pack of cards and 364 days left
cards = {x:4 for x in range(1, 14)}
step(cards, 364, [])
```

This site uses Akismet to reduce spam. Learn how your comment data is processed.