# Enigmatic Code

Programming Enigma Puzzles

## Enigma 1446: Double entendre

From New Scientist #2607, 9th June 2007

For each answer in these cross-figures, two clues have been given; one applies to each figure, but in no particular order.

Across:

1.  The sum of the digits of 1D. (2); The product of the digits of 1D. (2)
3.  A multiple of 3A from the other figure. (2); A divisor of 3A from the other figure. (2)
4.  A multiple of 1D but not equal to 1D. (3); Not a multiple of 1D. (3)

Down:

1.  Equal to a prime × 3D. (3); Not equal to a prime × 3D. (3)
2.  Differs from the square of 3A by 1. (3); Differs from the square of 3D by 1. (3)
3.  The sum of the digits of 4A. (2); The product of the digits of 4A. (2)

Find the completed grids (in either order).

[enigma1446]

### One response to “Enigma 1446: Double entendre”

1. Jim Randell 29 March 2013 at 9:07 am

I found this quite a fun puzzle to solve programatically.

This Python code makes heavy use of Python’s generator construct (yield ...) and also uses the yield from ... generator delegation construct, introduced in Python 3.3, so it won’t work in Python 2.7 (although in this case you could just replace the yield from expression with for x in expression: yield x and it would work). It runs in 2.3s.

from itertools import product, count
from enigma import irange, chunk, split, nconcat, multiply, Primes, printf

# let's assign positions in the grid thus:
#  0 1 2
#  3 4 5
#  6 7 8
# so the solutions are:
A1 = (0, 1)
A3 = (4, 5)
A4 = (6, 7, 8)
D1 = (0, 3, 6)
D2 = (2, 5, 8)
D3 = (4, 7)

# prime numbers
primes = Primes(1000)

# let's represent possible values in the grid
v = [
# individual digits
[0], [1], [2], [3], [4], [5], [6], [7], [8], [9],
# possible non-initial digits
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
# possible initial digits
[1, 2, 3, 4, 5, 6, 7, 8, 9],
]

# indices for initial and non-initial possibilities
(I0, I1) = (10, 11)

# the starting grid
def grid():
return [ I1, I0, I1, I0, I1, I0, I1, I0, I0 ]

# generate possible solutions for a clue (as a list of digits)
def generate(g, ps):
yield from product(*(v[g[i]] for i in ps))

# update a grid with a solution
def update(g, ps, ds):
if len(ds) != len(ps): return None
g2 = list(g)
for (p, d) in zip(ps, ds):
if d not in v[g2[p]]: return None
g2[p] = d
return g2

# can we fit the solutions into the grid?
def fit(g, *ss):
# are we done?
if len(ss) == 0: return g
# try to fit the first solution
(n, ps) = ss[:2]
ds = list(split(n, int))
g2 = update(g, ps, ds)
return None if g2 is None else fit(g2, *ss[2:])

# 1Aa: "The sum of the digits of 1D" (2)
def A1a(g):
for ds in generate(g, D1):
d1 = nconcat(ds)
s = sum(ds)
g2 = fit(g, s, A1, d1, D1)
if g2: yield g2

# 1Ab: "The product of the digits of 1D" (2)
def A1b(g):
for ds in generate(g, D1):
d1 = nconcat(ds)
p = multiply(ds)
g2 = fit(g, p, A1, d1, D1)
if g2: yield g2

# 4Aa: "A multiple of 1D, but not equal to 1D" (3)
def A4a(g):
for ds in generate(g, D1):
d1 = nconcat(ds)
for m in count(2):
n = m * d1
if n > 999: break
g2 = fit(g, n, A4, d1, D1)
if g2: yield g2

# 4Ab: "Not a multiple of 1D" (3)
def A4b(g):
for ds in generate(g, D1):
d1 = nconcat(ds)
g2 = fit(g, d1, D1)
for ds in generate(g2, A4):
a4 = nconcat(ds)
if a4 % d1 > 0: yield fit(g2, a4, A4)

# 1Da: "Equal to a prime x 3D" (3)
def D1a(g):
for ds in generate(g, D3):
d3 = nconcat(ds)
for p in primes:
n = p * d3
if n > 999: break
g2 = fit(g, n, D1, d3, D3)
if g2: yield g2

# 1Db: "Not equal to a prime x 3D" (3)
def D1b(g):
for ds in generate(g, D3):
d3 = nconcat(ds)
g2 = fit(g, d3, D3)
for ds in generate(g2, D1):
d1 = nconcat(ds)
(d, r) = divmod(d1, d3)
if not(r == 0 and d in primes): yield fit(g2, d1, D1)

# 2Da: "Differs from the square of 3A by 1" (3)
def D2a(g):
for ds in generate(g, A3):
a3 = nconcat(ds)
s = a3 * a3
for d in (-1, 1):
g2 = fit(g, s + d, D2, a3, A3)
if g2: yield g2

# 2Db: "Differs from the square of 3D by 1" (3)
def D2b(g):
for ds in generate(g, D3):
d3 = nconcat(ds)
s = d3 * d3
for d in (-1, 1):
g2 = fit(g, s + d, D2, d3, D3)
if g2: yield g2

# 3Da: "The sum of the digits of 4A" (2)
def D3a(g):
for ds in generate(g, A4):
a4 = nconcat(ds)
s = sum(ds)
g2 = fit(g, s, D3, a4, A4)
if g2: yield g2

# 3Db: "The product of the digits of 4A" (2)
def D3b(g):
for ds in generate(g, A4):
a4 = nconcat(ds)
p = multiply(ds)
g2 = fit(g, p, D3, a4, A4)
if g2: yield g2

# solve a set of clues
def solve(g, cs):
# are we done?
if len(cs) == 0:
yield g
return
# solve the first clue
for g2 in cs[0](g):
# and the rest (or [[ for x in ...: yield x ]] in Python 2.7)
if g2 is not None: yield from solve(g2, cs[1:])

# for the moment we'll ignore the 3A clues, until we've got candidate grids
# the other five clues (in a sensible order)
cs = ((D2a, D2b), (D3a, D3b), (D1a, D1b), (A1a, A1b), (A4a, A4b))

# choose an assignment of clues to the first grid
for s in product([0], [0, 1], [0, 1], [0, 1], [0, 1]):
# find possible first grids for this assignment
for g1 in solve(grid(), list(c[i] for (c, i) in zip(cs, s))):
# and for each of those grids...
# find possible second grids for this assignment
for g2 in solve(grid(), list(c[i ^ 1] for (c, i) in zip(cs, s))):
# extract the 3D answers from each grid
(a, b) = sorted(nconcat(g[i] for i in D3) for g in (g1, g2))
# and one has to be a multiple of the other
if b % a > 0: continue
# output the grids, and the clues used in the first grid
print(g1, g2, list(c[i].__name__ for (c, i) in zip(cs, s)))

Solution: The two completed grids are:

5 0 7 | 1 3 1
5 2 8 | 7 1 4
2 4 3 | 5 2 5

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