Design a site like this with WordPress.com
Get started

Updates from December, 2022 Toggle Comment Threads | Keyboard Shortcuts

  • Jim Randell 8:58 am on 1 December 2022 Permalink | Reply
    Tags:   

    Teaser 2689: The right choice 

    From The Sunday Times, 6th April 2014 [link]

    I am organising a tombola for the fete. From a large sheet of card (identical on both sides) I have cut out a lot of triangles of equal area. All of their angles are whole numbers of degrees and no angle exceeds ninety degrees. I have included all possible triangles with those properties and no two of them are identical. At the tombola entrants will pick a triangle at random and they will win if their triangle has a right-angle. The chances of winning turn out to be one in a certain whole number.

    What is that whole number?

    [teaser2689]

     
    • Jim Randell 8:59 am on 1 December 2022 Permalink | Reply

      Once we have chosen the three angles of the triangle (each an integer between 1° and 90°, that together sum 180°), we can form a triangle with the required area by scaling it appropriately.

      This Python program counts the number of possible triangles and the number of them that are right angled.

      It runs in 53ms. (Internal runtime is 1.0ms).

      Run: [ @replit ]

      from enigma import (decompose, fraction, printf)
      
      # generate possible triangles
      triangles = lambda: decompose(180, 3, increasing=1, sep=0, min_v=1, max_v=90)
      
      # count:
      # t = total number of triangles
      # r = number with a right angle
      r = t = 0
      for (a, b, c) in triangles():
        t += 1
        if c == 90: r += 1
      
      # output solution
      (p, q) = fraction(r, t)
      printf("P(win) = {r}/{t} = {p}/{q}")
      

      Solution: The chance of winning is 1/16.

      There are 720 triangles, and 45 of them are right angled.

      I think it would be annoying to choose a triangle with an 89° angle.

      Like

  • Jim Randell 8:28 am on 29 November 2022 Permalink | Reply
    Tags:   

    Teaser 2685: Not the Gold Cup 

    From The Sunday Times, 9th March 2014 [link]

    Five horses took part in a race, but they all failed to finish, one falling at each of the first five fences. Dave (riding Egg Nog) lasted longer than Bill whose horse fell at the second fence; Big Gun fell at the third, and the jockey wearing mauve lasted the longest. Long Gone lasted longer than the horse ridden by the jockey in yellow, Chris’s horse fell one fence later than the horse ridden by the jockey in green, but Fred and his friend (the jockey in blue) did not fall at adjacent fences. Nig Nag was ridden by Wally and Dragon’s jockey wore red.

    Who was the jockey in yellow, and which horse did he ride?

    [teaser2685]

     
    • Jim Randell 8:28 am on 29 November 2022 Permalink | Reply

      I used the [[ SubstitutedExpression ]] solver from the enigma.py library to assign the fence values (1-5) to the 3 groups of jockeys, horses and colours.

      The following run file executes in 66ms. (Internal runtime of the generated program is 94µs).

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      # assign fence numbers (1-5) to the following groups:
      #
      #  jockeys = A (Wally), B (Bob), C (Chris), D (Dave), E (Fred)
      #  horses  = F (Egg Nog), G (Big Gun), H (Long Gone), I (Nig Nag), J (Dragon)
      #  colours = K (mauve), L (yellow), M (green), N (blue), P (red)
      
      SubstitutedExpression
      
      --base="6"
      --digits="1-5"
      --distinct="ABCDE,FGHIJ,KLMNP"
      --template="(A B C D E) (F G H I J) (K L M N P)"
      --solution=""
      
      # "Dave (D) riding Egg Nog (F) lasted longer than Bill (B) who fell at the 2nd fence"
      "D = F"
      "D > B"
      "2 = B"
      
      # "Big Gun (G) fell at the 3rd"
      "3 = G"
      
      # "mauve (K) lasted the longest"
      "5 = K"
      
      # "Long Gone (H) lasted longer than yellow (L)
      "H > L"
      
      # "Chris's (C's) horse fell one fence later than the horse ridden by green (M)"
      "M + 1 = C"
      
      # "Fred (E) and his friend blue (N) did not fall at adjacent fences"
      "abs(E - N) > 1"
      
      # "Nig Nag (I) was ridden by Wally (A)"
      "I = A"
      
      # "Dragon's (J's) jockey wore red"
      "J = P"
      

      We can wrap this in Python code to format the solution in a more friendly form:

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, group, printf)
      
      # load the run file
      p = SubstitutedExpression.from_file("teaser2685.run")
      
      # map symbols to jockey, horse, colour names
      name = dict(
        A="Wally", B="Bob", C="Chris", D="Dave", E="Fred",
        F="Egg Nog", G="Big Gun", H="Long Gone", I="Nig Nag", J="Dragon",
        K="mauve", L="yellow", M="green", N="blue", P="red",
      )
      
      # solve the puzzle
      for s in p.solve(verbose=0):
        # group symbols by value
        d = group(s.keys(), by=s.get)
        # output solution for each fence
        for k in sorted(d.keys()):
          # extract jockey, horse, colour
          (j, h, c) = (name[s] for s in sorted(d[k]))
          printf("{k}: {j} ({c}) on {h}")
        printf()
      

      Solution: Fred was in yellow, riding Big Gun.

      The full solution is:

      1st fence: Wally (blue) on Nig Nag
      2nd fence: Bob (red) on Dragon
      3rd fence: Fred (yellow) on Big Gun
      4th fence: Dave (green) on Egg Nog
      5th fence: Chris (mauve) on Long Gone

      Like

  • Jim Randell 9:04 am on 22 November 2022 Permalink | Reply
    Tags:   

    Teaser 2688: Mother’s Day 

    From The Sunday Times, 30th March 2014 [link]

    Today we are having a family get-together to celebrate Mother’s Day. My maternal grandmother, my mother and I have each written down our date of birth in the form “ddmmyy”. This gives us three six-figure numbers and, surprisingly, both of the ladies’ numbers are multiples of mine. Furthermore, all of the digits from 0 to 9 occur somewhere in the three six-figure numbers.

    What is my mother’s six-figure date of birth?

    [teaser2688]

     
    • Jim Randell 9:05 am on 22 November 2022 Permalink | Reply

      At first I found many possible solutions to this puzzle.

      But if we require that the 6-digit numbers formed from the date cannot have a leading zero, then this narrows down the solution space considerably (and essentially restricts the three dates to the form 10xxxx, 20yyyy, 30zzzz, and the multiples being 1, 2, 3).

      This Python program runs in 129ms.

      Run: [ @replit ]

      from datetime import (date, timedelta)
      from enigma import (
        fdiv, inc, repeat, nconcat, nsplit, catch,
        subsets, append, tuples, union, join, printf
      )
      
      # extract date as a 6-digit number
      def number(d):
        if d.day < 10: return None
        return nconcat(d.day, d.month, d.year % 100, base=100)
      
      # check viable generation gap, a -> b
      def gap(a, b):
        y = fdiv((b - a).days, 365.2422)
        return not (y < 16 or y > 50)
      
      # consider dates for the setters birthdate
      for d in repeat(inc(timedelta(days=-1)), date(2014, 3, 30)):
        if d.year < 1922: break
        # construct the number "ddmmyy"
        n = number(d)
        if n is None: continue
        # look for proper multiples that also give a valid date
        mn = n
        ds = list()
        while True:
          mn += n
          if mn > 311299: break
          (dd, mm, yy) = nsplit(mn, base=100)
          for y in (1900 + yy, 1800 + yy):
            if y < 1892: continue
            md = catch(date, y, mm, dd)
            if md is None or number(d) is None: continue
            ds.append(md)
      
        # look for a set of 3 plausible ages
        for ss in subsets(sorted(ds), size=2):
          ss = append(ss, d)
          if not all(gap(a, b) for (a, b) in tuples(ss, 2)): continue
          # check all digits are used
          if len(union(nsplit(number(d)) for d in ss)) < 10: continue
          # output dates
          printf("{ss}", ss=join(ss, sep=" -> "))
      

      The program works backwards from 2014 to 1922 looking for sets of 3 dates that make a plausible set of birthdates for the three generations.

      It finds 2 possible situations, as below (ages shown are on the date the puzzle was published (2014-03-30)):

      Grandmother = 1919-08-30 (age = 94.6y)
      Mother = 1946-05-20 (age = 67.9y)
      Setter = 1973-02-10 (age = 41.1y)
      gap = 26.7y
      100273 × 2 = 200546
      100273 × 3 = 300819

      Grandmother = 1892-07-30 (age = 121.7y)
      Mother = 1928-05-20 (age = 85.9y)
      Setter = 1964-02-10 (age = 50.1y)
      gap = 35.7y
      100264 × 2 = 200528
      100264 × 3 = 300792

      The second of these is only just plausible, so presumably the first provides the required answer. (I would have preferred the puzzle eliminated one of these solutions by an explicit condition).

      Solution: The mother’s date of birth is: 200546 (i.e. 20th May 1946).

      To see solutions where the 6-digit number formed from the date is permitted to have a leading zero, the check at line 9 can be removed. In this case the program finds 115 solutions.

      Like

  • Jim Randell 8:49 am on 17 November 2022 Permalink | Reply
    Tags:   

    Teaser 2680: Yesterday 

    From The Sunday Times, 2nd February 2014 [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 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 -r
      
      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 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 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

  • Jim Randell 8:40 am on 10 November 2022 Permalink | Reply
    Tags:   

    Teaser 2611: Simple arithmetic 

    From The Sunday Times, 7th October 2012 [link]

    George and Martha are teaching their great-grandchildren some simple arithmetic. “If you add two thirties to four tens you get a hundred”, George was saying, and he wrote it like this:

    “Strangely”, added Martha, there are nine different letters there, and if you allow each letter to stand for a different digit, there is a unique sum which works”.

    Which digit would be missing?

    [teaser2611]

     
    • Jim Randell 8:40 am on 10 November 2022 Permalink | Reply

      We can use the [[ SubstitutedExpression.split_sum() ]] solver from the enigma.py library to solve this puzzle.

      The following run file executes in 58ms. (Internal runtime is 4.74ms).

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, irange, diff, join, printf)
      
      # construct the alphametic sum
      p = SubstitutedExpression.split_sum(
        ["THIRTY"] * 2 + ["TEN"] * 4, "HUNDRED",
        template="(2 * {THIRTY} + 4 * {TEN} = {HUNDRED})",
      )
      
      # solve the puzzle, and output unused digit(s)
      for s in p.solve():
        ds = diff(irange(0, 9), s.values())
        # output solution
        printf("-> unused digits = {ds}", ds=join(ds, sep=", "))
      

      Solution: The missing digit is 7.

      Like

    • GeoffR 11:40 am on 10 November 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      %          T H I R T Y
      %          T H I R T Y
      %                T E N
      %                T E N
      %                T E N
      %                T E N
      %        -------------
      %        H U N D R E D
      %        -------------
      
      set of int: INT = 0..9;
      var INT: T; var INT: H; var INT: I;
      var INT: R; var INT: Y; var INT: E;
      var INT: N; var INT: U; var INT: D;
      
      constraint T > 0 /\ H > 0;
      constraint all_different( [T,H,I,R,Y,E,N,U,D]);
      
      % Carry digits from RH end
      var 0..6:c1; var 0..6:c2; var 0..6:c3;
      var 0..1:c4; var 0..1:c5; var 0..1:c6;
      
      % Adding digits in columns, with carry digits from RH end of sum
      constraint D == (4*N + 2*Y) mod 10 /\ c1 == (4*N + 2*Y) div 10;
      constraint E == (c1 + 4*E + 2*T) mod 10 /\ c2 == (c1 + 4*E + 2*T) div 10;
      constraint R == (c2 + 4*T + 2*R) mod 10 /\ c3 == (c2 + 4*T + 2*R) div 10;
      constraint D == (c3 + 2*I) mod 10 /\ c4 == (c3 + 2*I) div 10;
      constraint N == (c4 + 2*H) mod 10 /\ c5 == (c4 + 2*H) div 10;
      constraint U == (c5 + 2*T) mod 10 /\ c6 == (c5 + 2*T) div 10;
      constraint H == c6;
      
      solve satisfy;
      
      output ["[T,H,I,R,Y,E,N,U,D] = " ++ show([T,H,I,R,Y,E,N,U,D]) ];
      % [T, H, I, R, Y, E, N, U, D] = 
      % [9, 1, 5, 2, 6, 0, 3, 8, 4]
      % ----------
      % ==========
      % By inspection, missing digit = 7.
      % TEN = 903, THIRTY = 915296, HUNDRED = 1834204.
      
      

      Like

    • GeoffR 10:25 pm on 10 November 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      %          T H I R T Y
      %          T H I R T Y
      %                T E N
      %                T E N
      %                T E N
      %                T E N
      %        -------------
      %        H U N D R E D
      %        -------------
       
      set of int: INT = 0..9;
      var INT: T; var INT: H; var INT: I;
      var INT: R; var INT: Y; var INT: E;
      var INT: N; var INT: U; var INT: D;
       
      constraint T > 0 /\ H > 0;
      constraint all_different( [T,H,I,R,Y,E,N,U,D]);
       
      var 100..999:TEN = 100*T + 10*E + N;
      var 100000..999999:THIRTY = 100000*T + 10000*H + 1000*I + 100*R + 10*T + Y;
      var 1000000..9999999:HUNDRED = 1000000*H + 100000*U + 10000*N + 1000*D + 100*R + 10*E + D;
      
      constraint 2 * THIRTY + 4 * TEN == HUNDRED;
      
      solve satisfy;
      
      output [ "T,H,I,R,Y,E,N,U,D] = " ++ show( [T,H,I,R,Y,E,N,U,D] ) ];
      
      % [T, H, I, R, Y, E, N ,U, D] = 
      % [9, 1, 5, 2, 6, 0, 3, 8, 4]
      % time elapsed: 0.17 s
      % ----------
      % ==========
      % By inspection, the missing digit is 7.
      
      
      

      Like

  • Jim Randell 9:01 am on 8 November 2022 Permalink | Reply
    Tags:   

    Teaser 2695: Powers behind the thrones 

    From The Sunday Times, 18th May 2014 [link]

    Gold sovereigns were minted in London for most years from the great recoinage in 1817 until Britain left the gold standard in 1917. I have a collection of eight sovereigns from different years during that period, the most recent being an Edward VII sovereign (he reigned from 1902 until 1910). I noticed that the year on one of the coins is a perfect square and this set me thinking about other powers. Surprisingly, it turns out that the product of the eight years on my coins is a perfect cube.

    What (in increasing order) are those eight years?

    [teaser2695]

     
    • Jim Randell 9:02 am on 8 November 2022 Permalink | Reply

      The values were are interested in lie in the interval [1817, 1910].

      And the only square in that range is 1849 (= 43²), so that must be one of the 8 values.

      And the largest value is in the interval [1902, 1910].

      This Python program finds the prime factors for numbers in the required range, and then constructs sets of 8 values whose prime factors, when combined, all appear a multiple of 3 times, and so the product of the values is a cube.

      It runs in 164ms.

      Run: [ @replit ]

      from enigma import (irange, prime_factor, multiset, delete, append, printf)
      
      # find values whose product is a cube
      # k = remaining values for find (int)
      # vs = values so far (list)
      # fs = prime factors so far (multiset)
      # d = map of value -> prime factors (multiset)
      # ks = candidate keys in d (list)
      def solve(n, vs, fs, d, ks):
        if n == 0:
          # check all prime exponents are multiples of 3
          if all(x % 3 == 0 for x in fs.values()):
            yield vs
        elif not len(ks) < n:
          # add in a new candidate
          for (i, k) in enumerate(ks):
            # and solve for the remaining candidates
            yield from solve(n - 1, append(vs, k), fs.combine(d[k]), d, ks[i + 1:])
      
      # collect candidate numbers and their prime factors (as a multiset)
      d = dict((n, multiset.from_pairs(prime_factor(n))) for n in irange(1817, 1910))
      
      # find factors that appear fewer than 3 times in total
      while True:
        # count the factors (by combining values from each of the numbers)
        m = multiset.from_dict(*d.values())
        fs = set(k for (k, v) in m.items() if v < 3)
        if not fs: break
        # and remove any numbers which involve any of these factors
        d = delete(d, (k for (k, v) in d.items() if any(p in fs for p in v)))
      
      # 1849 is one of the values in the set
      (vs1, fs1) = ([1849], d[1849])
      
      # and the max value is between 1902 and 1910
      for v in irange(1902, 1910):
        if v not in d: continue
        vs2 = append(vs1, v)
        fs2 = fs1.union(d[v])
      
        # solve for the remaining 6 values
        ks = sorted(k for k in d.keys() if k < v and k not in vs2)
        for vs in solve(6, vs2, fs2, d, ks):
          # output solution
          printf("years = {vs}", vs=sorted(vs))
      

      Solution: The eight years are: 1820, 1836, 1849, 1859, 1870, 1890, 1892, 1904.

      So we have:

      numbers = (1820, 1836, 1849, 1859, 1870, 1890, 1892, 1904)
      factors = (2^12, 3^6, 5^3, 7^3, 11^3, 13^3, 17^3, 43^3)
      factors = (2^4, 3^2, 5, 7, 11, 13, 17, 43)^3

      And the product of the numbers is: (2^4, 3^2, 5, 7, 11, 13, 17, 43)^3 = 526846320^3.

      Like

    • Hugh Casement 8:31 am on 10 November 2022 Permalink | Reply

      We can get part of the way by analysis.  We need another factor 43, so 1892 must be one of the years.
      The only integer in the range 1902 to 1910 with a small enough largest prime factor to allow us to find two more is 1904 = 2^4 × 7 × 17.
      After that I think trial and error (i.e. by computer) is needed.

      Like

      • Jim Randell 11:03 am on 10 November 2022 Permalink | Reply

        @Hugh: My manual solution proceeded as follows:

        Armed with the prime factorisations of the numbers between 1817 and 1910 (which I did not compute manually), we quickly find 1849, 1892, 1904 are on the list.

        Then we need more factors of 17, and there are only 2 candidates: 1836 and 1870. Both of which must be included. So we now have 5 of the 8 numbers on the list.

        We can eliminate numbers with a factor of 29 (1827, 1856, 1885).

        Considering numbers with factors of 13 (1820, 1859, 1872), if any of these are included it must be 1859 and just one of the others.

        Adding 1859 and 1820 to the list leaves factors of (2, 5, 7) to find, and the only candidate is 1890, and this does indeed give a viable list.

        And I didn’t look for further solutions.

        Like

  • Jim Randell 8:40 am on 1 November 2022 Permalink | Reply
    Tags:   

    Teaser 2679: Palprimes 

    From The Sunday Times, 26th January 2014 [link]

    My two pals and I have been considering “palprimes” (i.e. palindromic numbers that are also prime). In particular each of us tried to find a five-figure palprime and I managed to come up with 39293. Then each of my two pals found a five-figure palprime. On comparing them we were surprised to find that overall our three palprimes used all the digits from 1 to 9.

    What were the other two five-figure palprimes?

    [teaser2679]

     
    • Jim Randell 8:41 am on 1 November 2022 Permalink | Reply

      A 5-digit palindrome is of the form XYZYX, so consists of 3 digits.

      In order for 3 of them to use all the digits from 1-9 then each digit can only appear in one prime, and X, Y, Z must be different digits.

      The setter has used 3, 9, 2, so we need to find 2 primes that use the digits 1, 4, 5, 6, 7, 8, between them.

      This Python program runs in 59ms. (Internal runtime is 370µs).

      Run: [ @replit ]

      from enigma import (
        irange, nconcat, diff, is_prime, subsets, partitions, cproduct,
        unpack, printf
      )
      
      # construct a 5-digit palindrome from digits X, Y, Z
      pal5 = unpack(lambda X, Y, Z: nconcat(X, Y, Z, Y, X))
      
      # construct palindromic primes from digits <ds>
      def primes(ds):
        for ss in subsets(ds, size=len, select="P"):
          if is_prime(pal5(ss)):
            yield ss
      
      # digits used in the first prime
      p0 = (3, 9, 2)
      assert is_prime(pal5(p0))
      
      # split the remaining digits for the other 2 primes
      for dss in partitions(diff(irange(1, 9), p0), 3):
        for (p1, p2) in cproduct(primes(ds) for ds in dss):
          printf("{p0} {p1} {p2}", p0=pal5(p0), p1=pal5(p1), p2=pal5(p2))
      

      Solution: The other palprimes are: 16561 and 78487.


      In fact as the palprimes must end (and begin) with one of 1, 3, 7, 9, we can see that the two numbers we have to find must be of the form 1???1 and 7???7. And we just need to work out how to arrange the digits 4, 5, 6, 8 in the middle of them.

      And we can make a slightly shorter program based on this observation:

      from enigma import (nconcat, is_prime, subsets, printf)
      
      # construct a 5-digit palindrome from digits X, Y, Z
      pal5 = lambda X, Y, Z: nconcat(X, Y, Z, Y, X)
      
      # digits used in the first prime
      p0 = pal5(3, 9, 2)
      assert is_prime(p0)
      
      # insert digits 4, 5, 6, 8 in 1???1, 7???7
      for (A, B, C, D) in subsets((4, 5, 6, 8), size=len, select="P"):
        (p1, p2) = (pal5(1, A, B), pal5(7, C, D))
        if is_prime(p1) and is_prime(p2):
          printf("{p0} {p1} {p2}")
      

      Like

    • GeoffR 3:59 pm on 1 November 2022 Permalink | Reply

      
      from enigma import is_prime, nsplit
      
      mine = 39293  # given five digit palindromic prime
      mset = {3, 9, 2} # my digit set
      
      #  unused digits are 1, 4, 5, 6, 7, 8 for 2 palprimes
      # range of palindromic primes is from 14841 to 78687
      # find 1st palindromic prime
      for p1 in range(14841, 78687+1):
        if is_prime(p1):
          a, b, c, d, e = nsplit(p1)
          if 0 in (a, b, c, d, e):continue
          if a == e and b == d:
            s2 = set((a, b, c, d, e))
            if len(s2) == 3:  
              if len(mset.union(s2)) == 6:
                  
                # find 2nd palindromic prime 
                for p2 in range(p1+1, 78687+1):
                  if is_prime(p2):
                    f, g, h, i, j = nsplit(p2)
                    if 0 in (f, g, h, i, j):continue
                    if f == j and g == i:
                      s3 = set((f, g, h, i, j))
                      if len(s3) == 3:
                        all_set = mset.union(s2).union(s3)
                        if len(all_set) == 9:
                          print(f"Other two primes are {p1} and {p2}.")
      
      # Other two primes are 16561 and 78487.
      
      

      Like

  • Jim Randell 9:13 am on 25 October 2022 Permalink | Reply
    Tags:   

    Teaser 2698: Pond plants 

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

    John bought some packs of pond plants consisting of oxygenating plants in packs of eight, floating plants in packs of four and lilies in packs of two, with each pack having the same price. He ended up with the same number of plants of each type. Then he sold some of these packs for twenty-five per cent more than they cost him. He was left with just fifty plants (with fewer lilies than any other) and he had recouped his outlay exactly.

    How many of these fifty plants were lilies?

    [teaser2698]

     
    • Jim Randell 9:14 am on 25 October 2022 Permalink | Reply

      If John ends up with L packs of lilies, F packs of floating plants and X packs of oxygenating plants, then we have:

      2L + 4F + 8X = 50

      And if he started out by buying a total of N packs, and selling some (at 25% markup) to cover the cost of the initial purchase, then we have:

      (5/4)(N − (X + F + L)) = N
      N = 5(X + F + L)

      And if he initially purchased n plants of each type we have:

      N = n/8 + n/4 + n/2 = (7/8)n

      This Python program runs in 56ms. (Internal runtime is 210µs).

      Run: [ @replit ]

      from enigma import (express, div, printf)
      
      # consider the make up of the 50 remaining plants
      # L = lily packs, F = floating packs; X = oxygenating packs
      # 2L + 4F + 8X = 50
      for (L, F, X) in express(50, (2, 4, 8)):
        # there are fewer lilies than any other type of plant
        if not (2 * L < min(4 * F, 8 * X)): continue
        # calculate the initial number of packs bought
        N = 5 * (L + F + X)
        # calculate the initial number of plants of each type bought
        n = div(8 * N, 7)
        if n is None: continue
        # output solution (final number of lily plants)
        printf("{x} lily plants [L={L} F={F} X={X} -> N={N} n={n}]", x=2 * L)
      

      Solution: 14 of the remaining 50 plants were lilies.

      Initially John bought 80 of each type of plant (240 plants in total) = 10 packs of oxygenators + 20 packs of floating plants + 40 packs of lilies.

      Of these 70 packs he sold 8 packs of oxygenators + 15 packs of floating plants + 33 packs of lilies. Leaving him with 14 packs.

      The sale of these 56 packs at a 25% markup exactly met the initial cost of the 70 packs.

      Leaving John with 2 packs of oxygenators (= 16 plants) + 5 packs of floating plants (= 20 plants) + 7 packs of lilies (= 14 plants). A total of 50 plants.

      Like

  • Jim Randell 8:40 am on 18 October 2022 Permalink | Reply
    Tags:   

    Teaser 2538: Octahedra 

    From The Sunday Times, 15th May 2011 [link]

    Fabulé’s latest jewellery creation consists of a set of identically sized regular octahedra made of solid silver. On each octahedron, Fabulé has gold-plated four of its eight equilateral triangle faces. No two octahedra are the same, but if Fabulé had to make another, then it would be necessary to repeat one of the existing designs.

    How many octahedra are there in the set?

    [teaser2538]

     
    • Jim Randell 8:40 am on 18 October 2022 Permalink | Reply

      The octahedron is the dual of the cube, so colouring the faces of an octahedron is equivalent to colouring the vertices of a cube.

      I already have some code that deals with the rotations of the faces of the cube (see: Teaser 2835), so I used that to generate the rotations of an octahedron.

      We then consider all possible octahedra with 4 faces coloured gold and 4 coloured silver, and then remove those that are equivalent by rotation.

      The following Python program runs in 53ms. (Internal run time is 2.0ms).

      Run: [ @replit ]

      from enigma import (irange, subsets, arg, printf)
      from cube import Cube
      
      # make a cube with faces labelled in powers of 2
      fs = list(1 << i for i in irange(0, 5))
      cube = Cube(faces=fs)
      
      # extract the vertices from a cube
      def vertices(cube):
        (U, D, F, B, L, R) = cube.faces
        return (
          U + F + L, U + F + R, U + B + R, U + B + L,
          D + F + L, D + F + R, D + B + R, D + B + L,
        )
      
      # map vertex values to indices
      m = dict((v, k) for (k, v) in enumerate(vertices(cube)))
      
      # construct the rotations of the faces of an octahedron
      rot8s = list()
      for x in cube.rotations():
        rot8s.append(list(m[v] for v in vertices(x)))
      
      # now on with the puzzle ...
      
      # canonical form of a colouring of the faces of the octahedron
      def canonical(fs):
        return min(tuple(fs[r[i]] for (i, _) in enumerate(fs)) for r in rot8s)
      
      # colour k of the faces
      k = arg(4, 0, int)
      # initial colouring
      fs = list(int(i < k) for i in irange(0, 7))
      # collect all possible permutations of the faces
      rs = set(canonical(s) for s in subsets(fs, size=len, select="mP"))
      
      printf("{k} faces -> {n} different octahedra", n=len(rs))
      

      Solution: There are 7 different octahedra.

      Like

    • Hugh Casement 12:50 pm on 18 October 2022 Permalink | Reply

      I was thinking of the octahedron as two pyramids stuck together base to base, but treating its dual may be easier to visualize.

      The seven variants are then, for example:
      1. All four upper vertices.
      2 to 5. Three upper vertices and each in turn of the lower ones.
      6. Two adjacent upper vertices and their diametric opposites.
      7. Two opposite upper vertices and their diametric opposites.
      Any further patterns turn out to be repeats, rotated or reflected in one way or another (a cube has many axes of symmetry).

      If we allow ourselves any number of gold faces, from 0 to 8, then I think there are 23 variants in all.

      Like

      • Jim Randell 11:17 am on 19 October 2022 Permalink | Reply

        Yes. I’ve updated my program to allow you to specify the number of faces to be coloured:

        0 (or 8) → 1 octahedron
        1 (or 7) → 1 octahedron
        2 (or 6) → 3 octahedra
        3 (or 5) → 3 octahedra
        4 → 7 octahedra

        Which gives the 23 essentially different colourings.

        Like

    • Jim Olson 6:05 pm on 18 October 2022 Permalink | Reply

      I looked at this teaser the same as Hugh and came up with 23.

      Like

  • Jim Randell 8:46 am on 11 October 2022 Permalink | Reply
    Tags:   

    Teaser 2676: New diary 

    From The Sunday Times, 5th January 2014 [link]

    My diary has this design on the cover:

    In this 2-by-3 grid there are 12 junctions (including the corners), some pairs of which are joined by a straight line in the grid. In fact there are 30 such pairs.

    The diary publisher has been using such grids of various sizes for years and the 1998 diary was special because its grid had precisely 1998 pairs of junctions joined by lines. Within the next 10 years they will once again be able to produce a special diary where the number of joined pairs equals the year.

    (a) What was the grid size on the 1998 diary?
    (b) What is the year of this next special diary?

    [teaser2676]

     
    • Jim Randell 8:47 am on 11 October 2022 Permalink | Reply

      In an x by y grid each horizontal line (of length x) has:

      1 pair that are distance x apart
      2 pairs that are distance (x − 1) apart
      3 pairs that are distance (x − 2) apart

      x pairs that are distance 1 apart

      Giving a total of tri(x) pairs on that line, and there are (y + 1) horizontal lines.

      So the total number of horizontal pairs is:

      h(x, y) = (y + 1) tri(x)

      Similarly the total number of vertical pairs is:

      v(x, y) = (x + 1) tri(y)

      And so the total number of pairs is:

      pairs(x, y) = h(x, y) + v(x, y)
      pairs(x, y) = (y + 1)x(x + 1)/2 + (x + 1)y(y + 1)
      pairs(x, y) = (x + 1)(y + 1)(x + y)/2

      This Python program runs in 54ms. (Internal runtime is 488µs).

      Run: [ @replit ]

      from enigma import (irange, inf, div, decompose, printf)
      
      # count pairs on an x by y grid
      pairs = lambda x, y: div((x + 1) * (y + 1) * (x + y), 2)
      
      # check the 3x2 grid gives 30 pairs
      assert pairs(3, 2) == 30
      
      # generate (x, y, pairs(x, y)) values
      def generate():
        # consider increasing x + y values
        for t in irange(2, inf):
          for (x, y) in decompose(t, 2, increasing=1, sep=0, min_v=1):
            yield (x, y, pairs(x, y))
      
      # find the answers
      a = b = 0
      for (x, y, n) in generate():
        # (a) look for grids with 1998 pairs
        if n == 1998:
          printf("(a) {x} x {y} -> {n}")
          a = 1
        # (b) looks for grid in the range [2015, 2024]
        if 2014 < n < 2025:
          printf("(b) {x} x {y} -> {n}")
          b = 1
        # are we done?
        if a and b: break
      

      Solution: (a) The 1998 diary had a 2 × 35 grid; (b) The next special diary is for 2016.

      We have:

      1998 = pairs(2, 35)
      2016 = pairs(5, 23) = pairs(11, 13)


      Alternatively we can start with the required number of pairs n and produce possible (x, y) grids, by considering the divisors of n.

      Which gives us a shorter program.

      This Python program runs in 56ms. (Internal runtime is 622µs).

      Run: [ @replit ]

      from enigma import (divisors_pairs, irange, printf)
      
      def solve(n):
        for (a, b) in divisors_pairs(2 * n):
          for (x, y) in divisors_pairs(b - a - 1):
            if x + y == a:
              yield (x, y)
      
      # years to investigate
      qs = {
        'a': [1998],
        'b': irange(2015, 2024),
      }
      
      # solve the puzzle
      for k in sorted(qs.keys()):
        for n in qs[k]:
          for (x, y) in solve(n):
            printf("({k}) {x} x {y} -> {n}")
      

      Like

    • Hugh Casement 3:18 pm on 11 October 2022 Permalink | Reply

      Of course 2016 is in the past for us, and the next will be 2025:
      1 × 44 or 4 × 26 or 8 × 17.

      However, I maintain that those are the sizes of the arrays of square cells;
      the grids (of lines) are 2 × 45, 5 × 27, and 9 × 18.

      Like

  • Jim Randell 7:58 am on 2 August 2022 Permalink | Reply
    Tags:   

    Teaser 2534: Fiftieth anniversary 

    From The Sunday Times, 17th April 2011 [link] [link]

    We have recently celebrated the 50th anniversary of the Teaser column. At the party for the setters they gave each letter of the alphabet a different number from 1 to 26 (e.g. they made A = 7). Appropriately, this was done in such a way that, for each setter present, the values of the letters of their surname added up to 50. Angela Newing was there (so N+E+W+I+N+G = 50), as were Nick MacKinnon and Hugh Bradley. Only two of Graham Smithers, Danny Roth, Andrew Skidmore, John Owen and Victor Bryant could make it.

    Which two?

    [teaser2533]

     
    • Jim Randell 7:59 am on 2 August 2022 Permalink | Reply

      (See also: Teaser 2884).

      With a bit of analysis we can determine the required answer, and write a program to give us a viable assignment is a reasonable amount of time.

      We are given:

      A = 7
      NEWING = 50
      MACKINNON = 50
      BRADLEY = 50

      Hence:

      3N + CIKMO = 43
      BDELRY = 43
      ⇒ 3N + BCDEIKLMORY = 86

      And BCDEIKLMORY (11 letters) must be at least 71, so:

      3N ≤ 15
      N ≤ 5

      But if N is in (1, 2, 3, 4, 5), the minimal value of BCDEIKLMORY is increased to 83, so:

      3N ≤ 3
      N = 1
      BCDEIKLMORY = 83

      So the letters BCDEIKLMORY are assigned (2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13) (in some order).

      Now, all values from 1 – 13 are accounted for, leaving GHSTW to be assigned values from 14 – 26.

      SMITHERS = 2S + TH + EIMR ≥ 73

      So SMITHERS cannot be 50.

      SKIDMORE = S + IKMO + EDR = S + (40 – C) + (43 – BLY) ≥ 51

      So SKIDMORE cannot be 50.

      OWEN = 50 ⇒ O = ING = IG + 1

      But O must have a value ≤ 13 and G must have a value ≥ 14.

      So OWEN cannot be 50.

      Hence the only remaining candidates are ROTH and BRYANT.

      All that remains is to find a viable assignment of letters to values to show the puzzle has a solution.

      And we can use the [[ SubstitutedExpression ]] solver from the enigma.py library to do that.

      The following run file executes in 66ms. (The internal run time of the generated program is 48µs).

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      SubstitutedExpression
      
      --base="27"
      --digits="1-26"
      
      # fixed values
      --assign="A,7"
      --assign="N,1"
      
      # limits on other values
      --invalid="1|2|3|4|5|6|7|8|9|10|11|12|13,GHSTW"
      --invalid="14|15|16|17|18|19|20|21|22|23|24|25|26,BCDEIKLMORY"
      
      # these sum to 50
      "N + E + W + I + N + G == 50"
      "M + A + C + K + I + N + N + O + N == 50"
      "B + R + A + D + L + E + Y == 50"
      "R + O + T + H == 50"
      "B + R + Y + A + N + T == 50"
      
      # these don't
      "S + M + I + T + H + E + R + S != 50"
      "S + K + I + D + M + O + R + E != 50"
      "O + W + E + N != 50"
      
      --template=""
      --first  # we only need a single solution
      

      Solution: The other two setters were: Danny ROTH and Victor BRYANT.


      Here is one possible assignment found by the run file:

      A = 7
      B = 9
      C = 12
      D = 4
      E = 3
      G = 26
      H = 25
      I = 5
      K = 13
      L = 11
      M = 8
      N = 1
      O = 2
      R = 6
      S = 15
      T = 17
      W = 14
      Y = 10
      (F J P Q U V X Z) = (16, 18, 19, 20, 21, 22, 23, 24)

      Using these values we get:

      NEWING = 1 + 3 + 14 + 5 + 1 + 26 = 50
      MACKINNON = 8 + 7 + 12 + 13 + 5 + 1 + 1 + 2 + 1 = 50
      BRADLEY = 9 + 6 + 7 + 4 + 11 + 3 + 10 = 50
      ROTH = 6 + 2 + 17 + 25 = 50
      BRYANT = 9 + 6 + 10 + 7 + 1 + 17 = 50

      SMITHERS = 15 + 8 + 5 + 17 + 25 + 3 + 6 + 15 = 94
      SKIDMORE = 15 + 13 + 5 + 4 + 8 + 2 + 6 + 3 = 56
      OWEN = 2 + 14 + 3 + 1 = 20

      Without the [[ --first ]] parameter we find there are 33,182,352 possible assignments.

      Like

    • GeoffR 9:59 am on 2 August 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..26:A;   var 1..26:B;   var 1..26:C;   var 1..26:D;
      var 1..26:E;   var 1..26:F;   var 1..26:G;   var 1..26:H;   
      var 1..26:I;   var 1..26:J;   var 1..26:K;   var 1..26:L;
      var 1..26:M;   var 1..26:N;   var 1..26: O;  var 1..26:P;   
      var 1..26:Q;   var 1..26:R;   var 1..26:S;   var 1..26:T;   
      var 1..26:U;   var 1..26:V;   var 1..26:W;   var 1..26:X;   
      var 1..26:Y;   var 1..26:Z;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,
      P,Q,R,S,T,U,V,W,X,Y,Z]);
      
      constraint A == 7;
      
      % Letters of following names sum to 50
      constraint N+E+W+I+N+G = 50;
      constraint M+A+C+K+I+N+N+O+N == 50;
      constraint B+R+A+D+L+E+Y == 50;
      
      % Letters of only two of Smithers, Roth, Skidmore,
      % Owen and Bryant, sum to 50
      constraint sum([ S+M+I+T+H+E+R+S == 50, R+O+T+H == 50,
      S+K+I+D+M+O+R+E == 50, O+W+E+N == 50, B+R+Y+A+N+T == 50]) == 2;
      
      solve satisfy;
      
      output [ "SMITHERS = " ++ show(S+M+I+T+H+E+R+S) ++ "\n"
      ++ "ROTH = " ++ show(R+O+T+H) ++ "\n"
      ++ "SKIDMORE = " ++ show(S+K+I+D+M+O+R+E) ++ "\n"
      ++ "OWEN = " ++ show(O+W+E+N) ++ "\n"
      ++ "BRYANT = " ++ show(B+R+Y+A+N+T) ];
      
      % Typical solution of multiple solutions
      % SMITHERS = 90
      % ROTH = 50  <<<
      % SKIDMORE = 56
      % OWEN = 22
      % BRYANT = 50  <<<
      % ----------
      % Only ROTH and BRYANT sum to 50 for 5 extra names.
      
      

      Like

  • Jim Randell 9:07 am on 21 July 2022 Permalink | Reply
    Tags:   

    Teaser 2533: A lopsided number 

    From The Sunday Times, 10th April 2011 [link] [link]

    In this letters-for-digits substitution puzzle, each letter consistently represents a different digit. In the display, each letter in the top row is the sum of the two letters directly below it:

    What number is LOPSIDED?

    [teaser2533]

     
    • Jim Randell 9:08 am on 21 July 2022 Permalink | Reply

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

      The following run file executes in 64ms. The internal runtime of the generated program is 55µs).

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      SubstitutedExpression
      
      "F + I = P"
      "I + D = O"
      "D + D = S"
      "D + L = E"
      "L + E = R"
      
      --answer="LOPSIDED"
      

      Solution: LOPSIDED = 24961353.

      And: POSER = 94657; FIDDLE = 813325.

      Like

    • GeoffR 11:38 am on 21 July 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 1..9:P; var 1..9:O; var 1..9:S; var 1..9:E; var 1..9:R;
      var 1..9:F; var 1..9:I; var 1..9:D; var 1..9:L; 
      
      constraint all_different([P, O, S, E, R, F, I, D, L]);
      
      constraint F + I == P /\ I + D == O /\ D + D == S
      /\ D + L == E /\ L + E == R;
      
      solve satisfy;
      
      output ["LOPSIDED = \(L)\(O)\(P)\(S)\(I)\(D)\(E)\(D)"];
      
      % LOPSIDED = 24961353
      % ----------
      % ==========
      
      
      

      Like

  • Jim Randell 9:01 am on 14 July 2022 Permalink | Reply
    Tags:   

    Teaser 2532: In hot pursuit 

    From The Sunday Times, 3rd April 2011 [link] [link]

    George and Martha are jogging around a circular track. Martha starts at the most westerly point, George starts at the most southerly point, and they both jog clockwise at their own steady speeds. After a short while Martha is due north of George for the first time. Five minutes later she is due south of him for the first time. Then George catches up with her during their second laps at the most northeasterly point of the track.

    What is Martha’s speed (in degrees turned per minute)?

    [teaser2532]

     
    • Jim Randell 9:02 am on 14 July 2022 Permalink | Reply

      If George starts at 0° and goes at g degrees per minute, and Martha starts at 90° and goes at m degrees per minute.

      Then at time x (shortly after they set off) M is due north of G. So the angle G has gone is the same angular distance that M has to go to reach the north (180°) marker.

      So we have:

      xg = 90° − xm
      x(g + m) = 90°

      And 5 minutes later M is due south of G for the first time. The distance G has gone beyond the north (180°) marker is the same as the distance that M has to go to reach the south (360°/0°) marker.

      (x + 5)g − 180° = 270° − (x + 5)m
      (x + 5)(g + m) = 450°

      5x = x + 5
      4x = 5
      x = 5/4
      g + m = 72°/min

      Later, at some time t during lap 2, G catches up with M at the north-east (225°) marker. So G has gone 585° and M has gone 495°.

      Hence:

      585 = tg
      495 = tm

      t(g + m) = 1080
      t = 1080 / 72 = 15 min
      g = 39°/min
      m = 33°/min

      Solution: Martha’s speed is 33°/min.

      Like

  • Jim Randell 9:04 am on 20 December 2020 Permalink | Reply
    Tags:   

    Teaser 2570: Christmas Teaser 

    From The Sunday Times, 25th December 2011 [link]

    I asked my nine-year-old grandson Sam to set a Teaser for today’s special edition and the result was:

    SAM
    SET
    NICE
    CHRISTMAS
    TEASER

    Those words are the result of taking five odd multiples of nine and consistently replacing digits by letters.

    Given that THREE is divisible by 3; What is the 9-digit number CHRISTMAS?

    This was not a prize puzzle.

    [teaser2570]

     
    • Jim Randell 9:04 am on 20 December 2020 Permalink | Reply

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

      The following run file executes in 95ms.

      Run: [ @replit ]

      #! python -m enigma -r
      
      SubstitutedExpression
      
      # all words are odd multiples of 9
      --code="f = lambda x: x % 18 == 9"
      "f(SAM)"
      "f(SET)"
      "f(NICE)"
      "f(CHRISTMAS)"
      "f(TEASER)"
      
      # and THREE is a multiple of 3
      "THREE % 3 = 0"
      
      --answer="CHRISTMAS"
      

      Solution: CHRISTMAS = 489051765.

      Like

    • GeoffR 9:16 am on 20 December 2020 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:S; var 0..9:A; var 1..9:M; var 0..9:E;
      var 1..9:T; var 1..9:N; var 0..9:I; var 1..9:C;
      var 0..9:H; var 0..9:R;
      
      constraint all_different ([S,A,M,E,T,N,I,C,H,R]);
      
      var 100..999: SAM = 100*S + 10*A + M;
      var 100..999: SET = 100*S + 10*E + T;
      var 1000..9999: NICE = 1000*N + 100*I + 10*C + E;
      
      var 100000000..999999999: CHRISTMAS = S + 10*A
       + 100*M + 1000*T + 10000*S + 100000*I + 1000000*R
        + 10000000*H + 100000000*C;
      
      var 100000..999999: TEASER = 100000*T + 10000*E
       + 1000*A + 100*S + 10*E + R;
       
      var 10000..99999:THREE = 10000*T + 1000*H + 100*R + 11*E;
      
      constraint THREE mod 3 == 0;
      
      % five odd multiples of nine 
      constraint SAM mod 2 == 1 /\ SAM mod 9 == 0;
      constraint SET mod 2 == 1 /\ SET mod 9 == 0;
      constraint NICE mod 2 == 1 /\ NICE mod 9 == 0;
      constraint CHRISTMAS mod 2 == 1 /\  CHRISTMAS mod 9 == 0;
      constraint TEASER mod 2 == 1 /\ TEASER mod 9 == 0;
      
      solve satisfy;
      
      output["CHRISTMAS = " ++ show(CHRISTMAS)  ++
      "\nSAM = " ++ show(SAM) ++ ", SET = " ++ show(SET) ++
      "\nNICE = " ++ show(NICE) ++ ",  TEASER = " ++ show(TEASER) ];
      
      % CHRISTMAS = 489051765
      % SAM = 567, SET = 531
      % NICE = 2043,  TEASER = 136539
      % ----------
      % ==========
      
      
      

      Like

    • GeoffR 10:57 am on 20 December 2020 Permalink | Reply

      Another solution using the digits only in the multiple numbers.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:S; var 0..9:A; var 1..9:M; var 0..9:E;
      var 1..9:T; var 1..9:N; var 0..9:I; var 1..9:C;
      var 0..9:H; var 0..9:R;
      
      constraint all_different ([S,A,M,E,T,N,I,C,H,R]);
      
      % last digits for 5 odd numbers
      constraint M==1 \/ M==3 \/ M==5 \/ M==7 \/ M==9;
      constraint T==1 \/ T==3 \/ T==5 \/ T==7 \/ T==9;
      constraint E==1 \/ E==3 \/ E==5 \/ E==7 \/ E==9;
      constraint S==1 \/ S==3 \/ S==5 \/ S==7 \/ S==9;
      constraint R==1 \/ R==3 \/ R==5 \/ R==7 \/ R==9;
      
      % THREE is divisible by 3, as are the sum of its digits
      constraint (T + H + R + E + E) mod 3 == 0;
      
      % five multiples of nine
      % sum of digits  of each nmber is also divisible by 9
      constraint (S + A + M) mod 9 == 0 ;
      constraint (S + E + T) mod 9 == 0 ;
      constraint (N + I + C + E) mod 9 == 0;
      constraint (C + H + R + I + S + T + M + A + S) mod 9 == 0;
      constraint (T + E + A + S + E + R) mod 9 == 0;
      
      solve satisfy;
      
      output ["CHRISTMAS = " ++ show(C),show(H),show(R),
      show(I),show(S),show(T),show(M),show(A),show(S) ];
      
      % CHRISTMAS = 489051765
      % ----------
      % ==========
      
      

      Like

    • Frits 2:26 pm on 20 December 2020 Permalink | Reply

      Without using modulo and with the Walrus assignment (not possible to run with PyPy).

      from enigma import SubstitutedExpression 
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # all words are odd multiples of 9 (and don't start with zero)
        # digits M,E,S,T,R are odd and A,N,I,C,H are even
        
        # SAM digits: one odd, 2 even; so sum is even and multiple of 18
        # so A >= 2 (max(S+M) is 16), max(A) is 8 so M + S >= 10
        "M + S > 9",
        "S+A+M = 18",                # even
        "S+E+T = 9",                 # odd (9 or 27)
        "N+I+C+E = 9",               # odd, 27 not possible as 
                                     # max(N+I+C) = 14) and A is 6 or 8 (see below)
        
        # C+H+R+I+S+T+M+A+S equals C+H+R+I+S+T plus S+A+M
        "C+H+R+I+S+T == 27",          # odd (9, 27 or 45) 
        
        # T+E+A+S+E+R equals S+E+T plus A+E+R (so A+E+R is even)
        # max(A) is 8 so E + R >= 10
        "E+R > 9",
        "A+E+R = 18",                # even
        # E + R >= 10 and M + S >= 10 so T <= 5 
        
        # (A + E + R) mod 18 == 0 and (S + A + M) mod 18 == 0
        #  so E + R must be equal to M + S --> T is 1 or 5 and A is 6 or 8
         
        # and THREE is a multiple of 3
        # THREE contains one even digit and 4 odd digits 
        # so sum must be even and a multiple of 6
        "(tot := T+H+R+E+E) == 18 or tot == 24",   
        ],
        answer="(C,H,R,I,S,T,M,A,S), E,N",
        # A is 6 or 8, T is 1 or 5
        d2i=dict([(0, "MTESRACN")] + \
                 [(2, "MTESRA")] + \
                 [(3, "ANICHT")] + \
                 [(4, "MTESRA")] + \
                 [(7, "ANICHT")] + \
                 [(9, "ANICHT")] + \
                 [(k, "ANICH") for k in {1, 5}] + \
                 [(k, "MTESR") for k in {6, 8}]),
        distinct="CHRISTMAEN",
        reorder=0,
        verbose=0)   
      
      # print answers 
      for (_, ans) in p.solve():
        print(f"{ans}")
        
      # ((4, 8, 9, 0, 5, 1, 7, 6, 5), 3, 2)
      

      Like

      • Jim Randell 11:17 pm on 20 December 2020 Permalink | Reply

        @Frits: You could just use [[ "T+H+R+E+E in (18, 24)" ]] to eliminate the “walrus” operator.

        Like

        • Frits 11:26 am on 21 December 2020 Permalink | Reply

          @Jim: Thanks. “T+H+R+E+E in {18, 24}” doesn’t work in combination with SubstitutedExpression. However, “T+H+R+E+E in set((18, 24))” works.

          As N+I+C+E = 9 we can also further analyze that I = 0.

          Some benchmark tests on lists, sets and tuples. In one case an in_test for sets is slower than for lists and tuples.

          import time
          from timeit import timeit
          from datetime import datetime
          
          # See https://stackoverflow.com/questions/2831212/python-sets-vs-lists
          # https://www.nuomiphp.com/eplan/en/92679.html
           
          # Determine if an object is present
          def in_test(iterable):
            for i in range(1000):
              if i in iterable:
                pass
          
          # Iterating
          def iter_test(iterable):
            for i in iterable:
              pass
          
          # Determine if an object is present
          print("# in_test")
          print("# set   ", timeit(
          "in_test(iterable)",
          setup="from __main__ import in_test; iterable = set(range(1000))",
          number=10000))
           
          print("# list  ",timeit(
          "in_test(iterable)",
          setup="from __main__ import in_test; iterable = list(range(1000))",
          number=10000))
          
          print("# tuple ", timeit(
          "in_test(iterable)",
          setup="from __main__ import in_test; iterable = tuple(range(1000))",
          number=10000))
          print()
          
          # Iterating
          print("# iter_test")
          print("# set   ", timeit(
          "iter_test(iterable)",
          setup="from __main__ import iter_test; iterable = set(range(10000))",
          number=100000))
          
          print("# list  ", timeit(
          "iter_test(iterable)",
          setup="from __main__ import iter_test; iterable = list(range(10000))",
          number=100000))
          
          print("# tuple ", timeit(
          "iter_test(iterable)",
          setup="from __main__ import iter_test; iterable = tuple(range(10000))",
          number=100000))
          print()
          
          
          
          def in_test1():
            for i in range(1000):
              if i in (314, 628):
                pass
          
          def in_test2():
            for i in range(1000):
              if i in [314, 628]:
                pass
          
          def in_test3():
            for i in range(1000):
              if i in {314, 628}:
                pass
          
          def in_test4():
            for i in range(1000):
              if i == 314 or i == 628:
                pass
          print("# Another in_test")
          print("# tuple", end=" ")
          print(timeit("in_test1()", setup="from __main__ import in_test1", 
                number=100000))
          print("# list ", end=" ")
          print(timeit("in_test2()", setup="from __main__ import in_test2", 
                number=100000))
          print("# set  ", end=" ")
          print(timeit("in_test3()", setup="from __main__ import in_test3", 
                number=100000))
          print("# or   ", end=" ")
          print(timeit("in_test4()", setup="from __main__ import in_test4", 
                number=100000))
          print()
          
          
          l = list(range(10000000))
          t = tuple(range(10000000))
          s = set(range(10000000))
          
          start = time.perf_counter()  
          -1 in l
          elapsed = time.perf_counter() 
          e = elapsed - start 
          print("# Time spent in list is: ", e)
          
          start = time.perf_counter()  
          -1 in s
          elapsed = time.perf_counter() 
          e = elapsed - start 
          print("# Time spent in set is: ", e)
          
          
          start = time.perf_counter()  
          -1 in t
          elapsed = time.perf_counter() 
          e = elapsed - start 
          print("# Time spent in tuple is: ", e)
          print()
          
          
          # Running with PyPy
          #
          # in_test
          # set    0.081273
          # list   1.5676623
          # tuple  4.0899722
          
          # iter_test
          # set    3.432239
          # list   3.486127399999999
          # tuple  2.8504029000000006
          
          # Another in_test
          # tuple 0.1158544999999993
          # list  0.11811669999999985
          # set   0.8540392000000008
          # or    0.12334350000000072
          
          # Time spent in list is:  0.0029356000000007043
          # Time spent in set is:  4.600000000465343e-06
          # Time spent in tuple is:  0.014147899999997549
          #
          # Running with Python 3.9.0
          #
          # in_test
          # set    0.4714717
          # list   51.8807992
          # tuple  48.01474160000001
          
          # iter_test
          # set    11.122311100000005
          # list   7.8272695
          # tuple  8.692177200000003
          
          # Another in_test
          # tuple 4.704576400000008
          # list  4.779769200000004
          # set   3.6342248999999924
          # or    4.508794999999992
          
          # Time spent in list is:  0.09308849999999325
          # Time spent in set is:  3.800000001774606e-06
          # Time spent in tuple is:  0.09272150000001034
          

          Like

    • GeoffR 9:05 am on 22 December 2020 Permalink | Reply

      A Python 3 permutation solution.

      from itertools import permutations
      
      digits = set('0123456789')
      
      for P1 in permutations(digits, 4):
          T, H, R, E = P1
          if T == '0':
              continue
          if int(T) % 2 != 1:
              continue
          if int(E) % 2 != 1:
              continue
          THREE = int(T + H + R + E + E)
          if THREE % 3 != 0:
              continue
          Q1 = digits.difference(P1)
          # permute remaining digits
          for P2 in permutations(Q1):
              C, I, S, M, A, N = P2
              if '0' in (C, S, N):
                  continue
              if (int(M) % 2 != 1 or int(S) % 2 != 1
                      or int(R) % 2 != 1):
                  continue
              SAM, SET = int(S + A + M), int(S + E + T)
              if SAM % 9 != 0 or SET % 9 != 0:
                  continue
              NICE = int(N + I + C + E)
              TEASER = int(T + E + A + S + E + R)
              if NICE % 9 != 0 or TEASER % 9 != 0:
                  continue
              CHRISTMAS = int(C + H + R + I + S + T + M + A + S)
              if CHRISTMAS % 9 == 0:
                  print(f"CHRISTMAS = {CHRISTMAS}")
      
      
      

      Like

    • John Crabtree 3:16 am on 24 December 2020 Permalink | Reply

      E, M, R, S and T are odd, and A, C, H, I and N are even
      S+E+T is odd, = 9 = (1, 3, 5), and so M, R = (7, 9)
      N+I+C+E is odd, = 9 = (0, 2, 4, 3) or (0, 2, 6, 1)

      T+E+A+S+E+R is odd, = 27 and so A+E+R = 18
      C+H+R+I+S+T is odd, = 27, and so N+A+M+E (remaining letters) = 18 = A+E+R, and so R = M+N
      And so R = 9, M = 7, N = 2, and as C > 0, I = 0 and C + E = 7

      From N+A+M+E = 18 = S+A+M, S = E + 2, and so C + S = 9
      (C+H+R+I+S+T) – (T+H+R+E+E) = C + S – 2E = E mod 3 = 0 mod 3
      And so E = 3, A = 6; C = 4, S = 5; and T = 1, H = 8.

      CHRISTMAS = 489051765

      Like

  • Jim Randell 8:55 am on 1 October 2020 Permalink | Reply
    Tags:   

    Teaser 2544: Neighbourly nonprimes 

    From The Sunday Times, 26th June 2011 [link]

    I live in a long road with houses numbered 1 to 150 on one side. My house is in a group of consecutively numbered houses where the numbers are all nonprime, but at each end of the group the next house number beyond is prime. There are a nonprime number of houses in this group. If I told you the lowest prime number which is a factor of at least one of my two next-door neighbours’ house numbers, then you should be able to work out my house number.

    What is it?

    [teaser2544]

     
    • Jim Randell 8:55 am on 1 October 2020 Permalink | Reply

      I implemented the [[ chop() ]] function, which chops a sequence into contiguous segments that give the same value for a function. This can then be used to select the segments of contiguous non-primes.

      We can use a little shortcut to check if a prime p divides one of the neighbours of n. If we consider the product of the neighbours ((n − 1) and (n + 1)), then if the prime divides the product, it must divide one of the neighbours. Now:

      (n − 1)(n + 1) = n² − 1

      So, if the residue of n² modulo p is 1, then p divides one of the neighbours of n.

      The following Python program runs in 47ms.

      Run: [ @repl.it ]

      from enigma import irange, Primes, peek, filter_unique, printf
      
      # chop sequence s into segments that give the same value for function f
      # return pairs of (f-value, segment)
      def chop(f, s):
        (fv, ss) = (None, [])
        for x in s:
          v = f(x)
          if v == fv:
            ss.append(x)
          else:
            if ss: yield (fv, ss)
            (fv, ss) = (v, [x])
        if ss: yield (fv, ss)
      
      # primes up to 150
      primes = Primes(150)
      
      # consider segments of non-primes
      for (fv, ss) in chop(primes.is_prime, irange(1, 150)):
        if fv: continue
        # the length of the segment is non-prime (but > 1)
        n = len(ss)
        if n < 2 or n in primes: continue
      
        # the lowest prime number that is a factor of at least one of my neighbours
        # house numbers uniquely identifies my house number
        f = lambda n: peek(p for p in primes if (n * n) % p == 1)
        hs = filter_unique(ss, f).unique
      
        # output solution
        printf("group = {ss}; house = {hs}")
      

      Solution: Your house number is 144.

      It turns out there is only one segment of non-primes (in the specified range) that has a (non-unity) non-prime length:

      (140, 141, 142, 143, 144, 145, 146, 147, 148)

      It contains 9 numbers.

      And the lowest prime factor of the neighbours uniquely identifies one of the numbers. So it must be an even number, and they all have a smallest-neighbour-factor of 3, except for 144 which has a smallest-neighbour-factor of 5.

      Like

      • Jim Randell 12:57 pm on 1 October 2020 Permalink | Reply

        Or even simpler:

        Run: [ @repl.it ]

        from enigma import irange, Primes, tuples, peek, filter_unique, printf
        
        # primes up to 150
        primes = Primes(150)
        
        # consider 2 consecutive primes
        for (a, b) in tuples(primes, 2):
          # with a non-prime number of non-primes in between
          n = b - a - 1
          if n < 2 or n in primes: continue
          (a, b) = (a + 1, b - 1)
        
          # the lowest prime number that is a factor of at least one of my neighbours
          # house numbers uniquely identifies my house number
          f = lambda n: peek(p for p in primes if (n * n) % p == 1)
          hs = filter_unique(irange(a, b), f).unique
        
          # output solution
          printf("group = [{a} .. {b}]; house = {hs}")
        

        Like

        • Frits 5:05 pm on 1 October 2020 Permalink | Reply

          Nice,

          The output is a little different from the previous program.
          (the definition of a group excludes prime numbers)

          filter_unique also works with a+1 and b-1.

          Like

          • Jim Randell 5:23 pm on 1 October 2020 Permalink | Reply

            @Frits: Yes, you are right. The range should exclude the primes. I’ve fixed it up now.

            Like

    • Frits 10:31 am on 1 October 2020 Permalink | Reply

      [reorder=0] clause was necessary for a decent run time (100 ms).

       
      from enigma import SubstitutedExpression, is_prime
          
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ 
          "A < 2",  # speed up process
          "ABC < 148",
          "D < 2 and D >= A",  # speed up process
          "DEF > ABC + 2",
          "DEF < 151",
          # Find 2 prime numbers
          "is_prime(ABC)",
          "is_prime(DEF)",
          # There are a nonprime number of houses in this group
          "not is_prime(DEF - ABC - 1)",
          # with no other prime numbers between ABC and DEF
          "sum(1 for i in range(ABC + 1, DEF) if is_prime(i)) == 0",
          # my house number MNO lies in such a group
          "M < 2 and M >= A",  # speed up process
          "MNO > ABC",
          "MNO < DEF",
          # my neighbour on one side
          "MNO - 1 = UVW",
          # my neighbour on the other side
          "MNO + 1 = XYZ",
          # lowest prime number which is a factor of at least one of my 
          # neighbours' house numbers
          "min([x for x in range(2, 150) if UVW % x == 0 and is_prime(x)]) = JKL",
          "min([x for x in range(2, 150) if XYZ % x == 0 and is_prime(x)]) = GHI",
        ],
        answer="(min(GHI, JKL), MNO)",
        verbose=0,
        d2i="",        # allow number representations to start with 0
        distinct="",   # allow variables with same values
        reorder=0,
      )
       
      # solve puzzle
      answs = [y for _, y in p.solve()]
      
      # only print answers with a unique first element
      if len(answs) > 0:
        print("My house number:", [a[1] for a in answs 
              if [x[0] for x in answs].count(a[0]) == 1][0])
      
      # My house number: 144
      

      Like

  • Jim Randell 9:10 am on 27 August 2020 Permalink | Reply
    Tags:   

    Teaser 2681: Inconsequential 

    From The Sunday Times, 9th February 2014 [link]

    An “arithmetic” sequence is one in which each number is a fixed amount more than the previous one. So, for example, 10, 29, 48, … is an arithmetic sequence. In this case its ninth number is 162, which happens to be divisible by 9. I have in mind another arithmetic sequence whose ninth number is divisible by 9. This time it starts with two three-figure numbers, but in this case I have consistently replaced digits by letters, with different letters used for different digits.

    The arithmetic sequence then begins ONE, TWO, …, and its ninth number is NINE.

    To win, find the number to WIN.

    [teaser2681]

     
    • Jim Randell 9:10 am on 27 August 2020 Permalink | Reply

      The common difference is (TWOONE), and there are 8 instances of the common difference between ONE and NINE.

      So, we can solve this puzzle straightforwardly using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file executes in 110ms.

      Run: [ @repl.it ]

      #!/usr/bin/env python -m enigma -r
      
      SubstitutedExpression
      
      "div(NINE - ONE, TWO - ONE) = 8"
      "div(NINE, 9) > 0"
      
      --answer="WIN"
      

      Solution: WIN = 523.

      The progression is: ONE = 631, TWO = 956, …, NINE = 3231.

      The common difference is: TWOONE = 325.

      Like

    • GeoffR 10:39 am on 27 August 2020 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9: O; var 1..9: N; var 1..9: E;
      var 1..9: T; var 1..9: W; var 1..9: I;
      
      constraint all_different ([O, N, E, T ,W, I]);
      
      var 100..999: ONE = 100*O + 10*N + E;
      var 100..999: TWO = 100*T + 10*W + O;
      var 100..999: WIN = 100*W + 10*I + N;
      var 1000..9999: NINE = 1000*N + 100*I + 10*N + E;
      
      % Define 9th term of series
      constraint NINE = ONE + 8 * (TWO - ONE)
       /\ NINE mod 9 == 0;
      
      solve satisfy;
      
      output [ "Series is " ++ show(ONE) ++ "," ++ show(TWO) ++ 
      "..." ++ show(NINE) ++ "\n" ++ "WIN = " ++ show(WIN)]
      
      % Series is 631,956...3231
      % WIN = 523
      % ----------
      % ==========
      
      
      

      Like

    • GeoffR 11:02 am on 27 August 2020 Permalink | Reply

      
      from itertools import permutations
      
      for Q in permutations(range(1,10), 6):
        O, N, E, T, W, I = Q
        ONE, TWO = 100*O + 10*N + E, 100*T + 10*W + O
        WIN = 100*W + 10*I + N
        NINE = 1010*N + 100*I + E
        if NINE == ONE + (TWO - ONE) * 8 and NINE % 9 == 0:
          print(f"ONE = {ONE}, TWO = {TWO}, NINE = {NINE}, WIN = {WIN}")
      
      # ONE = 631, TWO = 956, NINE = 3231, WIN = 523
      
      

      Like

      • Frits 1:26 pm on 27 August 2020 Permalink | Reply

        @GeoffR, shouldn’t you allow for E, I and W to be zero as well?

        Like

    • Frits 1:20 pm on 27 August 2020 Permalink | Reply

       
      # elements in range <r> unequal to values of D[i]
      def domain(i, r):
        vals = set()
        for s in D[i]:
          # use globals() iso eval()
          vals.add(globals()[s])
        return [x for x in r if x not in vals]
      
      # set up dictionary of for-loop iterator names
      def init(li):
        for i in range(1,len(li)):
          D[li[i]] = li[0:i]
          
      # concatenate list of integers 
      to_num = lambda *args: int("".join(map(str, args)))    
      
      
      D = {}
      init(['O','N','E','T','W'])
      
      
      for O in range(1,10):
        for N in domain('N', range(1,10)):
          for E in domain('E', range(0,10)):
            ONE = to_num(O,N,E)
            for T in domain('T', range(1,10)):
              if T > O:  
                for W in domain('W', range(0,10)):
                  TWO = to_num(T,W,O)
                  unit = TWO - ONE
                  units8 = 8*unit + ONE
                  if units8 % 9 != 0 or units8 < 1000: continue
                  
                  s = str(units8)
                  if s[3] != str(E): continue 
                  if s[0] != str(N) or s[2] != str(N): continue 
                  # check letter I
                  if int(s[1]) in {O,N,E,T,W}: continue
            
                  WIN = str(W)+s[1]+str(N)
                  print("WIN ONE TWO NINE")
                  print(WIN, ONE, TWO, units8)
      
      # WIN ONE TWO NINE
      # 523 631 956 3231
      

      Like

    • GeoffR 1:47 pm on 27 August 2020 Permalink | Reply

      @Frits: In theory maybe, but it would not have made any difference in practice.
      As the title of the teaser says – “Inconsequential” ?

      Like

  • Jim Randell 9:06 am on 25 August 2020 Permalink | Reply
    Tags: ,   

    Teaser 2677: One for each day 

    From The Sunday Times, 12th January 2014 [link]

    George and Martha have been looking into tests for divisibility,  including one for the elusive number seven. George wrote down a thousand-figure number by simply writing one particular non-zero digit a thousand times. Then he replaced the first and last digits by another non-zero digit to give him a thousand-figure number using just two different digits. Martha commented that the resulting number was divisible by seven. George added that it was actually divisible by exactly seven of 2, 3, 4, 5, 6, 7, 8, 9 and 11.

    What were the first two digits of this number?

    Note: This puzzle is marked as flawed, as under the intended interpretation there is no solution.

    [teaser2677]

     
    • Jim Randell 9:06 am on 25 August 2020 Permalink | Reply

      Although the puzzle is flawed, I didn’t have a problem solving it, as I interpreted the “actually” in George’s statement to mean that George was correcting Martha’s statement, so it didn’t really matter if Martha’s statement was true or false. All we needed to do was find a number divisible by exactly seven of the given divisors. Which is what my code does, and it finds a unique solution, so I was happy enough with the puzzle.

      Python’s support for large integers means this one can be solved in a straightforward way. The following program runs in 55ms.

      Run: [ @replit ]

      from enigma import (subsets, printf)
      
      digits = "123456789"
      divisors = (2, 3, 4, 5, 6, 7, 8, 9, 11)
      
      for (a, b) in subsets(digits, size=2, select="P"):
        n = int(a + b * 998 + a)
        ds = list(d for d in divisors if n % d == 0)
        if len(ds) == 7:
          printf("a={a} b={b} [ds={ds}]")
      

      Solution: The first two digits of George’s number are 6 and 3.

      So the 1,000-digit number is 6333…3336. Which is divisible by 2, 3, 4, 6, 8, 9, 11.


      However, apparently the setter intended Martha’s statement to be true, and the number to be divisible by 7 and exactly six of the other divisors. Unfortunately, there is no such number. (As we can see by adding [[ and 7 in ds ]] to the condition on line 9).

      The intended solution was 2777…77772, and the setter mistakenly determined that this was divisible by 8. In fact the number is divisible by only six of the divisors: 2, 3, 4, 6, 7, 11.

      Like

    • Hugh+Casement 11:33 am on 26 December 2021 Permalink | Reply

      The puzzle could have stated that the number is an integer multiple of seven of the integers 2 to 12 inclusive.

      However, we don’t need the ability to handle huge numbers (or even a computer).

      We can regard the number as jk followed by 498 blocks of kk followed by kj.
      The test for divisibility by 11 involves taking the alternating positive and negative sum of the digits. jk and kj cancel out, and each kk cancels, so the alternating digit sum is 0: the number is an integer multiple of 11 whatever the values of j and k.

      It cannot be a multiple of 10 since we were told the digits are non-zero.

      A test for divisibility by 7 is to take the alternating positive and negative sum of groups of three digits from the right. If and only if the result is a multiple of 7 is the overall number also a multiple of 7.

      In this case we have a number with j on the left, then 332 blocks of kkk, and kkj on the right.
      The even number of blocks cancel out, leaving kkj − j = kk0 = k × 110.
      That is an integer multiple of 7 only if k = 7.

      If the number is not a multiple of 2 then it cannot be a multiple of 4, 6, 8, or 12.
      If it is not a multiple of 3 then it cannot be a multiple of 6, 9, or 12.
      In order to have seven factors in the list it must be a multiple of both 2 and 3.

      If j = 2 the number is an integer multiple of 2 and 4 (but, as Jim says, not 8).

      So now we know j and k.
      We can regard the number as 27 followed by 332 blocks of 777 followed by 72.
      777 is a multiple of 3, so the digit sum is a multiple of 3 and the number is an integer multiple of 3 (but not of 9). Being a multiple of 3 and 4 it is also a multiple of 12.

      The number is not a multiple of 5, 8, or 9 (nor 10).

      Like

  • Jim Randell 12:05 pm on 20 August 2020 Permalink | Reply
    Tags: ,   

    Teaser 2551: Take nothing for granted 

    From The Sunday Times, 14th August 2011 [link]

    In arithmetic, the zero has some delightful properties. For example:

    ANY + NONE = SAME
    X . ZERO = NONE

    In that sum and product, digits have been replaced with letters, different letters being used for different digits. But nothing should be taken for granted: here the equal signs are only approximations as, in each case, the two sides may be equal or differ by one.

    What is your NAME?

    [teaser2551]

     
    • Jim Randell 12:06 pm on 20 August 2020 Permalink | Reply

      We can use the [[ SubstitutedExpression() ]] solver from the enigma.py library to tackle this puzzle. Unfortunately there are two possible solutions.

      The following run file executes in 159ms.

      Run: [ @repl.it ]

      #!/usr/bin/env python -m enigma -r
      
      SubstitutedExpression
      
      "abs(ANY + NONE - SAME) < 2"
      "abs(X * ZERO - NONE) < 2"
      
      --answer="NAME"
      #--answer="SYNONYM" # for a unique answer
      

      Solution: The two possible values for NAME are: 7146 and 7543.

      The possible sums are:

      170 + 7976 = 8146; 6 × 1329 ≈ 7973
      570 + 7973 = 8543; 3 × 2659 ≈ 7976

      In both cases the addition is exact, but the multiplication sums are off by 1.

      If instead, the puzzle had asked the value of SYNONYM the answer would have been unique (= 8079704).

      Like

    • GeoffR 3:07 pm on 20 August 2020 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:A; var 1..9:N; var 0..9:Y; 
      var 0..9:O; var 0..9:E; var 1..9:X;
      var 1..9:Z; var 0..9:R; var 1..9:S;  
      var 0..9:M;
      
      constraint all_different([A,N,Y,O,E,X,Z,R,S,M]);
      
      var 100..999: ANY = 100*A + 10*N + Y;
      var 1000..9999: NONE = 1010*N + 100*O + E;
      var 1000..9999: SAME = 1000*S + 100*A + 10*M + E;
      var 1000..9999: ZERO = 1000*Z + 100*E + 10*R + O;
      var 1000..9999: NAME = 1000*N + 100*A + 10*M + E;
      
      constraint ANY + NONE == SAME \/ANY + NONE == SAME + 1
      \/ ANY + NONE == SAME - 1;
      
      constraint X * ZERO == NONE \/  X * ZERO == NONE + 1
      \/  X * ZERO == NONE - 1;
      
      solve satisfy;
      
      output ["NAME = " ++ show(NAME)
      ++ "\n Sum is " ++ show(ANY) ++ " + " ++ show(NONE) 
      ++ " = " ++ show(SAME)
      ++ "\n Product is " ++ show(X) ++ " * " ++ show(ZERO)
      ++ " = " ++ show(NONE)
      ++ "\n SYNONYM = " ++ show(S),show(Y),show(N),show(O),
      show(N),show(Y),show(M)];
      
      % NAME = 7543
      %  Sum is 570 + 7973 = 8543
      %  Product is 6 * 1329 = 7973
      %  SYNONYM = 8079704
      % ----------
      % NAME = 7146
      %  Sum is 170 + 7976 = 8146
      %  Product is 3 * 2659 = 7976
      %  SYNONYM = 8079704
      % ----------
      % ==========
      
      
      
      

      Like

  • Jim Randell 5:43 pm on 13 August 2020 Permalink | Reply
    Tags:   

    Teaser 2705: In the pub 

    From The Sunday Times, 25th July 2014 [link]

    George and Martha are in the pub. He has ordered a glass of lager for her and some ale for himself. He has written three numbers in increasing order, none involving a zero, then consistently replaced digits by letters to give:

    DRANK
    GLASS
    LAGER

    George explains this to Martha and tells her that the third number is in fact the sum of the previous two. From this information she is able to work out the value of each letter.

    What is the number of George’s ALE?

    [teaser2705]

     
    • Jim Randell 5:43 pm on 13 August 2020 Permalink | Reply

      We can solve this straightforwardly using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file executes in 270ms.

      Run: [ @replit ]

      #! python -m enigma -r
      
      SubstitutedExpression
      
      --digits="1-9"
      --answer="ALE"
      
      "DRANK + GLASS = LAGER"
      "G > D"
      

      Solution: ALE = 392.


      For a faster execution time, we can use the [[ SubstitutedSum() ]] solver to solve the alphametic sum, and then check the solutions it finds to ensure D < G.

      from enigma import (SubstitutedSum, irange, printf)
      
      # all digits are non-zero
      p = SubstitutedSum(['DRANK', 'GLASS'], 'LAGER', digits=irange(1, 9))
      
      for s in p.solve(check=(lambda s: s['D'] < s['G']), verbose=1):
        printf("ALE={ALE}", ALE=p.substitute(s, 'ALE'))
      

      By itself the alphametic sum has 3 solutions (when 0 digits are disallowed), but only one of these has D < G.

      Like

      • Frits 11:26 am on 14 August 2020 Permalink | Reply

        @Jim

        You might also add “D + G == L or D + G + 1 == L” to speed things up.

        Like

        • Jim Randell 2:53 pm on 14 August 2020 Permalink | Reply

          Yes. If we break down the sum into individual columns, with carries of 0 or 1 between them we can use the following run file to solve the problem:

          #! python -m enigma -r
          
          SubstitutedExpression
          
          --answer="{ALE}"
          --distinct="ADEGKLNRS"
          --invalid="0,ADEGKLNRS"
          --invalid="2-9,abcd"
          
          # breaking the sum out into individual columns
          "{D} + {G} + {a} =  {L}"
          "{R} + {L} + {b} = {aA}"
          "{A} + {A} + {c} = {bG}"
          "{N} + {S} + {d} = {cE}"
          "{K} + {S}       = {dR}"
          
          # and G > D
          "{G} > {D}"
          
          --template="(DRANK + GLASS = LAGER) (G > D)"
          

          If we specify the [[ --verbose=32 ]] to measure the internal run-time of the generated code, we find that it runs in 170µs. (The total execution time for the process (which is what I normally report) is 97ms).

          The question is whether the fraction of a second saved in run time is paid off by the amount of additional time (and effort) taken to write the longer run file.

          Like

          • Frits 8:18 pm on 15 August 2020 Permalink | Reply

            @Jim,

            If I use [[ –verbose=32 ]] on your code (with pypy) the internal run-time is 20% of the total execution time (15ms / 75ms). A lot more than you reported.

            I rewrote the generated code.
            It runs approx 3 times faster (elapsed time).

            PS Currently I am more interested in efficient Python code than the easiest/shortest way of programming.

            # breaking the sum out into individual columns
            # "{D} + {G} + {a} =  {L}"
            # "{R} + {L} + {b} = {aA}"
            # "{A} + {A} + {c} = {bG}"
            # "{N} + {S} + {d} = {cE}"
            # "{K} + {S}       = {dR}"
            
            notIn = lambda w: [x for x in range(1,10) if x not in w]
            
            for A in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
              for c in [0, 1]:
                x = 2*A + c; G = x % 10
                if G != A and G in {2,3,4,5,6,7,8}:
                  b = x // 10
                  for D in [1, 2, 3, 4, 5, 6, 7]:
                    if D != A and G > D:
                      for a in [0, 1]:
                        L = D + G + a
                        if L != A and L < 10:
                          for R in notIn({D,G,L,A}):
                            x = R + L + b
                            if x % 10 == A and x // 10 == a:
                              for K in notIn({D,G,L,A,R}):
                                for S in notIn({D,G,L,A,R,K}):
                                  x = K + S
                                  if x % 10 == R:
                                    d = x // 10
                                    for N in notIn({D,G,L,A,R,K,S}):
                                      x = N + S + d
                                      E = x % 10
                                      if E not in {D,G,L,A,R,K,S,N,0}:
                                        if x // 10 == c:
                                          print(" ALE DRANK GLASS LAGER\n",
                                                " ---------------------\n",
                                                A*100+L*10+E,
                                                D*10000+R*1000+A*100+N*10+K,
                                                G*10000+L*1000+A*100+S*11,
                                                L*10000+A*1000+G*100+E*10+R)
             

            Like

          • Jim Randell 10:00 am on 24 March 2021 Permalink | Reply

            I’ve added the [[ SubstitutedExpression.split_sum() ]] method to enigma.py, so you don’t have to split out the columns manually:

            from enigma import SubstitutedExpression
            
            # split the sum into columns
            args = SubstitutedExpression.split_sum("DRANK + GLASS = LAGER")
            
            # none of the symbols in the original sum can be zero
            args.d2i.update((0, x) for x in args.symbols)
            
            # solve the sum, with additional condition: (G > D)
            SubstitutedExpression(
              args.exprs + ["{G} > {D}"],
              distinct=args.symbols,
              d2i=args.d2i,
              answer="{ALE}",
              template=args.template + " ({G} > {D}) ({ALE})",
              solution=args.symbols,
            ).run()
            

            Like

          • Jim Randell 2:55 pm on 22 October 2022 Permalink | Reply

            We can now call the [[ SubstitutedExpression.split_sum ]] solver directly from a run file. Giving us a solution that is both short and fast.

            The following run file executes in 68ms, and the internal runtime of the generated program is just 139µs.

            Run: [ @replit ]

            #! python3 -m enigma -r
            
            SubstitutedExpression.split_sum
            
            --invalid="0,ADEGKLNRS"  # none of the digits are 0
            --answer="ALE"
            
            "{DRANK} + {GLASS} = {LAGER}"
            
            --extra="{G} > {D}"
            

            Like

    • Frits 11:28 am on 14 August 2020 Permalink | Reply

      from enigma import join
      
      print("ALE DRANK GLASS LAGER")
      print("---------------------")
      for G in range(1,10):
        for D in range(1,10):
          if D in {G}: continue
          if G <= D: continue
          for L in range(1,10):
            if L in {G,D}: continue
            if D + G != L and D + G + 1 != L: continue
            for A in range(1,10):
              if A in {G,D,L}: continue
              if A + A != 10 + G and A + A != G and A + A != 9 + G and A + A + 1 != G: continue
              for S in range(1,10):
                if S in {G,D,L,A}: continue
                GLASS = G*10000+L*1000+A*100+S*10+S
                for K in range(1,10):
                  if K in {G,D,L,A,S}: continue
                  for R in range(1,10):
                    if R in {G,D,L,A,S,K}: continue
                    if K + S != 10 + R and K + S != R: continue
                    for N in range(1,10):
                      if N in {G,D,L,A,S,K,R}: continue
                      DRANK = D*10000+R*1000+A*100+N*10+K
                      for E in range(1,10):
                        if E in {G,D,L,A,S,K,R,N}: continue
                        LAGER = L*10000+A*1000+G*100+E*10+R
                        if DRANK + GLASS != LAGER: continue
                        print(join([A,L,E]),join([D,R,A,N,K]),join([G,L,A,S,S]),join([L,A,G,E,R]))
      

      Like

      • Frits 12:52 pm on 14 August 2020 Permalink | Reply

        Fastest solution so far and more concise:

        # range 1,2,..,9 without elements in set w
        notIn = lambda w: [x for x in range(1,10) if x not in w]
        
        print("ALE DRANK GLASS LAGER")
        print("---------------------")
        for D in range(1,10):
          for G in notIn({D}):
            if G < D: continue
            for L in notIn({D,G}):
              if D + G != L and D + G + 1 != L: continue
              for A in notIn({D,G,L}):
                if A + A != 10 + G and A + A != G and \
                   A + A != 9 + G and A + A + 1 != G: continue
                for S in notIn({D,G,L,A}):
                  GLASS = G*10000+L*1000+A*100+S*10+S
                  for K in notIn({D,G,L,A,S}):
                    for R in notIn({D,G,L,A,S,K}):
                      if K + S != 10 + R and K + S != R: continue
                      for N in notIn({D,G,L,A,S,K,R}):
                        DRANK = D*10000+R*1000+A*100+N*10+K
                        for E in notIn({D,G,L,A,S,K,R,N}):
                          LAGER = L*10000+A*1000+G*100+E*10+R
                          if DRANK + GLASS != LAGER: continue
                          print(A*100+L*10+E, DRANK, GLASS, LAGER)
        
        # ALE DRANK GLASS LAGER
        # ---------------------
        # 392 14358 79366 93724
        

        Like

        • Jim Randell 1:12 pm on 14 August 2020 Permalink | Reply

          @Frits: You should be able to do without the loop for E.

          Once you have determined the other letters there is only one possible value for E determined by the fact: DRANK + GLASS = LAGER.

          This is one of the things the [[ SubstitutedExpression() ]] solver does to improve its performance. (See: Solving Alphametics with Python, Part 2.

          Not a huge win in this case (as there is only a single value left to be E), but generally quite useful technique.

          Like

          • Jim Randell 3:31 pm on 14 August 2020 Permalink | Reply

            … and if we use this technique on the equivalent expression:

            LAGERGLASS = DRANK

            We only need to consider the 6 symbols on the left, which gives a much more respectable execution time of 164ms for the following short run file:

            #! python -m enigma -r
            
            SubstitutedExpression
            
            --digits="1-9"
            --answer="ALE"
            
            # instead of: "DRANK + GLASS = LAGER"
            "LAGER - GLASS = DRANK"
            
            "G > D"
            

            Like

    • GeoffR 3:56 pm on 14 August 2020 Permalink | Reply

      The following link is an alphametic solver :

      http://www.tkcs-collins.com/truman/alphamet/alpha_solve.shtml

      (Enter DRANK and GLASS in the left hand box and LAGER in the right hand box)

      It outputs eight solutions, five of whch contain zeroes, which are invalid for this teaser.

      It also outputs three non-zero solutions, only one of which has GLASS > DRANK, which is the answer:
      i.e. 14358 + 79366 = 93724, from which ALE = 392

      Like

    • GeoffR 4:42 pm on 14 August 2020 Permalink | Reply

      import time
      start = time.time()
      
      from itertools import permutations
      
      for Q in permutations('123456789'):
          d,r,a,n,k,l,s,g,e = Q
          drank = int(d+r+a+n+k)
          glass = int(g+l+a+s+s)
          if glass > drank:
              lager = int(l+a+g+e+r)
              if drank + glass == lager:
                  ale = int(a+l+e)
                  print(f"ALE = {ale}")
                  t1 = time.time() - start
                  print(t1, "sec") 
                  print(f"Sum is {drank} + {glass} = {lager}")
      
      # ALE = 392
      # 0.0311 sec
      # Sum is 14358 + 79366 = 93724
      
      
      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:D; var 1..9:R; var 1..9:A; 
      var 1..9:N; var 1..9:K; var 1..9:G;
      var 1..9:L; var 1..9:S; var 1..9:E; 
      
      constraint all_different([D,R,A,N,K,G,L,S,E]);
      
      var 100..999: ALE = 100*A + 10*L + E;
      
      var 10000..99999: DRANK = 10000*D + 1000*R + 100*A + 10*N + K;
      var 10000..99999: GLASS = 10000*G + 1000*L + 100*A + 11*S;
      var 10000..99999: LAGER = 10000*L + 1000*A + 100*G + 10*E + R;
      
      constraint GLASS > DRANK /\ DRANK + GLASS == LAGER;
      
      solve satisfy;
      
      output ["Sum is " ++ show(DRANK) ++ " + " ++ show(GLASS)
      ++ " = " ++ show(LAGER) ++ "\nALE = " ++ show(ALE) ];
      
      % Sum is 14358 + 79366 = 93724
      % ALE = 392
      % time elapsed: 0.04 s
      %----------
      %==========
      
      
      

      Like

    • GeoffR 10:06 pm on 18 August 2020 Permalink | Reply

      % A Solution in MiniZinc - adding columns with carry digits
      include "globals.mzn";
      
      % D R A N K
      % G L A S S +
      %----------
      % L A G E R
      
      var 1..9:d; var 1..9:r; var 1..9:a; 
      var 1..9:n; var 1..9:k; var 1..9:g;
      var 1..9:l; var 1..9:s; var 1..9:e;
      
      var 0..1: carry1; var 0..1: carry2;
      var 0..1: carry3; var 0..1: carry4;
      
      var 100..999: ale = 100*a + 10*l + e;
      
      constraint all_different([d,r,a,n,k,g,l,s,e]);
      
      % adding columns, working from right side
      constraint (k + s) mod 10 == r           % column 1
      /\ (k + s) div 10 == carry1;
      
      constraint (n + s + carry1) mod 10 == e  % column 2
      /\ (n + s + carry1) div 10 == carry2;
      
      constraint (a + a + carry2) mod 10 == g  % column 3
      /\ (a + a + carry2) div 10 == carry3;
      
      constraint (r + l + carry3) mod 10 == a  % colmun 4
      /\ (r + l + carry3) div 10 == carry4;
      
      constraint (d + g + carry4) == l;        % column 5
      
      % number glass > drank
      constraint (10000*g + 1000*l + 100*a + 11*s)
      > (10000*d + 1000*r + 100*a + 10*n + k);
      
      solve satisfy;
      
      output ["ALE = " ++ show(ale) ];
      
      % ALE = 392
      % time elapsed: 0.04 s
      % ----------
      % ==========
      
      
      

      Like

      • John Crabtree 7:32 pm on 20 August 2020 Permalink | Reply

        I solved this by hand. Looking at the first three columns, if NK + SS < 100, by digital roots, D + R + A = 0 mod 9. If NK + SS > 100, D + R + A = 8 mod 9.
        For each condition and each A, G is fixed. Then for each possible D (<G and G + D < 10), R is fixed, and then so is L. I only found four sets of (A, G, D, R and L), ie (3, 6, 1, 5 8), (3, 6, 2, 4, 9), (1, 3, 2, 5, 6) and (3, 7, 1, 4, 9) for which it was necessary to evaluate the other letters.
        Could this be the basis for a programmed solution?

        Like

  • Jim Randell 9:26 am on 6 August 2020 Permalink | Reply
    Tags: by: Ian Duff   

    Teaser 2678: Map snap 

    From The Sunday Times, 19th January 2014 [link]

    I have two rectangular maps depicting the same area, the larger map being one metre from west to east and 75cm from north to south. I’ve turned the smaller map face down, turned it 90 degrees and placed it in the bottom corner of the larger map with the north-east corner of the smaller map touching the south-west corner of the larger map. I have placed a pin through both maps, a whole number of centimetres from the western edge of the larger map. This pin goes through the same geographical point on both maps. On the larger map 1cm represents 1km. On the smaller map 1cm represents a certain whole number of kilometres …

    … how many?

    [teaser2678]

     
    • Jim Randell 9:27 am on 6 August 2020 Permalink | Reply

      See also: Enigma 1177.

      If we suppose the smaller map has a scale of k kilometres per centimetre, then the dimensions of the smaller map are (100 / k) (west to east) and (75 / k) (south to north).

      If the point we are interested on the map has an easting of x and a northing of y (measured from the SW corner of the maps), then we have the following equations:

      k.x = 75 − y
      k.y = 100 − x

      If we consider possible integer values for k, we can determine values for x and y.

      y = 75 − k.x
      k.y = 75k − k².x
      100 − x = 75k − k².x
      x(k² − 1) = 75k − 100
      x = 25 (3k − 4) / (k² − 1)

      So we can look for values of k that give an integer value for x.

      This Python program runs in 49ms.

      Run: [ @replit ]

      from enigma import (irange, div, fdiv, printf)
      
      for k in irange(2, 75):
        x = div(25 * (3 * k - 4), k * k - 1)
        if x is None: continue
        y = fdiv(100 - x, k)
        printf("k={k} x={x} y={y:g}")
      

      Solution: The scale of the smaller map is 1cm = 6km.

      So the smaller map measures 16.67 cm by 12.5 cm.

      The marked point is 10 km from the western edge of the maps, and 15 km from the southern edge of the maps.

      On the larger map the distances are (10cm, 15cm) on the smaller map the distances are (1.67cm, 2.5cm).

      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
%d bloggers like this: