Enigmatic Code

Programming Enigma Puzzles

Enigma 360: For the time boing

From New Scientist #1509, 22nd May 1986 [link]

“This is my favourite clock,” said Mr Fescu, the Curator of the House of Clocks. “It has a curious mechanism which prevents it from stopping or being started except exactly on the hour. It only chimes on the hour and normally emits a number of bongs equal to the hour it is striking. But if it stops and is restarted at a later hour less than 12 hours on it emits all the chimes it missed between the stopping and starting times, not counting those of the hour at which it is restarted.”

“You mean if it stops at 9:00 and is restarted at 8:00 it emits 70 bongs?”

“Precisely,” said my guide, clutching and enormous key. “And what is more interesting is that certain numbers of bongs are special, in that they occur between one unique pair of stopping and starting times only. If you heard 70 bongs it could only signify that the clock stopped at 9:00 and was restarted at 8:00. Given the time at which the clock has stopped this time, there is only one hour at which I could rewind it to yield a magic number of bongs, and Lo!” he said, hopping from foot to foot and gesticulating at the church clock just visible in the distance, “that hour is arrived.”

So saying he moved the hands to the right time, wound the mechanism up and kicked it, whereupon it emitted an uneven number of bongs and a prime number at that.

What was the time; when had the clock stopped; how many bongs did the clock emit?

[enigma360]

8 responses to “Enigma 360: For the time boing”

1. Jim Randell 2 September 2016 at 7:47 am

This Python 2 code uses the filter_unique() function from the enigma.py library. It runs in 38ms.

```from itertools import product
from enigma import irange, filter_unique, is_prime, printf

# determine the number of bongs
def bongs(stop, start):
b = 0
while stop != start:
b += stop
stop += 1
if stop == 13: stop = 1
return b

# check the example given
assert bongs(9, 8) == 70

# record results (<stop>, <start>, <bongs>)
r = list()
for (stop, start) in product(irange(1, 12), repeat=2):
if stop == start: continue
b = bongs(stop, start)
r.append((stop, start, b))

# find "special"/"magic" bongs
(r, _) = filter_unique(r, (lambda (stop, start, b): b))

# find "special" bongs with a unique stop
(r, _) = filter_unique(r, (lambda (stop, start, b): stop))

# and look for an odd, prime number of bongs
for (stop, start, b) in r:
if b > 2 and is_prime(b):
printf("{stop} -> {start} = {b} bongs")
```

Solution: The time was 8 o’clock. The clock had stopped at 10 o’clock. The clock emitted 61 bongs.

The code uses parameter unpacking in lines 24 and 27, so it won’t run without modification under Python 3.

• Jim Randell 2 September 2016 at 8:23 am

One way we can adapt this program to run under Python 3 [*] with only a minor loss of readability is to define the function unpack(), which takes a function that takes a tuple as an argument and returns a new function that first unpacks the tuple before passing it to the function:

```def unpack(fn):
return lambda args: fn(*args)
```

We can then use lambda functions with named parameters that are wrapped by the unpack() function in the calls to filter_unique().

This code works in Python 2 or Python 3.

```from itertools import product
from enigma import irange, filter_unique, is_prime, printf

# unpack the args of a function
def unpack(fn):
return lambda args: fn(*args)

# determine the number of bongs
def bongs(stop, start):
b = 0
while stop != start:
b += stop
stop += 1
if stop == 13: stop = 1
return b

# check the example given
assert bongs(9, 8) == 70

# record results (<stop>, <start>, <bongs>)
r = list()
for (stop, start) in product(irange(1, 12), repeat=2):
if stop == start: continue
b = bongs(stop, start)
r.append((stop, start, b))

# find "special"/"magic" bongs
(r, _) = filter_unique(r, unpack(lambda stop, start, b: b))

# find "special" bongs with a unique stop
(r, _) = filter_unique(r, unpack(lambda stop, start, b: stop))

# and look for an odd, prime number of bongs
for (stop, start, b) in r:
if b > 2 and is_prime(b):
printf("{stop} -> {start} = {b} bongs")
```

The unpack() function is available in the enigma.py library from version 2016-09-02.

[*] It would perhaps be better to use collections.namedtuple() (which is available in Python 2 and Python 3) to make the list of results. We can then directly interrogate elements of the list by attribute in the lambda expressions.

2. Hugh Casement 2 September 2016 at 11:23 am

I think that’s the only prime number of boings that uniquely identifies the times.
Certain other times give a unique but composite number:
Stopped at 4, started at 11, 49 times. Stopped at 5, started at 4, 74 times.
Stopped at 6, started at 2, 64 times. Stopped at 7, started at 2, 58 times.
Stopped at 1, 2, 4, or 8 and started an hour later.
If the clock stops at 12 there are no numbers unique to that time of stopping.
Otherwise there are several numbers peculiar to each time of stopping.

• Jim Randell 2 September 2016 at 3:34 pm

Looking at the list of 24 “special” bongs I noticed that they occur in pairs such that:

bongs(a, b) + bongs(b, a) = 78 = T(12)

But there are only 4 “special” bongs with a unique stop time:

bongs(5, 4) = 74
bongs(6, 2) = 64
bongs(7, 2) = 58
bongs(10, 8) = 61

And only the final one is odd (or “uneven”) or prime.

• Hugh Casement 2 September 2016 at 5:29 pm

Yes, I see that symmetry now: a reflexion in the a = b diagonal, 78 being the number of boings one would expect it to give out if started 12 hours after stopping.
And equally there are only 4 “specials” with a unique start time (swap a and b, subtract from 78).
I think I’d be tempted to stick a nail in the striking train to silence it!
(Incidentally, boings or bongs count as striking, not chiming)

3. Brian Gladman 2 September 2016 at 9:18 pm
```from collections import defaultdict

# test for primes < 100
p = {2, 3, 5, 7}
is_prime = lambda x: x in p or x > 10 and all(x % n for n in p)

# for a sequence of tuples in <sols>, return those tuples that
# are unique for the values in the tuples at position ix
def unique(sols, ix):
d = defaultdict(list)
for s in sols:
k = s[ix]
d[k].append(s)
return (v[0] for k, v in d.items() if len(v) == 1)

# record the stop and restart times and the number of bongs
sols = []
for stop in range(1, 13):
for restart in range(stop + 1, stop + 12):
bongs = sum((x - 1) % 12 + 1 for x in range(stop, restart))
sols.append((stop, (restart - 1) % 12 + 1, bongs))

# find triples that are unique for their number of bongs
sols = unique(sols, 2)

# now find triples that are unique for their stop times
for stop, restart, bongs in unique(sols, 0):
# and have an odd prime number of bongs
if bongs > 2 and is_prime(bongs):
fs = 'It stopped at {}; it was restarted at {} and struck {} bongs.'
print(fs.format(stop, restart, bongs))
```
4. Hugh Casement 3 September 2016 at 10:00 am

Stopped at time j, restarted at time k:
number of boings = ½k(k – 1) – ½j(j – 1) – 78(j < k)
with the convention that true = -1, false = 0.
I think that works in all cases, including restarting 12 hours later
(even if that appears to be excluded in the puzzle as stated).
You'll recognize the (j – 1)th and (k – 1)th triangular numbers.