Enigmatic Code

Programming Enigma Puzzles

Enigma 1446: Double entendre

notableFrom 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.

Enigma 1446

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]

Advertisements

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

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: