Tagged: by: Peter G Chamberlain Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 10:09 am on 26 March 2024 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2674: Roll model 

    From The Sunday Times, 22nd December 2013 [link] [link]

    My new board game has squares numbered from 1 to 100 and has two unusual dice. The first die is 10-sided with numbers from 1 to 10, and the second is 4-sided with a prime number on each side. A move consists of throwing the two dice and then choosing either one of the numbers or their sum and moving that number of squares in either direction. I found myself on one square and realised that there were just two squares which it would be impossible for me to reach with my next move. Both of those squares had prime numbers that did not appear on the dice.

    (a) Which particular square was I on?
    (b) What are the four numbers on the second die?

    [teaser2674]

     
    • Jim Randell's avatar

      Jim Randell 10:10 am on 26 March 2024 Permalink | Reply

      There are (10 + 4 + 10×4) = 54 possible scores, and we can move in either direction.

      So, when we start on a square the reachable squares spread out from it in both directions. If we have 56 consecutive numbers, and we can make 54 of them using the dice, this would be the maximum possible. And so the largest possible square we can be on is 57 and the smallest possible is 44. And we can’t afford more than 6 non-usable scores.

      We cannot afford more than 1 gap in the scores from 1 to 43 (as any gaps will be mirrored on the other side), or more than 2 gaps overall (if they are close to the end, the gaps could be on the same side – although this seems improbable if they are both primes).

      This Python program considers possible sets of primes on the second die, and let looks for an appropriate starting square. (We could just consider all possible combinations of primes, but it is a bit faster check we don’t introduce too many gaps or overlaps as we build up the sets).

      It runs in 58ms. (Internal runtime is 1.3ms).

      Run: [ @replit ]

      from enigma import (irange, primes, printf)
      
      # squares on the board
      squares = set(irange(1, 100))
      
      # die 1
      d1 = list(irange(1, 10))
      
      # fill out die 2
      def die2(ns, d1, ds, d2=list()):
        k = len(d2)
        if k == 4:
          yield (d2, ds)
        else:
          # choose the next number on die 2
          for (i, n) in enumerate(ns):
            # update the set of deltas
            ds_ = ds.union([n], (n + x for x in d1))
            m = 11 * k + 10
            # check for overlaps and gaps
            if len(ds_) < m + 5: continue
            if len(set(irange(1, m)).difference(ds_)) > (1 if k < 3 else 2): continue
            # fill out the remaining faces
            yield from die2(ns[i:], d1, ds_, d2 + [n])
      
      # die 2 has 4 [different] primes
      ps = list(primes.between(5, 50))
      
      # consider possible die 2 and deltas
      for (d2, ds) in die2(ps, d1, set(d1)):
        n = len(ds)
        # consider starting on square i
        for i in irange(98 - n, 5 + n):
          # find unreachable squares
          missing = squares.difference((i - x for x in ds), (i + x for x in ds), [i])
          # there are exactly 2 primes missing, that are not on d2
          if len(missing) != 2: continue
          if any(x in d2 or x not in primes for x in missing): continue
          # output solution
          printf("die2={d2}; square={i}; missing={missing}", missing=sorted(missing))
      

      Solution: (a) You were on square 51; (b) The numbers on the second die are: 11, 23, 31, 41.

      And the squares that cannot be reached are 29 and 73.


      Manually we can adopt a “greedy” strategy to maximise the reach of the dice while minimising the gaps:

      Die 10 will cover deltas 1..10 on either side, and if 11 is not on die 2, then both deltas of 11 and 12 are unreachable, which would make at least 4 unreachable squares (2 on each site). So 11 must be on die 2 and we can cover deltas 1..21.

      The next missing delta is now 22 (which is not prime), so we either leave 22 as unreachable, or we put a prime less than 22 on die 2.

      If we leave 22 as unreachable, and put 23 on die 2 then we can cover deltas 1..33, our unreachable values are at ±22, and we can’t have any more gaps.

      The next missing delta is 34, which is not a prime, so the largest prime we can add to die 2 is 31, which means we can cover deltas 1..41 (except 22).

      The next missing delta is 42, so the largest prime we can add is 41, and so with die 2 = (11, 23, 31, 41) we can cover deltas 1..51 (except 22).

      Starting on squares 49..52 we have the following unreachable squares:

      49 → 27, 71 [not both prime]
      50 → 28, 72 [not both prime]
      51 → 29, 73 [both prime, not on die 2 ⇒ solution]
      52 → 30, 74 [not both prime]

      So we have found a single candidate solution, but we can continue and confirm this is the only candidate:

      If we choose to cover 22, by placing the largest possible prime (i.e. 19) on die 2, then we can cover deltas 1..29.

      The next missing delta is now 30, we could choose to leave ±30 unreachable, and place 31 on die 2, so we can cover 1..41 (except 30).

      And then we place 41 on die 2, and we can cover 1..51 (except 30).

      This gives the following unreachable squares with die 2 = (11, 19, 31, 41):

      49 → 19, 79 [both prime, but 19 on die 2]
      50 → 20, 80 [not both prime]
      51 → 21, 81 [not both prime]
      52 → 22, 82 [not both prime]

      This gives no solutions.

      So we need to cover 30, so we place 29 on die 2, we can then cover deltas 1..39.

      We can leave 40 as unreachable and place 41 on die 2, and we cover 1..51 (except 40).

      This gives the following unreachable squares with die 2 = (11, 19, 29, 41):

      49 → 9, 89 [not both prime]
      50 → 10, 90 [not both prime]
      51 → 11, 91 [not both prime]
      52 → 12, 92 [not both prime]

      This gives no solutions.

      So we need to cover 40, so we place 37 on die 2, and we cover 1..47, and this is not enough to leave just 2 unreachable squares.

      And we have found no further candidate solutions.

      Like

      • Hugo's avatar

        Hugo 11:39 am on 26 March 2024 Permalink | Reply

        A ten-sided die is not very practical, and a tetrahedron doesn’t roll.
        I suggest using an icosahedron and an octahedron, with each number occurring twice
        (for example, on opposite faces).

        Like

        • Jim Randell's avatar

          Jim Randell 12:23 pm on 26 March 2024 Permalink | Reply

          @Hugh: I think I have seen 10 sided dice based on a pentagonal trapezohedron [@wikipedia]. You could also roll a long dodecahedral prism. A square prism might not be so good though.

          Like

          • Lise Andreasen's avatar

            Lise Andreasen 4:34 pm on 4 April 2024 Permalink | Reply

            There are standard 4 and 10 sided easily available, among other things for paper RPG.

            Like

    • Frits's avatar

      Frits 10:47 pm on 26 March 2024 Permalink | Reply

       
      # primes up to 100
      P = [3, 5, 7]
      P = [2] + P + [x for x in range(11, 100, 2) if all(x % p for p in P)]
      
      # this program tries to follow Jim's manual solution.
      
      # for delta's 1 - 43 we can afford to miss one delta
      
      # add a new prime allowing only 1 gap for the first 43 delta's
      def solve(d, ds=[], skip=0):
        if len(ds) == 4:
          # check number of possible reachable squares
          if 2 *(ds[-1] + 10 - (skip > 0)) > 96:
            yield (ds, skip)
        else:
          # try to make delta <d> or ...
          if d in P:
            yield from solve(d + 11, ds + [d], skip)    
          else:
            # largest prime we can add to die 2
            lp = [p for p in P if p < d and p != skip][-1]
            yield from solve(lp + 11, ds + [lp], skip)    
          # ... leave delta <d> as unreachable
          if not skip:
            yield from solve(d + 1, ds, d)  
      
      # deltas 1-10 will be covered by the first die
      for d2, skip in solve(11):
        # we can cover deltas 1..r except possible <skip>
        r = d2[-1] + 10
        
        # one delta must have been skipped, otherwise the number of possible
        # reachable squares is too low
        
        # consider starting on square <pos>
        for pos in range(100 - r, r + 2) :
          # non-reachables
          nr = [pos - skip, pos + skip] 
          if any(x for x in nr if x not in P or x in d2): continue
          print(f"answer: Peter was on square {pos}, "
                f"the four numbers on the second die are: {d2}")    
      

      Like

  • Unknown's avatar

    Jim Randell 8:57 am on 5 March 2024 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2649: Right to left 

    From The Sunday Times, 30th June 2013 [link] [link]

    I have given each letter of the alphabet a different value from 0 to 25, so some letters represent a single digit and some represent two digits. Therefore, for example, a three-letter word could represent a number of three, four, five or six digits. In fact the word CORRECT represents a nine-figure number. It turns out that:

    TO
    REFLECT
    RIGHTTOLEFT

    are three even palindromes.

    What is the CORRECT number?

    [teaser2649]

     
    • Jim Randell's avatar

      Jim Randell 8:58 am on 5 March 2024 Permalink | Reply

      See also: Teaser 2859.

      We can use the [[ SubstitutedExpression ]] solver to solve this puzzle.

      Encoding the conditions directly gives us a run file that executes is 1.4s, but we can speed thing up a bit by checking the starts and ends of partial palindromes to make sure they match. By considering (R, T), (RE, CT) and (RI, FT) we bring the runtime down to 95ms (and the internal runtime of the generated program down to 18ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # use base 26 for values 0-25
      --base=26
      
      --invalid="0,CRT" # no leading zeros in words
      --invalid="0|10|20,OT" # no trailing zeros in palindromes
      --invalid="1|3|5|7|9|11|13|15|17|19|21|23|25,OT" # O and T must end in an even digit
      --invalid="1|3|5|7|9|11-19,RT" # R and T must start with an even digit
      
      # check digits form palindromic numbers
      --code="pal = lambda ds: is_palindrome(join(ds))"
      "pal([T, O])"
      "pal([R, E, F, L, E, C, T])"
      "pal([R, I, G, H, T, T, O, L, E, F, T])"
      
      # check digits have n characters
      --code="length = lambda ds: len(join(ds))"
      "length([C, O, R, R, E, C, T]) == 9"
      
      # [optional] speed things up by checking for partial palindromes
      --code="check = lambda xs, ys: zip_eq(join(xs), reverse(join(ys)))"
      "check([R], [T])"
      "check([R, E], [C, T])"
      "check([R, I], [F, T])"
      
      --answer="(C, O, R, R, E, C, T)"
      --template=""
      

      Solution: CORRECT = 124421124.

      We get the following assignments:

      C=1, E=21, F=12, G=11, I=22, O=2, R=4, T=24
      C=1, E=21, F=12, G=11, I=22, O=2, R=4, T=24
      C=1, E=21, F=12, G=11, I=22, O=2, R=4, T=24

      which gives CORRECT (= 1:2:4:4:21:1:24).

      And one of:

      H=20, L=0
      H=23, L=3
      H=25, L=5

      Which give the palindromes:

      TO = 24:2
      REFLECT = 4:21:12:x:21:1:24
      RIGHTTOLEFT = 4:22:11:2x:24:24:2:x:21:12:24
      where x = 0, 3, 5

      Liked by 1 person

    • Frits's avatar

      Frits 6:27 am on 6 March 2024 Permalink | Reply

      Reusing part of Jim’s code.

       
      n2d     = lambda n: [n] if n < 10 else n2d(n // 10) + [n % 10]
      jn      = lambda s, fn = lambda x: x: [fn(y) for x in s for y in n2d(x)]
      is_pal  = lambda *s: (j := jn(s)) == j[::-1]
      check   = lambda xs, ys: all(x == y for x, y in zip(jn(xs), jn(ys)[::-1]))
      
      palins = ["TO", "REFLECT", "RIGHTTOLEFT"]
      letters = {y for x in palins for y in x}
      B = 26
      
      # invalid digit / symbol assignments
      d2i = dict()
      for d in range(B):
        vs = set()
        if d == 0: vs.update('CORT')     # no leading zeros in words
        if (d % 10) in {10, 20}: 
          vs.update('OT')                # no trailing zeros in palindromes
        if d % 2: vs.update('OT')        # O and T must end in an even digit
        if (int(str(d)[::-1])) % 2: 
          vs.update('RT')                # R and T must start with an even digit
      
        d2i[d] = vs 
      
      # valid digits
      d2v = {lt: {k for k, vs in d2i.items() if lt not in vs} for lt in letters}
      
      if 1:
        print("domain:")
        for k, vs in d2v.items():
          print(f"{k}: {','.join(str(v) for v in vs)}")
      
      # return a valid loop structure string, indent after every "for" statement
      def indent(s):
        res, ind = "", 0
        for ln in s:
          res += " " * ind + ln + "\n"
          if len(ln) > 2 and ln[:3] == "for":
            ind += 2
        return res   
      
      # generate expression for unused letters in palindrome <p>     
      def add_palin_expressions(p, extras=[], exprs=[], done=set()):
        s, i, part = p, 0, dict()
        
        # loop over all letters in <s>
        while s:
          if (lt := s[0]) not in done:
            exp = f"for {lt} in {d2v[lt]}.difference([{','.join(done)}]):"
            exprs.append(exp.replace(", ", ","))
          
          done.add(lt)
          # fill left/right parts  
          part[i % 2] = part.get(i % 2, []) + [lt]
          i += 1
          if i > 1: # we have more than one letter
            if len(s) > 1:  # we'll use is_pal for the last one
              exprs.append(f"if not check([{', '.join(part[0])}], [{', '.join(part[1][::-1])}]): continue")
            # add extra condition if all variables are present  
            for extra in extras : 
              if len(done | extra[0]) == len(done):
                exprs.append(f"if not ({extra[1]}): continue")
                extras.remove(extra) 
          # remove precessed letter and start from the other side        
          s = s[1:][::-1]
      
        exprs.append(f"# final palindrome check for {p}")  
        exprs.append(f"if not is_pal({', '.join(p)}): continue")  
        
        return extras, exprs, done
      
      # extra conditions besides palindromes  
      conds = [(set("CORRECT"), "len(jn([C, O, R, R, E, C, T])) == 9")]
      es, dn = [], set()
      # main loop
      for pal in palins:
        es.append(f"# -----  process palindrome {pal}  -----")
        conds, es, dn = add_palin_expressions(pal, conds, es, dn)
        es.append(" ")
      
      # add final print statement for the answer
      es.append("print('answer: CORRECT =', ''.join(jn([C, O, R, R, E, C, T], fn=str)), end =', ')")
      ltrs = "{" + '=}, {'.join(letters) + "=}"
      es.append(f"print(f'{ltrs}')")
      
      exprs = indent(es)
      
      print(exprs)    
      exec(exprs)   
      

      Like

  • Unknown's avatar

    Jim Randell 11:30 am on 8 February 2024 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2659: Two by two 

    From The Sunday Times, 8th September 2013 [link] [link]

    I have given each letter of the alphabet a different value from 0 to 25, so some letters represent a single digit and some represent two digits.

    Therefore, for example, a three-letter word could represent a number of three, four, five or six digits. With my values it turns out that:

    TWO × TWO = FOUR

    Another “obvious” fact that I can tell you is that every digit occurring in TWO is a prime!

    What is the number FOUR?

    Interestingly, one of the earliest published alphametic puzzles was:

    TWO × TWO = THREE

    (using more usual alphametic rules), which appeared almost 100 years ago in the July 1924 issue of Strand Magazine.

    [teaser2659]

     
    • Jim Randell's avatar

      Jim Randell 11:31 am on 8 February 2024 Permalink | Reply

      Here is a solution using the [[ SubstitutedExpression ]] solver from the enigma.py library (although the method of constructing alphametic words is different from the usual alphametic rules).

      It runs in 194ms. (Internal runtime of the generated program is 78ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --base=26
      
      --code="nconc = lambda *ss: int(concat(ss))"
      --macro="@TWO = nconc(T, W, O)"
      --macro="@FOUR = nconc(F, O, U, R)"
      
      "sq(@TWO) == @FOUR"
      
      --answer="@FOUR"
      
      # T, W, O are chosen from: 2, 3, 5, 7, 22, 23, 25
      --invalid="0|1|4|6|8-21|24,TWO"
      
      # [optional] speeds things up a bit
      # FOUR is a square ...
      "is_square(@FOUR)"
      # ... so R must be the final digits of a square
      --invalid="2-3|7-8|10-15|17-20|22-23,R"
      

      Solution: FOUR = 105625.

      We have:

      TWO = 3:2:5 = 325
      FOUR = 10:5:6:25 = 105625 = 325^2


      And the solution to the TWO × TWO = THREE puzzle is:

      % python3 enigma.py SubstitutedExpression "TWO * TWO = THREE"
      (TWO * TWO = THREE)
      (138 * 138 = 19044) / E=4 H=9 O=8 R=0 T=1 W=3
      [1 solution]
      

      Like

    • GeoffR's avatar

      GeoffR 4:04 pm on 8 February 2024 Permalink | Reply

      from enigma import all_different
      from math import isqrt
      
      def is_sq(x):
         return isqrt(x) ** 2 == x
      
      tup_TWO = (2, 3, 5, 7, 22, 23, 25)
       
      for F in range(1, 26):
        for O in tup_TWO:
          if O == F:continue
          for U in range(26):
            if U in (F, O):continue
            for R in (4, 9, 25): # max value of R = 25
              if R in (U, O, F):continue
              FOUR = int(str(F) + str(O) + str(U) + str(R))
              if not is_sq(FOUR):continue
              for T in tup_TWO:
                for W in tup_TWO:
                  if not all_different(T, W, O, F, U, R):continue
                  TWO = int(str(T) + str(W) + str(O))
                  if TWO * TWO == FOUR:
                    print(f"TWO = {TWO}, FOUR = {FOUR}.")
                    
      # TWO = 325, FOUR = 105625.
      
      

      Like

    • GeoffR's avatar

      GeoffR 8:55 pm on 8 February 2024 Permalink | Reply

      TWO × TWO = THREE is much easier than TWO × TWO = FOUR, as the former has only digits 0..9 to consider.

      from itertools import permutations
      
      for p1 in permutations('1234567890', 3):
         T, W, O = p1
         if T == '0':continue
         TWO = int(T + W + O)
         q1 = set('01234560789').difference([T, W, O])
         for p2 in permutations(q1, 3):
            R, H, E = p2
            THREE = int(T + H + R + E + E)
            if TWO * TWO == THREE:
               print(f"Sum: {TWO} * {TWO} = {THREE}.")
      
      # Sum: 138 * 138 = 19044.
      

      Like

  • Unknown's avatar

    Jim Randell 11:11 am on 25 January 2024 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2620: Stating the obvious 

    From The Sunday Times, 9th December 2012 [link] [link]

    I have replaced the letters of the alphabet by the numbers 0 to 25 in some order, using different numbers for different letters. So, for example, a three-letter word could represent a number of three, four, five or six digits. With my particular values:

    ONE + ONE = TWO, and
    ONE is a perfect square.

    What number does TWO represent?

    [teaser2620]

     
    • Jim Randell's avatar

      Jim Randell 11:13 am on 25 January 2024 Permalink | Reply

      The value of a word is formed by concatenating the values (in base 10) of the digits, and then reading the result as a number.

      So, for example, if O = 10, N = 6, E = 25, then the value of ONE is 10625 (= 10:6:25).

      The following run file executes in 229ms. (Internal runtime of the generated program is 73ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --base=26
      --code="word = lambda *vs: int(concat(vs))"
      --macro="@ONE = word(O, N, E)"
      --macro="@TWO = word(T, W, O)"
      --invalid="0,OT"
      
      # ONE is a perfect square
      "is_square(@ONE)"
      
      # ONE + ONE = TWO
      "2 * @ONE == @TWO"
      
      --answer="@TWO"
      

      Solution: TWO = 4232.

      There are two assignments that achieve this:

      E=6 N=11 O=2 T=4 W=23 → ONE=2116 TWO=4232
      E=16 N=1 O=2 T=4 W=23 → ONE=2116 TWO=4232

      And if we allow leading zeros (i.e. O or T is 0), we can also have:

      E=25 N=2 O=0 T=4 W=5 → ONE=225 TWO=450
      E=25 N=6 O=0 T=12 W=5 → ONE=625 TWO=1250
      E=25 N=12 O=0 T=24 W=5 → ONE=1225 TWO=2450

      So perhaps the puzzle would have been better if the values used were 1 to 26.

      Like

    • GeoffR's avatar

      GeoffR 3:00 pm on 25 January 2024 Permalink | Reply

      from math import isqrt
      def is_sq(x):
         return isqrt(x) ** 2 == x
      
      for O in range(1, 27):
          for N in range(26):
              if N == O:
                  continue
              for E in range(26):
                  if E in (O, N):
                      continue
                  #1. ONE is a perfect square.
                  ONE = int(str(O) + str(N) + str(E))
                  if is_sq(ONE):
                      for T in range(1, 27):
                          if T in (O, N, E):
                              continue
                          for W in range(26):
                              if W in (T, O, N, E):
                                  continue      
                              #2. ONE + ONE = TWO
                              TWO = int(str(T) + str(W) + str(O))
                              if ONE + ONE == TWO:
                                  print(f"ONE = {ONE} and TWO = {TWO}")
                                  print(f"O={O}, N={N}, E={E}, T={T}, W={W}")
                                  print()
                                                    
      # ONE = 2116 and TWO = 4232
      # O=2, N=1, E=16, T=4, W=23
      
      # ONE = 2116 and TWO = 4232
      # O=2, N=11, E=6, T=4, W=23
      
      

      Like

    • Frits's avatar

      Frits 4:50 am on 26 January 2024 Permalink | Reply

          
      from itertools import permutations
      from enigma import is_square
      
      sols = set()
      # O has to be even as 2 * ONE = TWO  
      for O in range(2, 26, 2):
        lnO = 1 + (O > 9)
        for N, E in permutations([n for n in range(26) if n != O], 2):
          if not is_square(ONE := int(str(O) + str(N) + str(E))): continue
          
          # parse TWO into TW and O_
          (sTW, sO_) = ((sTWO := str(ONE + ONE))[:-lnO], sTWO[-lnO:])
          if sO_ != str(O): continue
          
          # parse TW into T and W
          for t in range(1 + (len(sTW) == 4), 2 + (len(sTW) > 2)):
            (T, W) = (int(sTW[:t]), int(sTW[t:]))
            # different numbers
            if T == W or any(n in {O, N, E} or n > 25 for n in [T, W]): continue
            sols.add(sTWO)
      
      print(f"answer: {' or '.join(sols)}")      
      

      Like

  • Unknown's avatar

    Jim Randell 7:51 am on 2 January 2024 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2667: Prime time 

    From The Sunday Times, 3rd November 2013 [link] [link]

    On a picture of a clock face I have written A next to one of the numbers. Then I counted a certain number of “hours” clockwise and wrote B. Then I continued in this pattern, always counting the same number of places clockwise and writing the next letter of the alphabet. In this way each letter corresponds to a number between 1 and 12. I noticed that the two numbers with three letters by them were prime. I also noticed that if I wrote the numbers corresponding to the letters of PRIME in order and read them as one long number, then I got a 6-digit prime.

    Which number corresponds to A and which to B?

    [teaser2667]

     
    • Jim Randell's avatar

      Jim Randell 7:52 am on 2 January 2024 Permalink | Reply

      This Python program runs in 60ms. (Internal runtime is 2.1ms).

      Run: [ @replit ]

      from enigma import (
        str_upper, clock, cproduct, irange, inf, multiset, concat, is_prime,
        map2str, printf
      )
      
      # the letters to distribute
      letters = str_upper
      
      # calculate clock values (1-12)
      fn = clock(12)
      
      # choose a starting number and a step
      for (A, n) in cproduct([irange(1, 12), irange(1, 11)]):
        # assign values to the letters
        d = dict((k, fn(v)) for (k, v) in zip(letters, irange(A, inf, step=n)))
      
        # find the values that correspond to 3 letters
        m = multiset.from_seq(d.values())
        k3s = list(k for (k, v) in m.items() if v == 3)
        # there are exactly 2 values, and each is prime
        if not (len(k3s) == 2 and all(is_prime(k) for k in k3s)): continue
      
        # collect the values corresponding to PRIME
        vs = list(d[k] for k in 'PRIME')
        # does it form a 6-digit prime number
        p = concat(vs)
        if not (len(p) == 6 and is_prime(int(p))): continue
      
        # output solution
        printf("A={A} n={n} p={p} [{d}]", d=map2str(d, sep=" ", enc=""))
      

      Solution: A = 7. B = 2.

      So we start at 7 = A and advance 7 hours for each letter.

      The assignment of letters to values is:

      1 = GS
      2 = BNZ
      3 = IU
      4 = DP
      5 = KW
      6 = FR
      7 = AMY
      8 = HT
      9 = OC
      10 = JV
      11 = EQ
      12 = LX

      The values corresponding to three letters being 2 and 7 (both prime).

      And we have:

      PRIME = 4:6:3:7:11

      Which corresponds to the 6-digit prime 463711.

      Like

  • Unknown's avatar

    Jim Randell 7:46 am on 15 October 2023 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2644: Route canal 

    From The Sunday Times, 26th May 2013 [link] [link]

    Stephen is planning a 70-mile canal trip from the lock at Aytown to the lock at Beeswick, stopping at a pub at a lock over half way along the route. He has listed the number of miles from each lock to the next, the largest being the stretch from the pub to the next lock. He has also noted the distances from Aytown to the pub and from the pub to Beeswick. All the numbers that he has written down are different primes.

    Stephen can use his figures to work out the number of miles from any lock to any other. He’s found that, whenever that number of miles between locks is odd, then it is also a prime.

    What (in order) are the numbers of miles between consecutive locks?

    [teaser2644]

     
    • Jim Randell's avatar

      Jim Randell 7:46 am on 15 October 2023 Permalink | Reply

      This Python program runs in 55ms. (Internal runtime is 806µs).

      Run: [ @replit ]

      from enigma import (irange, primes, express, diff, cproduct, subsets, flatten, printf)
      
      # decompose total <t> into different values from <ns>
      def decompose(t, ns):
        if not ns: return
        for qs in express(t, ns, qs=(0, 1)):
          yield list(n for (n, q) in zip(ns, qs) if q > 0)
      
      # check all odd distances are prime
      def check(ds):
        for (i, j) in subsets(irange(len(ds)), size=2):
          d = sum(ds[i:j + 1])
          if d % 2 == 1 and d not in primes: return False
        return True
      
      # consider possible distances from P to B (prime, < 35)
      for d2 in primes.between(2, 34):
        # distance from A to P (prime, > 35)
        d1 = 70 - d2
        if d1 not in primes: continue
      
        # decompose the distances into individual sections
        for d2s in decompose(d2, list(primes.between(2, d2 - 1))):
          max_d = d2s[-1]
          for d1s in decompose(d1, list(diff(primes.between(2, max_d - 1), d2s))):
      
            # construct possible ordered sequences
            for (s1, s2) in cproduct(subsets(s, size=len, select='P') for s in (d1s, d2s[:-1])):
              ds = flatten([s1, [max_d], s2])
              if check(ds):
                # output solution
                printf("{ds}")
      

      Solution: The distances between consecutive locks (from A to B) are (in miles): 17, 13, 11, 19, 7, 3.

      Like

  • Unknown's avatar

    Jim Randell 8:05 am on 8 August 2023 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2639: Prime number 

    From The Sunday Times, 21st April 2013 [link] [link]

    I have given each letter of the alphabet a different whole-number value between 1 and 99 so that each letter represents one or two digits. In this way, for example, a three- letter word can represent a number of three, four, five or six digits. With my values it turns out that:

    PRIME = NUMBER.

    Furthermore, rather fortuitously, each letter used in that display has a value equal to an odd prime number.

    What is the number PRIME?

    [teaser2639]

     
    • Jim Randell's avatar

      Jim Randell 8:06 am on 8 August 2023 Permalink | Reply

      We have encountered a puzzle similar to this before (see: Teaser 2628), and I wrote some generic code to solve that puzzle, which can also be used to solve this puzzle.

      The following Python 3 program runs in 64ms. (Internal runtime is 8.8ms).

      Run: [ @replit ]

      from enigma import (primes, subsets, filter2, group, item, update, remove, translate, join, printf)
      
      # solve() routine copied from teaser2628r.py:
      
      # replace letters in <X>, <Y> with values from <ns>, so the strings formed are equal
      # <ns> groups values by their final digit
      # return the map: letter -> value
      def _solve(X, Y, ns, d):
        # are we done?
        if X == Y:
          yield d
        elif X and Y:
          # remove any common suffix
          while X and Y and X[-1] == Y[-1]: (X, Y) = (X[:-1], Y[:-1])
          if not (X and Y): return
          # split the final characters into numeric / non-numeric
          (nums, nons) = filter2((lambda x: x.isdigit()), [X[-1], Y[-1]])
          # different final numerics = failure
          if len(nums) > 1: return
          # choose values with the same final digit
          # if there is a numeric value use that, otherwise use the groups
          for k in (nums or ns.keys()):
            ss = ns.get(k, [])
            for vs in subsets(ss, size=len(nons), select='P'):
              # update the strings
              d_ = update(d, nons, vs)
              (X_, Y_) = (translate(x, d_, embed=0) for x in (X, Y))
              ns_ = update(ns, [(k, remove(ss, *vs))])
              # and solve for the translated strings
              yield from _solve(X_, Y_, ns_, d_)
      
      # replace letters in <X>, <Y>
      def solve(X, Y, ns):
        # group values by their final digit
        ns = group(map(str, ns), by=item(-1), fn=set)
        # and call the solver
        return _solve(X, Y, ns, dict())
      
      # now solve the puzzle:
      
      # possible numeric values
      ns = list(primes.irange(3, 99))
      
      # word values we are interested in
      (w1, w2) = ("PRIME", "NUMBER")
      
      # turn a word into a string
      fmt = lambda w, sep=':': join((d.get(x, x) for x in w), sep=sep)
      
      # solve the puzzle
      for d in solve(w1, w2, ns):
        # output solution
        printf("{w1}={f1} {w2}={f2}", f1=fmt(w1), f2=fmt(w2))
      

      Solution: PRIME = 531713717.

      We have:

      PRIME = 53:17:13:71:7
      NUMBER = 5:31:71:3:7:17

      Like

      • Frits's avatar

        Frits 7:54 pm on 8 August 2023 Permalink | Reply

        One performance improvement could be to add:

        vars2 = len(nons) == 2

        and

        if vars2 and len(vs[0] + vs[1]) != 3: continue

        I have more performance optimisations but this seems to be the one with the most impact.

        Like

        • Jim Randell's avatar

          Jim Randell 8:42 am on 9 August 2023 Permalink | Reply

          @Frits: To keep things generic we could just skip selections of 2 values where they have the same length. That is a simple change.

          More complicated would be to store candidate values by the final digit and the number of values required and populate it only with different length pairs, and that would avoid the recalculation at each step.

          Like

    • Frits's avatar

      Frits 12:59 pm on 8 August 2023 Permalink | Reply

      Without much analysis (except that both PRIME and NUMBER must have the same trailing digit F).

       
      from enigma import SubstitutedExpression, join
      
      # build string of valid numbers
      P = [3, 5, 7]
      P += [x for x in range(11, 100, 2) if all(x % p for p in P)]
      P = "{" + join(P, ",") + "}" 
      
      # check for same leading digits
      leading = lambda s1, s2: all(x == y for x, y in zip(join(s1), join(s2)))
      # check for same trailing digits
      trailing = lambda s1, s2: all(x == y for x, y in zip(join(s1)[::-1], join(s2)[::-1]))
       
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          #      PRIME             NUMBER  
          # PQ RF IJ MA EF == NO UV MA BC EF RF"
          
          # first check from the back
          "EF in @primes",
          "RF in @primes and len(s2 := {EF, RF}) == 2",
          "trailing([EF], [RF])",
          
          "MA in @primes and len(s3 := s2 | set([MA])) == 3",
          "trailing([MA, EF], [EF, RF])",
          
          # now check from the front
          "PQ in @primes and len(s4 := s3 | set([PQ])) == 4",
          "NO in @primes and len(s5 := s4 | set([NO])) == 5",
          "leading([PQ, RF], [NO])",
      
          "UV in @primes and len(s6 := s5 | set([UV])) == 6",
          "leading([PQ, RF], [NO, UV, MA])",
          
          "IJ in @primes and len(s7 := s6 | set([IJ])) == 7",
          "leading([PQ, RF, IJ], [NO, UV, MA])",
          
          "BC in @primes and BC not in s7",
          
          # check all digits
          "join(@prime) == join(@number)",
        ],
        answer="join(@prime)",
        d2i=dict([(k, "QFJAOVC") for k in [0, 2, 4, 6, 8]]),
        macro=dict([("primes", P)] + [("prime", "[PQ, RF, IJ, MA, EF]")] + \
                   [("number", "[NO, UV, MA, BC, EF, RF]")]
        ),
        distinct="",
        env=dict(leading=leading, trailing=trailing),
        reorder=0,
        verbose=0,    # use 256 to see the generated code
      )
      
      # print answers
      for ans in p.answers():
        print(f"answer: {ans}")
      

      Like

    • Jim Randell's avatar

      Jim Randell 4:46 pm on 8 August 2023 Permalink | Reply

      Here is a run file for this puzzle.

      It would be fairly straightforward to write a program that used this format for a generic solution.

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --base=100
      
      # digits = primes.between(3, 99)
      --digits="3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97"
      
      # check the concatenations match (working from the end)
      --code="check = lambda xs, ys, **kw: zip_eq(join(xs), join(ys), reverse=1, **kw)"
      
      # check the full strings
      "check([P, R, I, M, E], [N, U, M, B, E, R], strict=1)"
      
      # for performance check the partial strings
      "check([E], [R])"
      "check([M, E], [E, R])"
      "check([I, M, E], [B, E, R])"
      "check([R, I, M, E], [M, B, E, R])"
      "check([P, R, I, M, E], [U, M, B, E, R])"
      
      --template=""
      --answer="((P, R, I, M, E), (N, U, M, B, E, R))"
      
      # zip_eq(..., strict=1) only works in Python 3.10+
      --code="require('sys.version', '3.10')"
      

      Like

  • Unknown's avatar

    Jim Randell 9:11 am on 9 May 2023 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2628: Unnaturally quiet 

    From The Sunday Times, 3rd February 2013 [link] [link]

    I have given each letter of the alphabet a different value from 0 to 25, so some letters represent a single digit and some represent two digits. Therefore, for example, a three-letter word could represent a number of three, four, five or six digits. With my values it turns out that:

    NATURAL = NUMBER.

    What is the sum of the digits in the number MUTE?

    [teaser2628]

     
    • Jim Randell's avatar

      Jim Randell 9:15 am on 9 May 2023 Permalink | Reply

      Presumably the value of a word is the number formed by the concatenation of the digits in the values for each letter.

      So we are looking for assignments that make ATURAL = UMBER (as the value of N is immaterial).

      This Python program looks for assignments of letters to numeric values that make the translated versions of the strings equal. It does this by assigning values to the leading letters that allow the corresponding digits to match.

      It runs in 122ms. (Internal runtime is 66ms).

      Run: [ @replit ]

      from enigma import (irange, subsets, filter2, group, item, update, remove, translate, join, catch, printf)
      
      # replace letters in <X>, <Y> with numeric values from <ns>, so the numeric strings formed are equal
      # <ns> groups values by their first digit
      # return the map: letter -> value
      def _solve(X, Y, ns, d):
        # are we done?
        if X == Y:
          yield d
        elif X and Y:
          # remove any common prefix
          while X and Y and X[0] == Y[0]: (X, Y) = (X[1:], Y[1:])
          if not (X and Y): return
          # split the leading characters into numeric / non-numeric
          (nums, nons) = filter2((lambda x: x.isdigit()), [X[0], Y[0]])
          # different leading numerics = failure
          if len(nums) > 1: return
          # choose values with the same leading digit
          # if there is a numeric value use that, otherwise use the groups
          for k in (nums or ns.keys()):
            ss = ns.get(k, [])
            for vs in subsets(ss, size=len(nons), select='P'):
              # update the strings
              d_ = update(d, nons, vs)
              (X_, Y_) = (translate(x, d_, embed=0) for x in (X, Y))
              ns_ = update(ns, [(k, remove(ss, *vs))])
              # and solve for the translated strings
              yield from _solve(X_, Y_, ns_, d_)
      
      # replace letters in <X>, <Y>
      def solve(X, Y, ns):
        # group numeric values (as strings) by their leading digit
        ns = group(map(str, ns), by=item(0), fn=set)
        # and call the solver
        return _solve(X, Y, ns, dict())
      
      # possible values
      ns = irange(0, 25)
      
      # word values we are interested in
      (w1, w2, w3) = ("NATURAL", "NUMBER", "MUTE")
      
      # turn a word into a string
      fmt = lambda w, sep=':': join((d.get(x, x) for x in w), sep=sep)
      # calculate digit sum
      dsum = lambda w: sum(map(int, fmt(w, sep='')))
      
      # solve the puzzle
      for d in solve(w1, w2, ns):
        # sum the digits in w3
        s = catch(dsum, w3)
        if s is not None:
          # output solution
          printf("dsum({w3}) = {s} [{w1}={f1} {w2}={f2} {w3}={f3}]", f1=fmt(w1), f2=fmt(w2), f3=fmt(w3))
      

      Solution: The sum of the digits in MUTE is 12.

      M and U are 12 and 21, in some order. And T and E are 11 and 22, in some order.

      So MUTE is either “12:21:11:22” or “21:12:22:11”. Either way there are four copies of each of the digits “1” and “2”.

      There are 8 ways to assign the letters in ATURAL and UMBER (and N can be any of the remaining values).

      The assignments are of the form:

      NATURAL = N:x:yy:xy:z:x:xz
      NUMBER = N:xy:yx:yz:xx:z

      where: (x, y) are (1, 2), in some order; and z ∈ {0, 3, 4, 5}.


      As noted by Brian Gladman [link], it is more efficient if the words are considered starting at the end, rather than the beginning.

      I think this is because there are many values with leading characters of “1” and “2”, but when values are grouped by their final character they form into much smaller sets (max size = 3).

      The internal runtime of my code is about twice as fast if the words and values are simply reversed (and the answer is the same). But it is straightforward to adapt the program to process the strings from the end.

      This program runs in 90ms. (Internal runtime is 33ms). [@replit].

      from enigma import (irange, subsets, filter2, group, item, update, remove, translate, join, catch, printf)
      
      # replace letters in <X>, <Y> with numeric values from <ns>, so the numeric strings formed are equal
      # <ns> groups values by their final digit
      # return the map: letter -> value
      def _solve(X, Y, ns, d=dict()):
        # are we done?
        if X == Y:
          yield d
        elif X and Y:
          # remove any common suffix
          while X and Y and X[-1] == Y[-1]: (X, Y) = (X[:-1], Y[:-1])
          if not (X and Y): return
          # split the final characters into numeric / non-numeric
          (nums, nons) = filter2((lambda x: x.isdigit()), [X[-1], Y[-1]])
          # different final numerics = failure
          if len(nums) > 1: return
          # choose values with the same final digit
          # if there is a numeric value use that, otherwise use the groups
          for k in (nums or ns.keys()):
            ss = ns.get(k, [])
            for vs in subsets(ss, size=len(nons), select='P'):
              # update the strings
              d_ = update(d, nons, vs)
              (X_, Y_) = (translate(x, d_, embed=0) for x in (X, Y))
              ns_ = update(ns, [(k, remove(ss, *vs))])
              # and solve for the translated strings
              yield from _solve(X_, Y_, ns_, d_)
      
      # replace letters in <X>, <Y>
      def solve(X, Y, ns):
        # group numeric values (as strings) by their final digit
        ns = group(map(str, ns), by=item(-1), fn=set)
        # and call the solver
        return _solve(X, Y, ns, dict())
      
      # possible numeric values
      ns = irange(0, 25)
      
      # word values we are interested in
      (w1, w2, w3) = ("NATURAL", "NUMBER", "MUTE")
      
      # turn a word into a string
      fmt = lambda w, sep=':': join((d.get(x, x) for x in w), sep=sep)
      # calculate digit sum
      dsum = lambda w: sum(map(int, fmt(w, sep='')))
      
      # solve the puzzle
      for d in solve(w1, w2, ns):
        # sum the digits in w3
        s = catch(dsum, w3)
        if s is not None:
          # output solution
          printf("dsum({w3}) = {s} [{w1}={f1} {w2}={f2} {w3}={f3}]", f1=fmt(w1), f2=fmt(w2), f3=fmt(w3))
      

      Like

  • Unknown's avatar

    Jim Randell 8:49 am on 17 November 2022 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2680: Yesterday 

    From The Sunday Times, 2nd February 2014 [link] [link]

    In a new design of mobile phone each of the number buttons 1 to 9 is associated with 2 or 3 letters of the alphabet, but not in alphabetical order (and there are no letters on any other buttons). For example: M, T and U are on the same button. Predictive software chooses letters for you as you type. The numbers to type for SUNDAY, TIMES and TEASER are all multiples of 495.

    What number should I type to make SATURDAY?

    [teaser2680]

     
    • Jim Randell's avatar

      Jim Randell 8:52 am on 17 November 2022 Permalink | Reply

      We can solve this puzzle using the [[ SubstitutedExpression ]] solver from the enigma.py library.

      The following run file executes in 67ms. (Internal runtime of the generated program is 1.8ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --digits="1-9"
      --distinct=""
      
      # SUNDAY, TIMES, TEASER are all multiples of 495
      "SUNDAY % 495 = 0"
      "TIMES % 495 = 0"
      "TEASER % 495 = 0"
      
      # M, T, U have the same value
      "M = T"
      "T = U"
      
      # no digit appears more than 3 times
      "all(v < 4 for v in multiset.from_seq([A, D, E, I, M, N, R, S, T, U, Y]).values())"
      
      --answer="SATURDAY"
      --template="SUNDAY TIMES TEASER SATURDAY"
      

      Solution: To type SATURDAY use the following keys: 58225185.

      The letters we are given are assigned to the following keys:

      1: D I
      2: M T U
      5: R S Y
      6: N
      8: A E

      So we have:

      SUNDAY = 526185 = 495 × 1063
      TIMES = 21285 = 495 × 43
      TEASER = 288585 = 495 × 583

      Like

    • GeoffR's avatar

      GeoffR 12:27 pm on 17 November 2022 Permalink | Reply

      # last digit of TIMES, SUNDAY, TEASER
      Y = S = R = 5
      
      digits = set(range(1, 10)).difference({5})
      
      for T in digits:
        M = U = T  # stated condition
        
        # Digits for I and E in TIMES
        for I in digits:
          for E in digits:
            TIMES = 10000*T + 1000*I + 100*M + 10*E + S
            if not (TIMES % 495 == 0):continue
            
            # Add Digit A for TEASER
            for A in digits:
              TEASER = 100000*T + 10000*E + 1000*A + 100*S + 10*E + R
              if not (TEASER % 495 == 0):continue
              
              # Add Digits N and D for SUNDAY
              for N in digits:
                for D in digits:
                  SUNDAY = 100000*S + 10000*U + 1000*N + 100*D + 10*A + Y
                  if not (SUNDAY % 495 == 0):continue
                  
                  print(f"Keys to press for SATURDAY are : ")
                  print(f"{S},{A},{T},{U},{R},{D},{A},{Y}")
                  print(f"SUNDAY = {SUNDAY}, TIMES={TIMES}, TEASER={TEASER}")
                  
      # Keys to press for SATURDAY are : 5,8,2,2,5,1,8,5
      # SUNDAY = 526185, TIMES=21285, TEASER=288585
      
      

      Like

    • GeoffR's avatar

      GeoffR 1:30 pm on 17 November 2022 Permalink | Reply

      This snippet needs to be added to the output to show no more than three digits were associated with
      each button.

      Digits used with each button as follows:

      [A,D,E,I,M,N,R,S,T,U,Y =
      [8,1,8,1,2,6,5,5,2,2,5]

      Like

  • Unknown's avatar

    Jim Randell 3:14 pm on 30 July 2020 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2699: Low power 

    From The Sunday Times, 15th June 2014 [link] [link]

    I have written down an addition sum and then consistently replaced digits by letters, with different letters used for different digits. The result is:

    KILO + WATT = HOUR

    Appropriately enough, I can also tell you that WATT is a perfect power.

    Find the three-figure LOW number.

    [teaser2699]

     
    • Jim Randell's avatar

      Jim Randell 3:15 pm on 30 July 2020 Permalink | Reply

      Originally I wrote a program to collect the 4-digit powers and then use the [[ SubstitutedSum() ]] to solve the alphametic sum and look for solutions where WATT is one of the powers. This runs in 68ms.

      Run: [ @replit ]

      from enigma import (defaultdict, irange, inf, SubstitutedSum, join, printf)
      
      # collect powers x^k that are 4-digits
      powers = defaultdict(list)
      for k in irange(2, inf):
        if 2**k > 9999: break
        for x in irange(1, inf):
          n = x**k
          if n < 1000: continue
          if n > 9999: break
          powers[n].append((x, k))
      
      # solve the substituted sum
      p = SubstitutedSum(['KILO', 'WATT'], 'HOUR', d2i={ 0: 'KWHL' })
      for s in p.solve():
        WATT = int(p.substitute(s, "WATT"))
        ps = powers.get(WATT, None)
        if ps:
          LOW = p.substitute(s, "LOW")
          printf("LOW={LOW} [{p.text} -> {t}, WATT = {ps}]",
            t=p.substitute(s, p.text),
            ps=join((join((x, '^', k)) for (x, k) in ps), sep=", "),
          )
      

      Solution: LOW = 592.

      The sum is:

      6159 + 2744 = 8903
      2744 = 14^3

      But it is less typing to use the [[ SubstitutedExpression() ]] solver to solve the alphametic sum and check that WATT is an exact power. The following run file executes in 106ms, so it’s still quite quick.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "KILO + WATT = HOUR"
      "any(is_power(WATT, k) for k in irange(2, 13))"
      
      --answer="LOW"
      

      We only need to check powers up to 13 as 2^14 = 16384 is 5 digits.

      There is a second possible solution to the sum:

      2907 + 3844 = 6751
      3844 = 62^2

      But this gives LOW a value of 073, so is disallowed.

      Like

    • GeoffR's avatar

      GeoffR 8:56 pm on 30 July 2020 Permalink | Reply

      from itertools import permutations
      digits = set(range(10))
      
      for num in range(2, 99):
        for p in range(2, 14):
          watt = num ** p
          if watt >= 10000:
            break
          if  watt >= 1000:
      
            # Find <WATT> digits first
            w, a, t, _t = (int(c) for c in str(watt))
            dig_left = digits.difference({w, a, t})
            if _t == t and len(dig_left) == 7:
      
              # permute remaining digits
              for q in permutations (dig_left):
                k, i, l, o, h, u, r = q
                kilo = 1000*k + 100*i + 10*l + o
                hour = 1000*h + 100*o + 10*u + r
                if kilo + watt == hour:
                  low = 100*l + 10*o + w
                  if  100 < low < 1000:
                    print(f"LOW = {low}")
                    print(f"Sum is {kilo} + {watt} = {hour}")
      
      # LOW = 592
      # Sum is 6159 + 2744 = 8903
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 12:18 pm on 14 June 2020 Permalink | Reply
    Tags: by: Peter G Chamberlain   

    Teaser 2655: Sudoprime 

    From The Sunday Times, 11th August 2013 [link] [link]

    The grid shows a cross-figure with two of its digits given. The eleven answers (five of them being “across” and the other six “down”) are all different prime numbers with no leading zeros. No digit appears more than once in any row, column or main diagonal.

    What are the two largest of the eleven primes?

    [teaser2655]

     
    • Jim Randell's avatar

      Jim Randell 12:19 pm on 14 June 2020 Permalink | Reply

      See also: Enigma 1730, Enigma 1740, Enigma 1755.

      I used the [[ SubstitutedExpression() ]] solver from the enigma.py library to solve this puzzle. The condition that there are no repeated digits in the rows, columns or diagonals can be expressed using the [[ --distinct ]] parameter, which lets us specify multiple groups of symbols, where none of the groups may contain repeated digits.

      The following run file executes in 93ms.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      #
      #  A # # # B
      #  C D # E F
      #  # G H I #
      #  J K # L M
      #  N # # # P
      #
      
      # the two values we are given
      --assign="G,8"
      --assign="N,7"
      
      # the 11 answers are all different prime numbers
      "is_prime(AC)"
      "is_prime(BF)"
      "is_prime(CD)"
      "is_prime(EF)"
      "is_prime(JK)"
      "is_prime(LM)"
      "is_prime(JN)"
      "is_prime(MP)"
      "all_different(AC, BF, CD, EF, JK, LM, JN, MP)"
      
      "is_prime(DGK)"
      "is_prime(EIL)"
      "is_prime(GHI)"
      "all_different(DGK, EIL, GHI)"
      
      # no digit appears more than once in any row, column, or main diagonal
      --distinct="AB,CDEF,GHI,JKLM,NP,ACJN,DGK,EIL,BFMP,ADHLP,NKHEB"
      
      # answer is the two largest primes
      --answer="last(ordered(DGK, EIL, GHI), 2)"
      
      # suppress verbose output
      --template=""
      

      Solution: The two largest primes are: 821, 983.

      The completed grid looks like this:

      Like

    • GeoffR's avatar

      GeoffR 3:21 pm on 14 June 2020 Permalink | Reply

      % A Solution in MiniZinc - same grid as Jim
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D; var 1..9:E; 
      var 1..9:F; var 1..9:G; var 1..9:H; var 1..9:I; var 1..9:J; 
      var 1..9:K; var 1..9:L; var 1..9:M; var 1..9:N; var 1..9:P; 
      
      % Given values
      constraint G == 8 /\ N == 7;
      
      % Rows
      var 11..97:CD = 10*C + D; 
      var 11..97:EF = 10*E + F; 
      var 11..97:JK = 10*J + K; 
      var 11..97:LM = 10*L + M; 
      var 101..997:GHI = 100*G + 10*H + I;
      % Columns
      var 11..97:AC = 10*A + C; 
      var 11..97:JN = 10*J + N; 
      var 101..997:DGK = 100*D + 10*G + K; 
      var 101..997:EIL = 100*E + 10*I + L; 
      var 11..97:BF = 10*B + F; 
      var 11..97:MP = 10*M + P; 
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      % The eleven answers are all prime numbers
      constraint is_prime(CD) /\ is_prime(EF) /\ is_prime(JK) /\ is_prime(LM)
      /\ is_prime(GHI) /\ is_prime(AC) /\ is_prime(JN) /\ is_prime(DGK)
      /\ is_prime(EIL) /\ is_prime(BF) /\ is_prime(MP);
      
      % All the prime numbers are different
      constraint all_different([CD,EF,JK,LM,GHI,AC,JN,DGK,EIL,BF,MP]);
      
      % Top and bottom rows have different digits
      constraint A != B /\ N != P;
      
      % Other rows have different digits
      constraint all_different([C,D,E,F]) /\ all_different([G,H,I])
      /\ all_different([J,K,L,M]);
      
      % All the columns have different digits
      constraint all_different([A,C,J,N]) /\ all_different([D,G,K])
      /\ all_different([E,I,L]) /\ all_different([B,F,M,P]);
      
      % The diagonals have different digits
      constraint all_different([A,D,H,L,P]) /\ all_different([B,E,H,K,N]);
      
      solve satisfy;
      
      output ["Three digit primes are : " ++ show([DGK,GHI,EIL])
      ++ "\nA, B, C, D, E, F = " ++ show([A,B,C,D,E,F])
      ++ "\nG, H, I, J, K, L = " ++ show([G,H,I,J,K,L])
      ++ "\nM, N, P = " ++ show([M,N,P]) ];
      
      % Three digit primes are : [983, 821, 617]
      % A, B, C, D, E, F = [6, 9, 1, 9, 6, 7]
      % G, H, I, J, K, L = [8, 2, 1, 4, 3, 7]
      % M, N, P = [1, 7, 3]
      % ----------
      % ==========
      % Grid solution
      % 6 # # # 9
      % 1 9 # 6 7
      % # 8 2 1 #
      % 4 3 # 7 1
      % 7 # # # 3
      
      
      
      

      Like

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel
Design a site like this with WordPress.com
Get started