Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:01 am on 2 April 2024 Permalink | Reply
    Tags:   

    Teaser 2614: The end of time 

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

    In the table the letters represent the numbers 1 to 25 in some order. In each row, each column and each main diagonal the sum of the five numbers is the same. If you list all the letters in increasing numerical order then somewhere in the list you will get …, S, U, N, D, A, Y, T, I, M,.. with E coming later.

    Which letters are equal to 11, 9, 7, 3, 23 and 22?

    [teaser2614]

     
    • Jim Randell's avatar

      Jim Randell 9:02 am on 2 April 2024 Permalink | Reply

      The total of the numbers from 1 to 25 is 325. So the magic constant is 325/5 = 65.

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

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --base=26
      --digits="1-25"
      
      # rows are magic
      "A + B + C + D + E == 65"
      "F + G + H + I + J == 65"
      "K + L + M + N + O == 65"
      "P + Q + R + S + T == 65"
      "U + V + W + X + Y == 65"
      
      # columns are magic
      "A + F + K + P + U == 65"
      "B + G + L + Q + V == 65"
      "C + H + M + R + W == 65"
      "D + I + N + S + X == 65"
      "E + J + O + T + Y == 65"
      
      # diagonals are magic
      "A + G + M + S + Y == 65"
      "E + I + M + Q + U == 65"
      
      # look for required subsequence
      "S + 1 = U"
      "U + 1 = N"
      "N + 1 = D"
      "D + 1 = A"
      "A + 1 = Y"
      "Y + 1 = T"
      "T + 1 = I"
      "I + 1 = M"
      "M < E"
      
      --template=""
      

      And here is a wrapper that assembles the required answer(!).

      from enigma import (SubstitutedExpression, rev, join, printf)
      
      # load the alphametic puzzle
      p = SubstitutedExpression.from_file("teaser2614.run")
      
      # solve it
      for s in p.solve(verbose=0):
        r = rev(s)
        # output solution
        vs = [11, 9, 7, 3, 23, 22]
        printf("{vs} -> {ks}", ks=join(r[v] for v in vs))
      

      Solution: The given values spell: ANSWER.

      The grid looks like this:

      And the letters ordered by value are:

      J B W K Q H S U N D A Y T I M O V P C G L R E F X

      Like

      • Frits's avatar

        Frits 10:59 am on 2 April 2024 Permalink | Reply

        @Jim, 5th column equation is same as 5th row equation.

        Like

        • Jim Randell's avatar

          Jim Randell 11:13 am on 2 April 2024 Permalink | Reply

          Thanks. Fixed now.

          I did make a version of the code that derives the expressions from the rows of the square [@replit] (which eliminates mistakes like this), but I posted the literal version as it is more readable.

          Like

    • GeoffR's avatar

      GeoffR 2:22 pm on 2 April 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..25:A;   var 1..25:B;   var 1..25:C;   var 1..25:D;
      var 1..25:E;   var 1..25:F;   var 1..25:G;   var 1..25:H;
      var 1..25:I;   var 1..25:J;   var 1..25:K;   var 1..25:L;
      var 1..25:M;   var 1..25:N;   var 1..25:O;   var 1..25:P;
      var 1..25:Q;   var 1..25:R;   var 1..25:S;   var 1..25:T;
      var 1..25:U;   var 1..25:V;   var 1..25:W;   var 1..25:X;
      var 1..25:Y;
      
      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]);
      
      % ROWS
      constraint A+B+C+D+E == 65 /\ F+G+H+I+J == 65 /\ K+L+M+N+O == 65
      /\ P+Q+R+S+T == 65 /\ U+V+W+X+Y == 65;  % Jim's analysis
      
      % COLUMNS
      constraint  A+F+K+P+U == 65 /\ B+G+L+Q+V == 65 /\ C+H+M+R+W == 65
      /\ D+I+N+S+X == 65 /\ E+J+O+T+Y == 65;
      
      % DIAGONALS
      constraint A+G+M+S+Y == 65 /\ U+Q+M+I+E == 65;
      
      %  S, U, N, D, A, Y, T, I, M are in increasing sequence
       constraint S+1 == U /\ U+1 == N /\ N+1 == D /\ D+1 == A /\ A+1 == Y
      /\ Y+1 == T /\ T+1 ==I /\ I + 1 == M;
      
      constraint M < E; %... with E coming later
      
      solve satisfy;
      
      output[" [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] = " ++
      "\n" ++ show(  [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] ) ++
      "\n" ++ "Grid : " ++ "\n" ++ show ([A,B,C,D,E] )
      ++"\n" ++ show([F,G,H,I,J]) ++ "\n" ++ show( [K,L,M,N,O] )
      ++"\n" ++ show( [P,Q,R,S,T]) ++ "\n" ++ show ( [U,V,W,X,Y]) ];
      
      % [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] =
      % [11, 2, 19, 10, 23, 24, 20, 6, 14, 1, 4, 21, 15, 9, 16, 18, 5, 22, 7, 13, 8, 17, 3, 25, 12]
      % Grid :
      % [11, 2, 19, 10, 23]
      % [24, 20, 6, 14, 1]
      % [4, 21, 15, 9, 16]
      % [18, 5, 22, 7, 13]
      % [8, 17, 3, 25, 12]
      %----------
      %==========
      % By interpolation 11, 9, 7, 3 , 23, 22 = A, N, S, W, E, R
      

      Like

    • Frits's avatar

      Frits 6:09 pm on 3 April 2024 Permalink | Reply

      # make sure loop variable value is not equal to previous ones
      def domain(v, r=range(1, 26)):
        # find already used loop values  ...
        vals = set()
        # ... by accessing previously set loop variable names
        i = 0  # initially <i> (otherwise compiler error)
        for i, s in enumerate(lvd[v], 1):
          val = globals()[s]
          # general domain check
          if not (0 < val < 26): return []
          vals.add(val)
        
        # don't except duplicates in previous variables
        if len(vals) != i:
          return []
       
        return [x for x in r if x not in vals]
      
      # set up dictionary of for-loop variables
      lv = list("NXSUDAYTIMGEQVWBCLOKJFHPR")
      lvd = {v: lv[:i] for i, v in enumerate(lv)}
      
      # D + I + N + S = (N + 1) + (N + 5) + N + (N - 2) = 4N + 4 --> X = 61 - 4N
      for N in domain('N', range(9, 16)):
        X = 61 - 4 * N
        S, U, N, D, A, Y, T, I, M = [N + x for x in range(-2, 7)]
        G = 65 - (A + M + S + Y)
        # E + I + M + Q + U = 65, E = 65 - Q - (I + M + U) and E > M
        # assume E = M + x, x > 0 --> x = 65 - Q - (I + 2M + U) > 0 
        if (I + 2 * M + U) > 63: continue
        # check rest of numbers
        for E in domain('E', range(M + 1, 26)):
          Q = 65 - (E + I + M + U)
          for V in domain('V'):
            W = 65 - (U + V + X + Y)
            for B in domain('B'):
              C = 65 - (A + B + D + E) 
              L = 65 - (B + G + Q + V)
              for O in domain('O'):
                K = 65 - (L + M + N + O)
                J = 65 - (E + O + T + Y)
                for F in domain('F'):
                  H = 65 - (F + G + I + J)
                  P = 65 - (A + F + K + U)
                  for R in domain('R'):
                    if R != 65 - (P + Q + S + T): continue
                    
                    # check remaining equations to be sure we give a correct answer
                    if P + Q + R + S + T != 65: continue
                    if C + H + M + R + W != 65: continue
      
                    vs = [11, 9, 7, 3, 23, 22]
                    nums = [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]
                    chars = "ABCDEFGHIJKLMNOPQRSTUVWXY"
      
                    print(f"answer: {[chars[nums.index(v)] for v in vs]}")     
      

      Like

      • Frits's avatar

        Frits 8:39 pm on 3 April 2024 Permalink | Reply

        line 30 also implies that N is less than 12.

        Like

  • Unknown's avatar

    Jim Randell 6:04 pm on 31 March 2024 Permalink | Reply
    Tags: by: Sir John Cowley   

    Brainteaser 1092: If a man and a half… 

    From The Sunday Times, 10th July 1983 [link]

    I wanted some small alterations done, to my house, so I asked Tom Smith, the local builder, to come and see me.

    I told Tom exactly what the job was and he said he could do it alone, but if he brought his brother Dick (also a trained builder), and his son Harry (an apprentice), the three of them could do the job in half the time that he; Tom, would take if he worked alone.

    Dick could also do the job by himself, but it would take him a day (eight hours) longer than the three of them working together. Young Harry working alone would take six days more than the three of them.

    I wanted the job done quickly, but as there was no room for all three of them to work together, and it was not necessary to employ two trained builders, I decided to employ either Tom and Harry working together, or Dick and Harry working together.

    Exactly how much longer would the second pair take to finish the job than the first pair?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser1092]

     
    • Jim Randell's avatar

      Jim Randell 6:05 pm on 31 March 2024 Permalink | Reply

      Suppose Tom, Dick, Harry can do T, D, H amounts of work per day.

      If we suppose the job consists of 1 unit of work, which all 3 can complete in d days:

      1 = (T + D + H) d

      Tom would take twice as long to do it alone:

      1 = T 2d

      T = 1/(2d)

      Dick would take 1 day longer than all three:

      1 = D (d + 1)

      D = 1/(d + 1)

      And Harry alone would take 6 days longer than all three:

      1 = H (d + 6)

      H = 1/(d + 6)

      And so:

      1/d = T + D + H
      = 1/(2d) + 1/(d + 1) + 1/(d + 6)
      = [(d + 1)(d + 6) + 2d(d + 6) + 2d(d + 1)] / 2d(d + 1)(d + 6)

      2(d + 1)(d + 6) = (d + 1)(d + 6) + (2d² + 12d) + (2d² + 2d)
      d² + 7d + 6 = 4d² + 14d
      3d² + 7d − 6 = 0
      (3d − 2)(d + 3) = 0
      d = 2/3 (= 5h 20m)

      and:

      T = 3/4
      D = 3/5
      H = 3/20

      So the time taken for Tom and Harry (= t1) is:

      t1 = 1 / (T + H)
      T + H = 3/4 + 3/20 = 9/10
      t1 = 10/9

      And the time taken for Dick and Harry (= t2) is:

      t2 = 1 / (D + H)
      D + H = 3/5 + 3/20 = 3/4
      t2 = 4/3

      And the difference:

      t2 − t1 = 4/3 − 10/9 = 2/9 (days)

      Solution: It would take exactly 1 hour, 46 minutes, 40 seconds longer.

      Liked by 1 person

  • Unknown's avatar

    Jim Randell 4:16 pm on 29 March 2024 Permalink | Reply
    Tags:   

    Teaser 3210: Random rabbit 

    From The Sunday Times, 31st March 2024 [link] [link]

    Every year Easter Bunnies must pass a series of demanding tests, the most important being the double jump. Rabbits perform a single jump comprising two hops, with the total distance scoring. For both hops, Max knows he has an equal chance of jumping any distance between two limits. For the first hop, the limits are 80 and 100cm. For his weaker second hop, these limits decrease but keep the same proportion.

    However, the instructors have increased the required standard from 152 to 163cm. Max is worried; he can still pass, but his chance is half what it was before.

    What, as a percentage, is his chance of passing this year?

    This brings the total number of Teaser puzzles posted on the site to 1024 (= 2^10), which is roughly 1/3 of all published Teaser puzzles.

    [teaser3210]

     
    • Jim Randell's avatar

      Jim Randell 5:47 pm on 29 March 2024 Permalink | Reply

      We can consider the possible jumps to be a region of the cartesian plane between x = [80, 100] (= hop 1) and y = [80f, 100f] (= hop 2), where f ∈ (0, 1) gives the reduction for the second hop.

      The probabilities are then calculated from the proportion of this rectangular region that is above the lines x + y = 152 (last year) and x + y = 163 (this year). (Assuming that hops are uniformly distributed).

      This Python program finds the value of f numerically, such that the second area (dark blue) is half the first area (both blues), and then calculates the corresponding probabilities.

      It runs in 71ms. (Internal runtime is 179µs).

      Run: [ @replit ]

      from enigma import (find_value, fdiv, sq, inf, printf)
      
      # find area where x + y > t
      def A(t, f):
        # where does x + y = t hit y = 80f and 100f
        (x1, x2) = (t - 80 * f, t - 100 * f)
        if x1 < 80:
          # entire area = 20 * 20f
          return 400 * f
        if x2 < 80:
          # remove bottom left corner
          return 400 * f - 0.5 * sq(x1 - 80)
        if x1 < 100:
          # trapezium
          return 20 * f * (10 * f + 100 - x1)
        if x2 < 100:
          # top left corner
          return 0.5 * sq(100 - x2)
        # otherwise, zero area
        return 0
      
      # find ratio of areas for the two scenarios
      def fn(f):
        # area for t = 152
        A1 = A(152, f)
        if A1 == 0: return inf
        # area for t = 163
        A2 = A(163, f)
        # return the ratio A2/A1
        return fdiv(A2, A1)
      
      # find when fn(f) = 0.5, for f in the interval (0, 1)
      r = find_value(fn, 0.5, 0.0, 1.0)
      f = r.v
      
      # calculate probabilities
      T = 400 * f  # total area
      P1 = fdiv(A(152, f), T)
      P2 = fdiv(A(163, f), T)
      
      # output solution
      printf("{P1:.1%} -> {P2:.1%} [f={f:.2f}]")
      

      Solution: Max’s chance of passing this year is 45%.

      The value of f is 0.8, so the range for the second hop is 64 – 80 cm.

      This gives a total area of the rectangle as: 20 × 16 = 320.

      For last year we want the area of the rectangle above the line (80, 72) – (88, 64) which gives: 288.

      For this year we want the area above the line (83, 80) – (99, 64) which gives: 144.

      And so the probability this year (144/320 = 45%) is exactly half of the probability last year (288/320 = 90%).

      Like

    • Frits's avatar

      Frits 7:38 pm on 29 March 2024 Permalink | Reply

      For the time being an approximation, less than half a second with PyPy.
      Starts to slow down quickly when increasing the precision.

      from itertools import product
      
      lmts = [80, 100]
      stds = {2023: 152, 2024: 163}
      fr, to = 52, 79 
      done = 0
      
      m = 1 # multiplication factor
      while not done:
        lmts = [10 * x if m > 1 else x for x in lmts]
        rng1 = range(lmts[0], lmts[1] + 1)
        
        # a = lower limit for 2nd jump
        for a in range(to, fr - 1, -1):
          rng2 = [a * x / lmts[0] for x in rng1]
          # chance of passing per year
          P2023 = sum(x + y >= m * stds[2023] for x, y in product(rng1, rng2)) / (len(rng1) * len(rng2))
          P2024 = sum(x + y >= m * stds[2024] for x, y in product(rng1, rng2)) / (len(rng1) * len(rng2))
          # check if we are close to 50% 
          if abs(P2024 / P2023 - 0.500) <= 1e-3:
            print(f"answer: approximately {round(100 * P2024, 1)}%")
            done = 1
            break
      
          if P2024 / P2023 < 0.5: break  
        
        # try to get closer to 50,00% by increasing the ranges by 10 
        m *= 10
        (fr, to) = (10 * a, 10 * (a + 1))      
      

      Like

      • Frits's avatar

        Frits 2:50 pm on 30 March 2024 Permalink | Reply

        More efficient.

        from math import ceil
         
        lmts = [80, 100]
        stds = {2023: 152, 2024: 163}
        # if 2nd hop is below 52 we can never reach 152
        fr, to = 52, 79
        done = 0
        
        # calculate the chance if we pick one element from r1 and r2 that
        # their sum is greater or equal <std> 
        def calcP(r1, r2, std):
          miss, strt = 0, 0
          ln1, ln2 = len(rng1), len(rng2)
          df1, df2 = rng1[1] - rng1[0], rng2[1] - rng2[0]
          # factor f: f * df2 >= df1 --> f >= df1 / df2
          f = ceil(df1 / df2)
          r2rev = rng2[::-1]
        
          # if x1 + x2 is lower than <std> for this x2 then x1 + (x2 + df2) >= std
          # --> (x1 - df1) + (x2 + df2 + f * df2) >= std  (as f * df2 >= df1)
          # meaning: for the next x1 iteration we can select a subset of rng2
          
          # loop from the high value to low value
          for x1 in rng1[::-1]:
            for j, x2 in enumerate(r2rev[strt:], start=strt):
              if x1 + x2 < std:
                strt = j - f if j - f >= 0 else j - 1 
                # increment number of combinations below the standard
                miss += (ln2 - j)
                # for the next x1 we can start checking x2 as of (x2 + k * df2)
                break
            else:
              continue
              
            # a break has occurred  
            if j == 0:
              # for lower x1's add maximum missing number (ln2)
              miss += (ln2 * (x1 - rng1[0]))
              break
         
          return ((ln1 * ln2) - miss) / (ln1 * ln2)       
          
         
        m = 1 # multiplication factor
        while not done:
          lmts = [10 * x if m > 1 else x for x in lmts]
          rng1 = range(lmts[0], lmts[1] + 1)
           
          # a = lower limit for 2nd jump
          for a in range(to, fr - 1, -1):
            rng2 = [a * x / lmts[0] for x in rng1]
            # chance of passing per year
            P2023 = calcP(rng1, rng2, m * stds[2023]) 
            P2024 = calcP(rng1, rng2, m * stds[2024])
            
            # check if we are close to 50% 
            if abs(P2024 / P2023 - 0.5) <= 1e-4:
              print(f"answer: approximately {round(100 * P2024, 2)}%")
              done = 1
              break
         
            if P2024 / P2023 < 0.5: break 
            
          # try to get closer to 50,00% by increasing the elements by 10
          m *= 10
          (fr, to) = (10 * a, 10 * (a + 1))       
        

        PyPy is a lot faster than CPython. I don’t recall a program where PyPy was more than 17 times quicker.

        D:\Python>pypy RandomRabbit1D.py
        answer: approximately 45.0%
        [temp.py] total time: 1.4625589s (1.46s)
        D:\Python>pyth RandomRabbit1D.py
        answer: approximately 45.0%
        [temp.py] total time: 25.6650562s (25.67s)  
        

        Like

        • Frits's avatar

          Frits 12:11 pm on 1 April 2024 Permalink | Reply

          line 24 can be done more efficiently.

               
          for x1 in [x for x in rng1 if x < std - r2[-0]][::-1]:
          

          The program runs now in 200ms/500ms for PyPy/CPython.

          Like

  • Unknown's avatar

    Jim Randell 10:35 am on 28 March 2024 Permalink | Reply
    Tags:   

    Teaser 2636: Simple Easter Teaser 

    From The Sunday Times, 31st March 2013 [link] [link]

    I have written down three numbers, at least one of which is odd. Furthermore, one of the three is the sum of the other two. Then I have consistently replaced digits by letters, using different letters for different digits, and the numbers have become:

    SIMPLE
    EASTER
    TEASER

    What number is EASTER?

    [Incidentally, Easter Sunday 2013 has a palindromic date, 31/3/13. Interestingly, this happens next in 2031].

    This puzzle completes the archive of puzzles originally published in 2013.

    [teaser2636]

     
    • Jim Randell's avatar

      Jim Randell 10:36 am on 28 March 2024 Permalink | Reply

      At least one of the numbers is odd, so at least one of E or R must be odd.

      We can check that one of the numbers is the sum of the other two by adding all of them together, and then this total must be twice one of the original numbers:

      SIMPLE + EASTER + TEASER ∈ { 2×SIMPLE, 2×EASTER, 2×TEASER }

      So the LHS must be an even number, and if both E and R were odd, the LHS would be an odd number, so only one of them can be odd. And if E is odd then the LHS is also odd, hence E must be even and R must be odd.

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

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      "SIMPLE + EASTER + TEASER in { 2 * SIMPLE, 2 * EASTER, 2 * TEASER }"
      
      # E is even, R is odd
      "E % 2 = 0"
      "R % 2 = 1"
      
      --answer="EASTER"
      

      Solution: EASTER = 278521.

      And the sum is:

      EASTER + TEASER = SIMPLE → 278521 + 527821 = 806342

      There is a further solution to this alphametic sum where all the terms are even:

      EASTER + TEASER = SIMPLE → 417342 + 341742 = 759084


      For a faster program we can consider the three possible sums (which can be solved using the [[ SubstitutedExpression.split_sum() ]] solver.

      The following Python program runs in 58ms. (Internal runtime is 8.6ms).

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, delete, sprintf, join, printf)
      
      # words involved in the alphametic
      words = ['SIMPLE', 'EASTER', 'TEASER']
      
      # try each word as the result
      for (i, result) in enumerate(words):
        expr = sprintf("{terms} = {result}", terms=join(delete(words, [i]), sep=" + "))
        # construct the alphametic puzzle
        p = SubstitutedExpression.split_sum(
          expr,
          extra=["(E % 2 != 0) or (R % 2 != 0)"],  # at least one is odd
        ).solver()
        # solve the puzzle
        for s in p.solve(verbose=0):
          # output solution
          printf("{expr} / {s}", s=p.substitute(s, expr))
      

      Like

    • GeoffR's avatar

      GeoffR 1:47 pm on 28 March 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:S; var 0..9:I; var 0..9:M; var 0..9:P; var 0..9:L;
      var 1..9:E; var 0..9:A; var 1..9:T; var 1..9:R;
      
      constraint all_different ([S, I, M, P, L, E, A, T, R]);
      
      % At least one of three numbers is odd.
      constraint sum([E mod 2 == 1, R mod 2 == 1]) > 0;
      
      var 100000..999999:SIMPLE == 100000*S + 10000*I + 1000*M + 100*P + 10*L + E;
      var 100000..999999:EASTER == 100000*E + 10000*A + 1000*S + 100*T + 10*E + R;
      var 100000..999999:TEASER == 100000*T + 10000*E + 1000*A + 100*S + 10*E + R;
      
      %  One of the three numbers is the sum of the other two
      constraint SIMPLE == EASTER + TEASER
      \/ EASTER == SIMPLE + TEASER
      \/ TEASER == SIMPLE + EASTER;
      
      solve satisfy;
      
      output[ "EASTER = " ++ show(EASTER) 
      ++ "\n" ++ "SIMPLE = " ++ show(SIMPLE) 
      ++ "\n" ++ "TEASER = " ++ show(TEASER) ];
      
      % EASTER = 278521
      % SIMPLE = 806342
      % TEASER = 527821
      % ----------
      % ==========
      % So SIMPLE = EASTER + TEASER
      

      Like

    • Frits's avatar

      Frits 9:13 pm on 28 March 2024 Permalink | Reply

        
      from itertools import permutations
      
      # EASTER
      # TEASER     only valid sum ot the three sums (otherwise E = 0)
      # ------ +
      # SIMPLE
      
      # A can't be zero as it would also lead to M = 0
      for R, E, S, T in permutations(range(1, 10), 4):
        # basic checks (R must be odd)
        if not ((2 * R) % 10 == E and R % 2 and E + T in {S - 1, S}): continue
      
        if (L := (2 * E + (R > 5)) % 10) in {R, E, S, T}: continue
        if (P := (S + T + (E > 4)) % 10) in {R, E, S, T, L}: continue
        
        for A in set(range(1, 10)).difference({R, E, S, T, L, P}):
          EASTER = int(''.join(str(x) for x in [E, A, S, T, E, R]))
          TEASER = int(''.join(str(x) for x in [T, E, A, S, E, R]))
          
          if (SIMPLE := EASTER + TEASER) // 100000 != S: continue
          IM = (SIMPLE // 1000) % 100
          if any(int(x) in {R, E, S, T, L, P, A} for x in str(IM)): continue
          print("answer:", EASTER)   
      

      Like

  • Unknown's avatar

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

    Teaser 2674: Roll model 

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

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

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

    [teaser2674]

     
    • Jim Randell's avatar

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

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

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

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

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

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

      Run: [ @replit ]

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

      This gives no solutions.

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

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

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

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

      This gives no solutions.

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

      And we have found no further candidate solutions.

      Like

      • Hugo's avatar

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

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

        Like

        • Jim Randell's avatar

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

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

          Like

          • Lise Andreasen's avatar

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

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

            Like

    • Frits's avatar

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

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

      Like

  • Unknown's avatar

    Jim Randell 10:03 am on 24 March 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 351: Birthday party 

    From The Sunday Times, 28th January 1968 [link]

    Some years ago the Bell family were holding their usual annual special birthday party. Four members of the family, of four different generations, had birthdays on the same day of the year. They were old Adam, his son Enoch, Enoch’s son Joseph and Joseph’s son David. On this occasion David remarked that the sum of any three of their four ages was a perfect square.

    Some years later old Adam died on his birthday, but it so happened that on the very same day David’s son Samuel was born, and the annual party was continued in subsequent years.

    In 1967 at the usual party Samuel made exactly the same remark that David had made, on the previous occasion.

    In what year did Adam die and how old was he then?

    (Perhaps I should mention that no one survived to be 100!).

    This puzzle is included in the book Sunday Times Brain Teasers (1974).

    [teaser351]

     
    • Jim Randell's avatar

      Jim Randell 10:03 am on 24 March 2024 Permalink | Reply

      This Python program considers possible potential gaps between generations, and then looks for ages that makes any subset of size 3 sum to a square. Once we have found potential candidates we look for a pair with suitable overlapping gaps.

      It runs in 221ms. (Internal runtime is 144ms).

      Run: [ @replit ]

      # generate possible (<gaps>, <ages>)
      def generate():
        # suppose the gaps between generations are in the range [15, 40]
        # which means the sum of the 3 gaps lies between 45 and 97
        for g in irange(45, 97):
          for (p, q, r) in decompose(g, 3, increasing=0, sep=0, min_v=15, max_v=40):
            # consider lowest age
            for a in irange(1, 98 - g):
              b = a + p
              c = b + q
              d = c + r
              t = a + b + c + d
              if not all(is_square(t - x) for x in (a, b, c, d)): continue
              printf("[({r}, {q}, {p}) -> ({d}, {c}, {b}, {a})]")
              yield ((r, q, p), (d, c, b, a))
      
      # look for gaps (p, q, r) -> (q, r, s)
      for (((p, q, r), (A1, E1, J1, D1)), ((q_, r_, s), (E2, J2, D2, S2))) in subsets(generate(), size=2, select='P'):
        if not (q_ == q and r_ == r and E2 > E1): continue
        # event 2 is in 1967
        y2 = 1967
        y1 = y2 - (E2 - E1)
        printf("{y1} (A={A1} E={E1} J={J1} D={D1}) -> {y2} (E={E2} J={J2} D={D2} S={S2})")
        b = y1 - A1  # A's birth year
        d = y2 - S2  # A's death year = S's birth year
        a = d - b # A's age at death
        printf("-> A born {b}; died {d}, aged {a}")
      

      Solution: Adam died in 1953, on his 96th birthday.


      There are only three viable candidate lists:

      (58, 41, 22, 1) with gaps of (17, 19, 21)
      (78, 57, 34, 9) with gaps of (21, 23, 25)
      (89, 66, 41, 14) with gaps of (23, 25, 27)

      An age of 1 might be a little young to be making observations, but in any case only the final two candidates have suitable overlapping gaps.

      So, at the first event we have:

      Adam = 78; Enoch = 57; Joseph = 34; David = 9

      And at the second event:

      Enoch = 89; Joseph = 66; David = 41; Samuel = 14

      This is 32 years later than the first event, and as it happened in 1967 the first event was in 1935.

      So Adam was born in 1857, and Samuel was born in 1953, the same year Adam died. So Adam died on his 96th birthday.

      Like

    • Frits's avatar

      Frits 12:40 pm on 24 March 2024 Permalink | Reply

      Faster and probably easier to understand.
      I have also not put in an age restriction for the youngest family member to make observations.

       
      from enigma import SubstitutedExpression, subsets
      
      # invalid digit / symbol assignments
      d2i = dict()
      for d in range(0, 100):
        vs = set() 
        if d > 69: vs.update('A')
        # not excluding young fathers (with such names)
        for i in range(4):
          if not (10 * i <= d < 10 * (i + 7)): vs.update('ABCD'[i])
        d2i[d] = vs  
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # increasing ages A, B, C and D
          "B > (A + 10)",
          "C > (B + 10)", 
          "is_square(A + B + C)",
          "D > (C + 10)", 
          # the sum of any three of their four ages was a perfect square
          "all(is_square(x + y + z) for x, y, z in subsets(@ages, 3))"
        ],
        base=100,
        macro=dict([("ages", "[D, C, B, A]")]),
        answer="@ages",
        d2i=d2i,
        distinct="",
        verbose=0,    # use 256 to see the generated code
      )
      
      # a,     e,     j,     d
      # e + n, j + n, d + n, s 
      for (a, e, j, d), (e2, j2, d2, s)  in subsets(p.answers(), 2, select="P"):
        # same age increase for Enoch, Joseph and David
        if len(set([e2 - e, j2 - j, d2 - d])) != 1: continue
        n = e2 - e
        # some years later old Adam died on his birthday
        if s >= n - 2: continue
        print(f"answer: Adam died in {1967 - s} at the age of {a + n - s}")    
      

      Like

      • Jim Randell's avatar

        Jim Randell 1:34 pm on 24 March 2024 Permalink | Reply

        @Frits: Yes, it a good idea to check three of the values sum to a square before moving on to the fourth.

        This Python program runs in 70ms. (Internal runtime is 11ms).

        Run: [ @replit ]

        from enigma import (irange, powers, decompose, subsets, is_square, singleton, printf)
        
        # generate possible (<gaps>, <ages>)
        def generate():
          # choose possible squares for b + c + d
          for ta in powers(10, 15):
            for (b, c, d) in decompose(ta, 3, increasing=1, sep=15, min_v=16, max_v=99):
              # find values for a
              for a in irange(1, b - 15):
                t = ta + a
                if not all(is_square(t - x) for x in (b, c, d)): continue
                printf("[ages = ({d}, {c}, {b}, {a})]")
                yield (d, c, b, a)
        
        # look for gaps (p, q, r) -> (q, r, s)
        for ((A1, E1, J1, D1), (E2, J2, D2, S2)) in subsets(generate(), size=2, select='P'):
          g = singleton({E2 - E1, J2 - J1, D2 - D1})
          if g is None or not (g > 0): continue
          # event 2 is in 1967
          y2 = 1967
          y1 = y2 - g
          printf("{y1} (A={A1} E={E1} J={J1} D={D1}) -> {y2} (E={E2} J={J2} D={D2} S={S2})")
          b = y1 - A1  # A's birth year
          d = y2 - S2  # A's death year = S's birth year
          a = d - b # A's age at death
          if not (d > y1 and a < 100): continue
          printf("-> A born {b}; died {d}, aged {a}")
        

        Like

      • Frits's avatar

        Frits 2:21 pm on 24 March 2024 Permalink | Reply

        It is getting more and more annoying to have to do an import or specify a function for a normal operation like ceil.

        This program performs the same as Jim’s 2nd program and seems to have more requirements checks than Jim.

        @Jim, it is not immediately clear to me from your code that Adam didn’t die as a centenarian or that Samuels’ age isn’t too high.

        from itertools import permutations
        from math import ceil
        
        # minimal difference between generations
        diff = 15
        mxA = 98   # if Adam dies one year after the party he still is only 99
        
        d6 = 6 * diff
        sqs = set(i * i for i in range(ceil(d6**.5), int((4 * mxA - d6)**.5) + 1))
        
        sols = []
        for A in range(1, mxA - 3 * diff + 1):
          for B in range(A + diff, mxA - 2 * diff + 1):
            for C in range(B + diff, mxA - diff + 1):
              if (A + B + C) not in sqs: continue
              for D in range(C + diff, mxA + 1):
                # borrowed from Jim
                if any((A + B + C + D - x) not in sqs for x in [D, C, B, A]): 
                  continue
                sols.append([D, C, B, A])
        
        # a,     e,     j,     d
        # e + n, j + n, d + n, s 
        for (a, e, j, d), (e2, j2, d2, s) in permutations(sols, 2):
          # same age increase for Enoch, Joseph and David
          if len(set([e2 - e, j2 - j, d2 - d])) != 1: continue
          n = e2 - e
          # some (1 or more) years later Adam died on his birthday and 
          # no one (read Adam) survived to be 100
          if s >= n or a + n - s > 99: continue
          print(f"answer: Adam died in {1967 - s} at the age of {a + n - s}")     
        

        Like

        • Jim Randell's avatar

          Jim Randell 2:59 pm on 24 March 2024 Permalink | Reply

          @Frits: Yes, for completeness we can add a check to ensure a < 100 and d > y1.

          Fortunately it doesn’t remove the single candidate solution.

          Like

      • Frits's avatar

        Frits 8:29 pm on 24 March 2024 Permalink | Reply

        Inspired by Brian. Fastest approach sofar (5ms with PyPy).

             
        from itertools import combinations
        
        # minimal difference between generations
        diff = 15
        
        sqs = [sq for n in range(1, 20) 
               if 3 * (diff + 1) < (sq := n * n) <= 3 * (99 - diff)]
        
        # find sets of four different values, all less
        # than 100, for which any three add to a square
        quads = []
        # pick four squares (a + b + c, a + b + d, a + c + d, b + c + d)
        for sq1, sq2, sq3, sq4 in combinations(sqs, 4):
          a, r = divmod(sq1 + sq2 + sq3 - 2 * sq4, 3)
          if r or a < 1: continue
          
          if (d := sq4 - sq1 + a) > 99: continue
          b, c = sq2 - a - d, sq3 - a - d
        
          # check difference between generations
          if any(y - x < diff for x, y in zip((a, b, c), (b, c, d))): continue
           
          quads.append((a, b, c, d))
        
        # consider the two events mentioned
        for p in quads:
          for q in quads:
            if p == q:
              continue
            #               ages in 1967 
            (d, j, e, a), (s, d_, j_, e_) = p, q
            # the age gap between the two events must 
            # be the same for David, Joseph and Enoch
            if len({d_ - d, j_ - j, e_ - e}) == 1:
              gap = e_ - e
              if s < gap and (ad := a + gap - s) < 100:
                print(f"Adam died in {1967 - s} at age {ad}.")   
        

        Like

        • Jim Randell's avatar

          Jim Randell 10:01 pm on 24 March 2024 Permalink | Reply

          Starting from the squares is an even better idea.

          If we have four ages (a, b, c, d) (in ascending order), and each 3-subset sums to a square number, we have:

          a + b + c = u²
          a + b + d = v²
          a + c + d = w²
          b + c + d = x²

          The squares also appear in ascending order, and the difference between consecutive squares is the difference between consecutive ages.

          Then by summing the equations we get:

          t = a + b + c + d = (u² + v² + w² + x²) / 3

          And given the squares we can recover the ages:

          a = t − x²
          b = t − w²
          c = t − v²
          d = t − u²

          Here’s my version. It has an internal runtime of 349µs.

          Run: [ @replit ]

          from enigma import (powers, sqrtc, sqrtf, subsets, div, singleton, printf)
          
          # generate possible ages
          def generate():
            g = 15  # min generation gap
            # find possible sets of 4 squares
            m = 3 + 3*g
            for (u2, v2, w2, x2) in subsets(powers(sqrtc(m), sqrtf(300 - m)), size=4):
              # check generation gaps
              if v2 - u2 < g or w2 - v2 < g or x2 - w2 < g: continue
              # 3(a + b + c + d) = u^2 + v^2 + w^2 + x^2
              t = div(u2 + v2 + w2 + x2, 3)
              if t is None: continue
              # calculate (a, b, c, d)
              (a, b, c, d) = (t - x2, t - w2, t - v2, t - u2)
              if not (0 < a < b < c < d < 100): continue
              printf("[ages = ({d}, {c}, {b}, {a})]")
              yield (d, c, b, a)
          
          # look for ages at the 2 events
          for ((A1, E1, J1, D1), (E2, J2, D2, S2)) in subsets(generate(), size=2, select='P'):
            # gap between events
            g = singleton({E2 - E1, J2 - J1, D2 - D1})
            if g is None or not (g > 0): continue
            # event 2 is in 1967
            y2 = 1967
            y1 = y2 - g
            printf("{y1} (A={A1} E={E1} J={J1} D={D1}) -> {y2} (E={E2} J={J2} D={D2} S={S2})")
            b = y1 - A1  # A's birth year
            d = y2 - S2  # A's death year = S's birth year
            # check timeline
            if not (y1 + 1 < d < 100 + b): continue
            # output solution
            printf("-> A born {b}; died {d}, aged {a}", a=d - b)
          

          Like

  • Unknown's avatar

    Jim Randell 4:39 pm on 22 March 2024 Permalink | Reply
    Tags:   

    Teaser 3209: All in order 

    From The Sunday Times, 24th March 2024 [link] [link]

    Audley’s age is a two-figure number. He has that number of cards and on them he has spelt out the consecutive numbers from one up to and including his age, (“one”, “two”, etc) with one number on each card.

    Then he has arranged the cards in a row in alphabetical order. It turns out that two of the numbers are in the “correct” place (i.e. in the same place as if he had arranged the cards in numerical order).

    If he had done all this a year ago, or if he repeated this whole exercise in a year’s time, there would be no card in the correct place.

    How old is he?

    [teaser3209]

     
    • Jim Randell's avatar

      Jim Randell 4:51 pm on 22 March 2024 Permalink | Reply

      This Python program generates (age, correct) pairs for increasing ages, and then looks for a sequence of (0, 2, 0) occurring in the correct values, to find the required age.

      It runs in 58ms. (Internal runtime is 585µs).

      Run: [ @replit ]

      from bisect import insort
      from enigma import (irange, inf, int2words, tuples, unzip, printf)
      
      # generate (<age>, <correct>) values for increasing ages
      def generate(m=inf):
        # numbers in order (numerically and alphabetically)
        (num, lex) = (list(), list())
        for n in irange(1, m):
          # insert the next number
          w = int2words(n)
          num.append(w)
          insort(lex, w)
          # how many are in the correct position?
          p = sum(x == y for (x, y) in zip(lex, num))
          yield (n, p)
      
      # find triples of (<age>, <correct>) with <correct> = 0, 2, 0
      for ts in tuples(generate(100), 3):
        (ages, ps) = unzip(ts)
        if ps == (0, 2, 0) and 9 < ages[1] < 100:
          printf("{ages} -> {ps}")
      

      Solution: Audley is 87.

      The two numbers in the correct position are “eleven” and “sixty two”.

      The next time it would happen is when Audley is 172.

      Like

      • Jim Randell's avatar

        Jim Randell 8:39 am on 24 March 2024 Permalink | Reply

        Or a version that considers ages in decreasing order:

        It runs in 63ms. (Internal runtime is 517µs).

        Run: [ @replit ]

        from enigma import (irange, int2words, tuples, unzip, printf)
        
        # generate (<age>, <correct>) values for decreasing ages
        def generate(n):
          # numbers in alphabetical order
          lex = sorted(irange(1, n), key=int2words)
          # consider decreasing ages
          while lex:
            # how many in the correct position
            k = sum(x == i for (i, x) in enumerate(lex, start=1))
            yield (n, k)
            lex.remove(n)
            n -= 1
        
        # find triples of (<age>, <correct>) with <correct> = 0, 2, 0
        for ts in tuples(generate(100), 3):
          (ages, ps) = unzip(ts)
          if ages[1] < 10: break
          if ps == (0, 2, 0):
            printf("{ages} -> {ps}")
        

        Like

    • Frits's avatar

      Frits 5:25 pm on 22 March 2024 Permalink | Reply

      Used some code from [ https://s2t2.home.blog/2021/04/15/teaser-2786-out-of-order/ ]

       
      from bisect import insort
      
      upto19 = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven',
                'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen',
                'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen',
                'nineteen']
      asof20 = ['twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy',
                'eighty', 'ninety']
      
      # convert integer to word
      i2w = lambda i: "one hundred" if i == 100 else upto19[i] if i < 20 else \
                      asof20[(i - 20) // 10] + " " + upto19[(i - 20) % 10]
                     
      n_order = [i2w(n) for n in range(1, 9)]
      a_order = sorted(n_order)
      last3 =[-1, -1, -1]
      
      # also consider two ages outside Audley's age boundaries
      for a in range(9, 101):
        # age as a word
        w = i2w(a)
        # cards in numerical order
        n_order.append(w)
        # insert into a list in alphabetical order
        insort(a_order, w)
      
        # numbers in the "correct" place
        n_correct = sum(x == y for x, y in zip(n_order, a_order))
        if (last3 := last3[1:] + [n_correct]) == [0, 2, 0]:
          print(f"answer: {a - 1}")     
      

      Like

      • Frits's avatar

        Frits 11:59 am on 23 March 2024 Permalink | Reply

        First item of ‘upto19’ can also be set to ‘\b’ (backspace) to remove the trailing blank for numbers 20, 30, 40, …

        Like

  • Unknown's avatar

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

    Brainteaser 1647: Happy Easter 

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

    Here is a sum with an odd total in which digits have been consistently replaced by letters, different letters being used for different digits:

    Find the value of EGGS.

    [teaser1647]

     
    • Jim Randell's avatar

      Jim Randell 10:27 am on 21 March 2024 Permalink | Reply

      Using the [[ SubstitutedExpression ]] solver from the enigma.py library.

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      # the alphametic sum
      "DEAR + READER + HAPPY = EASTER"
      
      # the total is odd
      --extra="R % 2 = 1"
      
      --answer="EGGS"
      

      Solution: EGGS = 4882.

      The sum is: 1403 + 340143 + 60997 = 402543.

      Which assigns 9 of the ten digits, leaving G = 8.

      Like

    • GeoffR's avatar

      GeoffR 7:03 pm on 21 March 2024 Permalink | Reply

      from itertools import permutations
      digits = set('0123456789')
      
      for R in (1,3,5,7,9):
          r = str(R)
          q1 = digits - {r}
          for p1 in permutations(q1, 3):
              d, e, a = p1
              dear = int(d + e + a + r)
              reader = int(r + e + a + d + e + r)
              q2 = q1 - set(p1)
              for p2 in permutations(q2, 3):
                  h, p, y = p2
                  happy = int(h + a+ p + p + y)
                  EASTER = dear + reader + happy
                  easter = str(EASTER)
                  if easter[0] == e and easter[4] == e:
                      if easter[1] == a and easter[-1] == r:
                         s, t = easter[2], easter[3]
                         used = set((e, a, s, t, r, d, h, p, y))
                         if len(used) == 9:
                             g = digits - used
                             G = int(g.pop())
                             E, S = int(e), int(s)
                             EGGS = 1000*E + 110*G +  S
                             print(f"EGGS = {EGGS}")
                             print(f"Sum: {dear} + {reader} + {happy} = {EASTER}")
      # EGGS = 4882           
      # Sum: 1403 + 340143 + 60997 = 402543
      

      Like

    • Frits's avatar

      Frits 11:11 am on 22 March 2024 Permalink | Reply

      from functools import reduce
      
      # convert digits sequence to number
      d2n = lambda s: reduce(lambda x,  y: 10 * x + y, s)
      
      dgts = set(range(10))
      
      # R is odd as total is odd but not 5 (Y becomes 5) or 9 (E becomes 10)
      for R in [1, 3, 7]:
        if len(q1 := dgts - {R, E := R + 1, Y := 10 - R}) != 7: continue
        for A in q1:
          # carry + E + H = 10 + A --> A < carry + E
          if not (A < 2 + E): continue
         
          if len(q2 := q1 - {A, P := 9 - A}) != 5: continue
          APPY = d2n([A, P, P, Y])
          for D in q2:
            if D == 0: continue
            DEAR = d2n([D, E, A, R])
            READER = d2n([R, E, A, D, E, R])
         
            # sum last 4 columns
            carry, tot = divmod(DEAR + READER % 10000 + APPY, 10000)
            # carry + E + H = 10 + A
            H = 10 + A - carry - E
            if H in {0, D} or H not in q2: continue
           
            # tot must be equal to STER
            S, T = divmod(tot // 100, 10)
         
            if len(q3 := q2 - {D, H, S, T}) != 1: continue
           
            HAPPY = 10000 * H + APPY
            EASTER = d2n([E, A, S, T, E, R])
            if DEAR + READER + HAPPY != EASTER: continue
      
            print(f"answer: EGGS = {d2n([E, (G := q3.pop()), G, S])}")
            '''
            print()
            print(f"{DEAR:>6}")
            print(f"{READER:>6}")
            print(f"{HAPPY:>6}")
            print("------")
            print(f"{EASTER:>6}")
            '''
      

      Like

    • Ruud's avatar

      Ruud 6:42 am on 21 April 2024 Permalink | Reply

      Brute force, extremely simple, solution. Runs within 30 seconds …

      import itertools
      from istr import istr
      
      for d, e, a, r, h, p, y, s, t,g in istr(itertools.permutations("0123456789", 10)):
          if r.is_odd() and (d | e | a | r) + (r | e | a | d | e | r) + (h | a | p | p | y) == (e | a | s | t | e | r):
              print('eggs = ', e|g|g|s)
      

      Like

      • Frits's avatar

        Frits 10:55 am on 22 April 2024 Permalink | Reply

        Hi Ruud, with CPython your version runs for 140seconds on my PC. A similar program without the istr package runs for 3 seconds with CPython (PyPy is faster).

        from itertools import permutations
         
        for d, e, a, r, h, p, y, s, t, g in permutations("0123456789"):
          if (r in "13579" and "0" not in (d + r + h + e) and
              int(d+e+a+r) + int(r+e+a+d+e+r) + int(h+a+p+p+y) == int(e+a+s+t+e+r)):
            print('eggs = ', e+g+g+s)     
        

        Like

    • GeoffR's avatar

      GeoffR 11:10 am on 21 April 2024 Permalink | Reply

      Using primary school addition method of columns and carry digits.

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:D; var 1..9:E; var 0..9:A; var 1..9:R;
      var 0..9:S; var 0..9:T; var 0..9:G; var 1..9:H;
      var 0..9:P; var 0..9:Y;
      var 1000..9999:EGGS = 1000*E + 110*G + S;
      
      constraint R in {1,3,5,7,9};
      
      % Column carry digits from right hand end
      var 0..2:c1; var 0..2:c2; var 0..2:c3; var 0..2:c4; var 0..2:c5; 
      
      constraint all_different ([D, E, A, R, S, T, G, H, P, Y]);
      
      % Adding columns from the right hand end
      constraint (R + R + Y) mod 10 == R /\ c1 == (R + R + Y) div 10;
      constraint (c1 + A + E + P) mod 10 == E /\ c2 == (c1 + A + E + P) div 10;
      constraint (c2 + E + D + P) mod 10 == T /\ c3 ==  (c2 + E + D + P) div 10;
      constraint (c3 + D + A + A) mod 10 == S /\ c4 ==  (c3 + D + A + A) div 10;
      constraint (c4 + E + H) mod 10 == A /\ c5 == (c4 + E + H) div 10;
      constraint E == R + c5;
      
      solve satisfy;
      output ["EGGS = " ++ show(EGGS) ++ "\n" 
      ++ "([D, E, A, R, S, T, G, H, P, Y] = "  ++ show([D, E, A, R, S, T, G, H, P, Y]  )];
       
      % EGGS = 4882
      % ([D, E, A, R, S, T, G, H, P, Y] = 
      %  [1, 4, 0, 3, 2, 5, 8, 6, 9, 7]
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:54 am on 19 March 2024 Permalink | Reply
    Tags:   

    Teaser 2673: Footprints 

    From The Sunday Times, 15th December 2013 [link] [link]

    A cubical dice, with faces labelled as usual, is placed in one of the nine squares of a three-by-three grid, where it fits exactly. It is then rotated about one of its edges into an adjacent square and this is done a total of eight times so that the dice visits each square once. The “footprint” of the route is the total of the nine faces that are in contact with the grid.

    (a) What is the lowest footprint possible?
    (b) What is the highest footprint possible?
    (c) Which whole numbers between those two values cannot be footprints?

    [teaser2673]

     
    • Jim Randell's avatar

      Jim Randell 8:55 am on 19 March 2024 Permalink | Reply

      I used the [[ Cube() ]] class to deal with rotations of the standard die (see: Teaser 2835).

      This Python program considers possible representative starting squares on the grid (a corner, an edge, the centre square) using a standard right-handed die, and then positioning it in all possible orientations it looks for possible footprint values as it moves around the grid. (We get the same results with the remaining squares on the grid, and also if we were to use a left-handed die).

      It runs in 82ms. (Internal runtime is 17ms).

      Run: [ @replit ]

      from enigma import (irange, append, diff, printf)
      from cube import (Cube, U, D, L, R, F, B)
      
      # possible moves:
      #
      #  1 2 3
      #  4 5 6
      #  7 8 9
      #
      move = {
        1: { F: 2, L: 4 },
        2: { B: 1, F: 3, L: 5 },
        3: { B: 2, L: 6 },
        4: { R: 1, F: 5, L: 7 },
        5: { R: 2, B: 4, F: 6, L: 8 },
        6: { R: 3, B: 5, L: 9 },
        7: { R: 4, F: 8 },
        8: { R: 5, B: 7, F: 9 },
        9: { R: 6, B: 8 },
      }
      
      # calculate footprint totals
      # d = current die orientation
      # i = current die position
      # t = accumulated footprint total
      # vs = visited positions
      def footprints(d, i, t=0, vs=set()):
        # add in the value on the D face
        t += d.faces[D]
        vs = append(vs, i)
        # have we visited each square in the grid?
        if len(vs) == 9:
          yield t
        else:
          # make a move
          for (r, j) in move[i].items():
            if j not in vs:
              yield from footprints(d.rotate([r]), j, t, vs)
      
      # a standard die (U, D, L, R, F, B)
      die = (1, 6, 2, 5, 3, 4)
      
      # accumulate footprint totals
      ts = set()
      # consider possible starting squares: corner = 1, edge = 2, centre = 5
      for i in [1, 2, 5]:
        # consider possible initial orientations for the die
        for d in Cube(faces=die).rotations():
          # calculate footprint totals
          ts.update(footprints(d, i))
      
      # calculate min/max footprints
      (a, b) = (min(ts), max(ts))
      # output solution
      printf("min = {a}; max = {b}; missing = {ms}", ms=diff(irange(a, b), ts))
      

      Solution: (a) The minimum possible footprint is 21; (b) The maximum possible footprint is 42; (c) 29 and 34 cannot be footprints.

      The following paths starting in the centre square (5) and visiting (5, 6, 3, 2, 1, 4, 7, 8, 9) (i.e. spiralling out from the centre) give the minimum and maximum:

      (1); F → (2); R → (4); B → (1); B → (3); L → (2); L → (4); F → (1); F → (3) = footprint 21
      (6); F → (5); R → (4); B → (6); B → (3); L → (5); L → (4); F → (6); F → (3) = footprint 42


      With a bit of analysis we can get a faster program:

      There are only 3 essentially different paths (every other path is a reflection/rotation/reversal of one of these):

      If we start at the beginning of one of these paths with a die showing (x, y, z) around one corner, and opposite faces (X, Y, Z) (so x + X = y + Y = z + Z = 7), then we get the following footprints:

      X + z + x + y + z + Y + X + z + x = 21 + 3z
      X + z + x + y + X + z + x + y + z = 14 + 2y + 3z
      X + z + y + X + Z + y + z + X + Z = 14 + 2y + 3X

      (y, z) = (2, 1) gives a minimum of 21 in second equation, and (y, z) = (5, 6) gives a maximum of 42.

      We can examine the full range of footprints by considering the value of 1 face in the first equation, and 2 adjacent faces in the second (or third) equation.

      This Python program examines all possible footprints, and has an internal runtime of 63µs.

      Run: [ @replit ]

      from enigma import (irange, diff, printf)
      
      # calculate footprint totals
      scores = [1, 2, 3, 4, 5, 6]
      ts = set()
      for x in scores:
        ts.add(21 + 3*x)
        for y in scores:
          if x == y or x + y == 7: continue
          ts.add(14 + 2*x + 3*y)
      
      # calculate min/max footprints
      (a, b) = (min(ts), max(ts))
      # output solution
      printf("min = {a}; max = {b}; missing = {ms}", ms=diff(irange(a, b), ts))
      

      Like

  • Unknown's avatar

    Jim Randell 8:28 am on 17 March 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 948: Journey north-east 

    From The Sunday Times, 21st September 1980 [link]

    For the purpose of my problem I have to choose two towns 26 miles apart. I might have chosen Oxford and Newbury, but it would be more appropriate for me as a Scotsman to go much farther north and choose Kingussie and Grantown-on-Spey, where the roads are somewhat less busy.

    Alf, Bert and Charles start off at the same time from Kingussie to make their way north-eastwards to Grantown-on-Spey, 26 miles distant.

    Alf walks at a constant speed of four miles per hour. Bert and Charles drive together in a car. After a certain time, Bert leaves the car, and walks forward at the same rate as Alf, while Charles drives back to meet Alf.

    Alf gets Into the car with Charles, and they continue to drive to Grantown-on-Spey, arriving there just as Bert does.

    On each stretch Charles averages 40 miles per hour.

    What is the time (in minutes) taken for them all to travel from Kingussie to Grantown-on-Spey?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser948]

     
    • Jim Randell's avatar

      Jim Randell 8:29 am on 17 March 2024 Permalink | Reply

      See: Teaser 3140, where we determined:

      If there are k pedestrians to be transported a distance d, and each walks a distance x at velocity w and is transported a distance (d − x) at velocity v, and the total time taken is t, then we have:

      n = 2k − 1
      x = dw(n − 1) / (v + wn)
      t = d(w + vn) / v(v + wn)

      We can plug the numbers for this puzzle in and calculate the result:

      Run: [ @replit ]

      from enigma import (fdiv, printf)
      
      # initial conditions
      k = 2   # number of walkers
      d = 26  # distance
      w = 4   # walking speed
      v = 40  # driving speed
      
      # calculate walking distance per person
      n = 2 * k - 1
      x = fdiv(d * w * (n - 1), v + w * n)
      
      # calculate time taken
      t = fdiv(x, w) + fdiv(d - x, v)
      
      # output solution
      printf("t = {t:g} hours (= {m:g} min) [x={x:g} miles]", m=t * 60)
      

      Solution: The total time taken is 93 minutes.

      Alf walks the first 4 miles (in 60 minutes), and is driven the remaining 22 miles (in 33 minutes).

      Bert is driven 22 miles first, and walks the last 4 miles.

      Charles drives 22 miles to drop off Bert, returns 18 miles to collect Alf, and then 22 miles to the destination, a total of 62 miles (in 93 minutes).

      So each arrives at the destination after 93 minutes.

      Like

    • Lise Andreasen's avatar

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

      Alf walks x miles. For symmetry reasons, Bert also walks x miles. In the middle we have y miles. 26 = x + y + x.
      They all spend the same amount of time.
      x/4 + (x+y)/40 = (2x+3y)/40.
      Solve.

      Like

  • Unknown's avatar

    Jim Randell 4:45 pm on 15 March 2024 Permalink | Reply
    Tags:   

    Teaser 3208: The scores are all square 

    From The Sunday Times, 17th March 2024 [link] [link]

    For Skaredahora’s quartet four players read the same musical score, but from different compass directions. There are symbols of three types, indicating different whole number beat durations, on a square 17×17 grid. Each player reads the beat position in their left-to-right direction, and pitch in their bottom-to-top.

    Each player plays four notes; South reads a plus at (beat,pitch) position (3,12), a circle at (14,1), a cross at (16,3), and a plus at beat 9. For example, if a cross indicates three beats, South plays a note of pitch 3 at beat 16, which is still sounding at beats 17 and 18, while East plays a note of pitch 2 sounding at beats 3, 4 and 5.

    No player sounds more than one note at the same time. All possible pitch differences between notes sounding simultaneously from different players occur, except zero and exactly one other value.

    Which non-zero pitch difference never occurs? What pitch does South play at beat 9?

    [teaser3208]

     
    • Jim Randell's avatar

      Jim Randell 6:10 pm on 15 March 2024 Permalink | Reply

      This program considers possible positions for the unspecified “+” note, and then constructs the notes heard at each beat for each player and checks the remaining constraints.

      It runs in 59ms. (Internal runtime is 1.8ms).

      Run: [ @replit ]

      from enigma import (irange, inf, tuples, cproduct, subsets, diff, singleton, printf)
      
      # fill out beats for notes in <X>, durations <dd>
      def beats(X, dd):
        bs = [0] * 22
        for (b, p, d) in X:
          for i in irange(dd[d]):
            i += b
            if bs[i] != 0: return
            bs[i] = p
        return bs
      
      # check a set of beats
      def check(bs):
        if None in bs: return
        # find differences between non-zero pitches
        ds = set()
        for ps in zip(*bs):
          ds.update(abs(x - y) for (x, y) in subsets(filter(None, ps), size=2))
        # differences exclude 0 and exactly one other value
        if 0 in ds: return
        return singleton(diff(irange(1, 16), ds))
      
      # duration symbols
      ks = "ox+"
      
      # suppose the unspecified '+' is at pitch <x> for S
      for x in irange(1, 17):
        # construct the notes (<beat>, <pitch>, <duration>) for each player
        S = [(3, 12, '+'), (9, x, '+'), (14, 1, 'o'), (16, 3, 'x')]
        N = list((18 - b, 18 - p, d) for (b, p, d) in S)
        W = list((18 - p, b, d) for (b, p, d) in S)
        E = list((p, 18 - b, d) for (b, p, d) in S)
      
        # determine maximum possible note duration
        dm = dict((k, inf) for k in ks)
        for X in [S, N, W, E]:
          for ((a, _, k), (b, _, _)) in tuples(sorted(X), 2):
            dm[k] = min(dm[k], b - a)
        if 0 in dm.values(): continue
      
        # choose durations
        for ds in cproduct(irange(1, dm[k]) for k in ks):
          if len(set(ds)) != 3: continue
          dd = dict(zip(ks, ds))
      
          # record pitches on each beat
          (bS, bN, bW, bE) = (beats(X, dd) for X in [S, N, W, E])
          k = check((bS, bN, bW, bE))
          if not k: continue
      
          # output solution
          printf("S={bS}", bS=bS[1:])
          printf("N={bN}", bN=bN[1:])
          printf("W={bW}", bW=bW[1:])
          printf("E={bE}", bE=bE[1:])
          printf("x={x} dd={dd} -> k={k} S[9]={S9}", S9=bS[9])
          printf()
      

      Solution: A pitch difference of 4 does not occur. South plays pitch 17 at beat 9.

      The note durations are:

      o = 1 beat
      x = 2 beats
      + = 5 beats

      Here is a diagram of the notes played:

      Like

      • Frits's avatar

        Frits 3:08 pm on 22 March 2024 Permalink | Reply

        @Jim, only thing to improve for a general solution is getting rid of the hardcoded 22 in line 5.

        Like

    • Frits's avatar

      Frits 5:23 pm on 16 March 2024 Permalink | Reply

      Based on part of Jim’s first published program.

       
      from enigma import SubstitutedExpression, subsets
      
      # invalid digit / symbol assignments
      d2i = dict()
      for d in range(0, 10):
        vs = set()
        if d == 0: vs.update('KLM')
        if d > 1: vs.update('U')
        if d > 2: vs.update('KL') # by manual inspection of S resp. N
        if d > 5: vs.update('M')  # by manual inspection of S
        d2i[d] = vs  
      
      # check for 15 different pitch differences (without zero)
      def check(K, L, M, UV):
        S = sorted([(3,      12, M), (9,  UV, M), (14,      1, K), (16,  3, L)])
        E = sorted([(UV,      9, M), (1,   4, K), (3,       2, L), (12, 15, M)])
        N = sorted([(2,      15, L), (4,  17, K), (9, 18 - UV, M), (15,  6, M)])
        W = sorted([(18 - UV, 9, M), (6,   3, M), (15,     16, L), (17, 14, K)])
        d = dict()
        for pl in [S, E, N, W]:
          done = set()  
          for (b, p, s) in pl:
            for j in range(s):
              # no simultaneous notes by the same player
              if (k := b + j - 1) in done: return False
              d[k] = d.get(k, []) + [p]
              done.add(k)
              
        # pitch differences
        diffs = set(abs(p2 - p1) for vs in d.values() for p1, p2 in subsets(vs, 2))
        if 0 in diffs or len(diffs) != 15: return False
        
        return set(range(1, 17)).difference(diffs).pop()
      
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # duration 'o': K, duration 'x': L, duration '+': M
          # the unspecified pitch
          "0 < UV < 18",
          # main checks
          "(missing := check(K, L, M, UV)) != 0",
        ],
        answer="(K, L, M, UV, missing)",
        d2i=d2i,
        # different whole number beat durations
        distinct=("KLM"),
        env=dict(check=check),
        verbose=0,    # use 256 to see the generated code
      ) 
      
      # print answers
      for ans in p.answers():
        print(f"{ans}")
        print("answer: pitch difference that never occurs:", ans[4])
        print("        South plays pitch", ans[3], "at beat 9")    
      

      Like

  • Unknown's avatar

    Jim Randell 11:52 am on 12 March 2024 Permalink | Reply
    Tags:   

    Teaser 2646: Monday birthdays 

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

    In one auspicious month last year our family had two great celebrations: Harry, the oldest member of the family, had his 100th birthday and, in the same month, his great-grandson Peter was born.

    It turns out that they were both born on the same day of the week. Of course, Harry has celebrated several of his 100 birthdays on a Monday, as will Peter. However, even if Peter lives that long, the number of his first 100 birthdays that fall on a Monday will be two fewer than Harry’s.

    On which day of the week were they born?

    [teaser2646]

     
    • Jim Randell's avatar

      Jim Randell 11:52 am on 12 March 2024 Permalink | Reply

      This puzzle was set in 2013, so Harry had his 100th birthday in 2012, and so was born in 1912.

      Peter was born in the same month in 2012 as Harry’s 100th birthday, so Peter’s 100th birthday will be in 2112.

      And we are interested in counting birthdays that fall on a Monday.

      This Python program collects candidate birthdays >(month, day) for each of them and groups them by the number of Monday birthdays in the first 100. We then look for situations where Harry has 2 more Monday birthdays that Peter, and then try to find common (weekday, month) combinations.

      It runs in 63ms. (Internal runtime is 3.1ms).

      Run: [ @replit ]

      from datetime import (date, timedelta)
      from enigma import (defaultdict, group, intersect, printf)
      
      # collect birthdays between y1 and y2 that fall on a Monday
      def collect(y1, y2):
        # find the first Monday (weekday = 0) in the range
        inc = timedelta(days=1)
        d = date(y1, 1, 1)
        while d.weekday() != 0: d += inc
        # and then skip forward by weeks
        inc = timedelta(days=7)
        # count the number of Monday birthdays by date
        r = defaultdict(int)
        while True:
          r[(d.month, d.day)] += 1
          d += inc
          if d.year > y2: break
        # group dates by Monday count
        return group(r.keys(), by=r.get)
      
      # count birthdays on a Monday for Harry from 1913 - 2012
      gH = collect(1913, 2012)
      # count birthdays on a Monday to Peter from 2013 - 2112
      gP = collect(2013, 2112)
      
      # collect results
      rs = set()
      
      # consider possible counts for H
      for (kH, vH) in gH.items():
        # P has a count that is 2 less
        kP = kH - 2
        vP = gP.get(kP)
        if vP is None: continue
      
        # collect possible (weekday, month) combinations for Harry and Peter
        fn = lambda d: (d.weekday(), d.month)
        dH = group((date(1912, m, d) for (m, d) in vH), by=fn)
        dP = group((date(2012, m, d) for (m, d) in vP), by=fn)
      
        # look for (weekday, month) combinations common to both
        for (w, m) in intersect([dH.keys(), dP.keys()]):
          rs.add(w)
      
      # find possible days of the week
      days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      for w in rs:
        printf("weekday = {w}", w=days[w])
      

      Solution: Harry and Peter were both born on a Tuesday.

      And they could be both born on Tuesdays in any month from March to December.

      For example:

      Harry was born on Tuesday, 5th March 1912, and has had 15 birthdays on a Monday up to 2012 (in years: 1917, 1923, 1928, 1934, 1945, 1951, 1956, 1962, 1973, 1979, 1984, 1990, 2001, 2007, 2012).

      Peter was born on Tuesday, 6th March 2012, and will have 13 birthdays on a Monday up to 2112 (in years: 2017, 2023, 2028, 2034, 2045, 2051, 2056, 2062, 2073, 2079, 2084, 2090, 2102).

      The sequences differ because 2000 was a leap year, but 2100 will not be.

      Like

  • Unknown's avatar

    Jim Randell 1:34 pm on 10 March 2024 Permalink | Reply
    Tags: by: Rachel Blunt   

    Brain teaser 1023: A mixed maths class 

    From The Sunday Times, 7th March 1982 [link]

    My mathematics class consists of six boys and six girls. In their annual examination each was awarded integral an mark out of 100.

    Disappointingly no boy received a distinction (over 80)  but all the boys managed over 40. The lowest mark in the class was 36.

    Upon listing the boys’ marks I noticed that all their marks were different prime numbers and that their average was an even number. Further three of the boys’ marks formed an arithmetic progression, and the other three another arithmetic progression.

    Turning, my, attention to the girls I found that their marks were all different. There was little overall difference in the performance of the sexes, the total of the girls’ marks being just one more than the total of the boys’. Three of the girls’ marks formed one geometric progression, and the other three formed another geometric progression with the same ratio as the first one.

    Finally when listing the results in numerical order I was pleased to see that Annie (who did so badly last year) had come seventh in the class.

    What were the top six marks (in descending order)?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser1023]

     
    • Jim Randell's avatar

      Jim Randell 1:35 pm on 10 March 2024 Permalink | Reply

      This Python program looks at possible scores for the boys and the girls and then looks for a common key (for the boys the key is the sum of the scores, for the girls it is the sum minus 1), and then checks for possibilities that place a girl (Annie) in 7th position.

      It runs in 60ms. (Internal runtime is 3.0ms).

      Run: [ @replit ]

      from enigma import (
        irange, primes, subsets, partitions, seq_all_same, tuples,
        fraction, group, item, intersect, cproduct, printf
      )
      
      # does sequence <seq> form an arithmetic progression
      is_arithmetic = lambda seq: seq_all_same(y - x for (x, y) in tuples(seq, 2))
      
      # generate possible marks for the boys
      def gen_boys():
        # the marks are 6 different primes between 41 and 80
        for bs in subsets(primes.between(41, 80), size=6):
          # and their average is an even number
          t = sum(bs)
          if t % 12 != 0: continue
          # and they form two 3-length arithmetic progressions
          for (b1, b2) in partitions(bs, 3):
            if not (is_arithmetic(b1) and is_arithmetic(b2)): continue
            printf("[boys = {b1} + {b2} -> {t}]")
            # return (<total>, (<series1>, <series2>))
            yield (t, (b1, b2))
      
      # generate possible marks for the girls
      def gen_girls():
        # choose geometric progression that start 36
        a = 36
        for b in irange(37, 100):
          (c, r) = divmod(b * b, a)
          if c > 100: break
          if r != 0: continue
          # now look for another geometric progression with the same ratio = b/a
          for x in irange(37, 100):
            (y, ry) = divmod(x * b, a)
            (z, rz) = divmod(y * b, a)
            if z > 100: break
            if ry != 0 or rz != 0: continue
            t = sum([a, b, c, x, y, z]) - 1
            printf("[girls = ({a}, {b}, {c}) + ({x}, {y}, {z}); r={r} -> {t}]", r=fraction(b, a))
            # return (<total> - 1, (<series1>, <series2>))
            yield (t, ((a, b, c), (x, y, z)))
      
      # group boys by total
      boys = group(gen_boys(), by=item(0), f=item(1))
      
      # group girls by total - 1
      girls = group(gen_girls(), by=item(0), f=item(1))
      
      # look for common keys
      for k in intersect([boys.keys(), girls.keys()]):
        for ((b1, b2), (g1, g2)) in cproduct([boys[k], girls[k]]):
          # marks in order
          ms = sorted(b1 + b2 + g1 + g2, reverse=1)
          # 7th position (= index 6) must be a girl
          if not (ms[6] in g1 + g2): continue
          # output solution
          printf("boys = {b1} + {b2}, girls = {g1} + {g2} -> {ms}")
      

      Solution: The top six marks were: 90, 81, 79, 73, 67, 60.

      The only scenario is:

      boys = (41, 47, 53) + (67, 73, 79) → sum = 360
      both sequences have a common difference of 6

      girls = (36, 54, 81) + (40, 60, 90) → sum = 361
      both sequences have a common ratio of 3/2

      Which gives the following sequence of scores:

      1st = 90 (girl)
      2nd = 81 (girl)
      3rd = 79 (boy)
      4th = 73 (boy)
      5th = 67 (boy)
      6th = 60 (girl)
      7th = 54 (girl)
      8th = 53 (boy)
      9th = 47 (boy)
      10th = 41 (boy)
      11th = 40 (girl)
      12th = 36 (girl)

      This the only scenario where the 7th best score is a girl.

      Although there are two other scenarios for the boys that have the right sum, but each places a boy in 7th place overall:

      boys = (43, 61, 79) + (47, 59, 71) → sum = 360 [7th = 59]
      boys = (47, 53, 59) + (61, 67, 73) → sum = 360 [7th = 59]

      Separately there are 5 possible scenarios for the boys and 5 for the girls, but only those sequences with sums of 360/361 give matching keys.

      Like

    • GeoffR's avatar

      GeoffR 10:27 am on 12 March 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % variables for boys scores
      var 41..79:B1; var 41..79:B2; var 41..79:B3; 
      var 41..79:B4; var 41..79:B5; var 41..79:B6; 
      
      % variables for girls scores
      var 36..100:G1; var 36..100:G2; var 36..100:G3;
      var 36..100:G4; var 36..100:G5; var 36..100:G6;
      
      % Geometric ratio is a/b, arithmetic difference = d
      var 1..9:a; var 1..9:b; var 1..9:d;  
      constraint a > b;
      
      % 2-digit primes for this teaser
      set of int: primes = {41, 43, 47, 53, 59, 61, 67, 71, 73, 79};
      
      constraint sum([B1 in primes, B2  in primes, B3  in primes,
      B4 in primes, B5 in primes, B6 in primes]) == 6;
       
      % Boys scores are in arithmetic progression (will be different)
      constraint B2 - B1 == d /\ B3 - B2 == d;
      constraint B4 > B1;
      constraint B5 - B4 == d /\ B6 - B5 == d;
       
      % Girls scores in geometric progression (will be different)
      constraint G1 == 36 /\ G4 > G1;
      constraint G2*b == G1*a /\ G3*b == G2*a
      /\ G5*b == G4*a /\ G6*b == G5*a;
      
      % Average of boys scores is an even number
      constraint (B1 + B2 + B3 + B4 + B5 + B6) div 6 > 0
      /\ (B1 + B2 + B3 + B4 + B5 + B6) mod 2 == 0;
      
      % Total of girls scores in one more than total of boys scores
      constraint G1 + G2 + G3 + G4 + G5 + G6 == B1 + B2 + B3 + B4 + B5 + B6 + 1;
      
      % Sort all pupils marks - gives ascending order
      array[1..12] of var int: pupils = [B1,B2,B3,B4,B5,B6,G1,G2,G3,G4,G5,G6];
      array[1..12] of var int: SP = sort(pupils);
      
      solve satisfy;
      
      output ["Boys scores = " ++ show([B1,B2,B3,B4,B5,B6]) 
      ++ "\n" ++ "Girls scores = " ++ show([G1,G2,G3,G4,G5,G6])
      ++ "\n" ++ "Pupils scores (in ascending order) = " ++ show(SP)
      ++ "\n" ++ "The top seven marks, (in descending order), were: "
      ++ show([SP[12], SP[11], SP[10], SP[9], SP[8], SP[7], SP[6]]) ];
      
      % Boys scores = [47, 53, 59, 61, 67, 73]
      % Girls scores = [36, 54, 81, 40, 60, 90]
      % Pupils scores (in ascending order) = [36, 40, 47, 53, 54, 59, 60, 61, 67, 73, 81, 90]
      % The top seven marks, (in descending order), were: 
      % [90, 81, 73, 67, 61, 60, 59]
      % ----------
      % Boys scores = [41, 47, 53, 67, 73, 79]
      % Girls scores = [36, 54, 81, 40, 60, 90]
      % Pupils scores (in ascending order) = [36, 40, 41, 47, 53, 54, 60, 67, 73, 79, 81, 90]
      % The top seven marks, (in descending order), were: 
      % [90, 81, 79, 73, 67, 60, 54]  <<< answer
      % ----------
      % ==========
      % The second solution gives a score of 54 in 7th position - a girl's score
      % and the first solution gives a score of 59 in 7th position - a boy's score
      

      Like

  • Unknown's avatar

    Jim Randell 4:42 pm on 8 March 2024 Permalink | Reply
    Tags:   

    Teaser 3207: Dodecahedra 

    From The Sunday Times, 10th March 2024 [link] [link]

    Fabulé’s next creation will be a set of equal-sized silver regular dodecahedra, but some of the faces will be gold-plated. He is undecided whether to go ahead with either a “Charm” set or a “Partial” set.

    “Charm” is composed of dodecahedra with at least one gold-plated face but with no gold-plated face having a common side with more than one other gold-plated face. “Partial” is composed of dodecahedra with exactly six gold-plated faces. All the items in each set are distinguishable.

    What is the maximum number of dodecahedra possible in (a) “Charm”, (b) “Partial”?

    [teaser3207]

     
    • Jim Randell's avatar

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

      See also: Teaser 2538.

      This Python program constructs the 60 rotations of the faces of a regular dodecahedron, and then uses them to count the number of distinguishable dodecahedra in each set.

      It runs in 140ms. (Internal runtime is 64ms).

      Run: [ @replit ]

      from enigma import (rotate, flatten, irange, subsets, printf)
      
      # arrangement of adjacent faces
      adj = {
        0: [1, 2, 3, 4, 5],
        1: [0, 5, 8, 7, 2],
        2: [0, 1, 7, 6, 3],
        3: [0, 2, 6, 10, 4],
        4: [0, 3, 10, 9, 5],
        5: [0, 4, 9, 8, 1],
      }
      # add in the opposite faces
      opp = lambda f: 11 - f  # opposite face
      adj.update((opp(k), list(opp(v) for v in reversed(vs))) for (k, vs) in list(adj.items()))
      
      # make a set of rotations with upper face U, adjacent faces fs
      def make_rots(U, fs):
        for _ in fs:
          r = [U] + fs
          r.extend(reversed(list(opp(f) for f in r)))
          yield r
          fs = rotate(fs, 1)
      
      # construct the 60 rotations of the dodecahedron
      rots = flatten(make_rots(k, vs) for (k, vs) in adj.items())
      
      # apply a rotation to a set of faces
      rot = lambda r, fs: tuple(sorted(r[f] for f in fs))
      
      # canonical colouring for a set of faces
      canonical = lambda fs: min(rot(r, fs) for r in rots)
      
      # "Charm" = colour some faces; no more than 2 adjacent
      rs = set()
      # choose coloured faces
      for fs in subsets(irange(12), min_size=1, max_size=4, fn=set):
        # check no coloured face has more than 1 coloured neighbour
        if not any(len(fs.intersection(adj[f])) > 1 for f in fs):
          rs.add(canonical(fs))
      printf("Charm = {n} dodecahedra", n=len(rs))
      
      # "Partial" = colour 6 of the faces
      rs = set(canonical(fs) for fs in subsets(irange(12), size=6))
      printf("Partial = {n} dodecahedra", n=len(rs))
      

      Solution: (a) Charm has a maximum of 11 dodecahedra; (b) Partial has a maximum of 24 dodecahedra.

      Here is a diagram of the 11 possible arrangements for “Charm”:

      There is 1 colouring with 1 gold face; 3 colourings with 2 gold faces; 3 colourings with 3 gold faces; 4 colourings with 4 gold faces.

      The first 4 colourings in the diagram have no shared edges between gold faces, the next 4 have one pair of faces that share an edge, and the final 3 have two separate pairs of faces that share an edge.

      And here are the 24 possible arrangements for “Partial” (6 coloured faces):

      Like

    • Hugo's avatar

      Hugo 9:08 am on 18 March 2024 Permalink | Reply

      I had a think about other Platonic solids.
      For the tetrahedron there is one way to have no gilt faces, one way to gild one face, one way to gild two faces (because any two faces share an edge), one way to gild three faces (because that leaves just one face silver so is just the inverse of one gilt face), and one way to gild all four faces.
      For the cube there is one way to have no gilt faces or all six faces gilt, one way to gild one face or five faces, two ways to gild two faces (either adjacent or opposite) and therefore two ways to gild four faces, two ways to gild three faces (all sharing a vertex, or two opposite faces and one between them).

      The octahedron exceeds my power of visualizing in three dimensions. Anyone have any ideas?

      Like

      • Jim Randell's avatar

        Jim Randell 9:41 am on 18 March 2024 Permalink | Reply

        In Teaser 2538 we looked at colouring some of the the faces of an octahedron.

        The code for calculating the “Partial” collection can be used to find the colourings of the dodcahedron:

        0 (or 12) → 1
        1 (or 11) → 1
        2 (or 10) → 3
        3 (or 9) → 5
        4 (or 8) → 12
        5 (or 7) → 14
        6 → 24
        total = 96

        OEIS A098112 gives the total number of colourings of the icosahedron as 17824.

        Like

    • Hugo's avatar

      Hugo 10:35 am on 18 March 2024 Permalink | Reply

      Thank you, Jim. I’d forgotten that.

      Like

  • Unknown's avatar

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

    Teaser 2649: Right to left 

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

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

    TO
    REFLECT
    RIGHTTOLEFT

    are three even palindromes.

    What is the CORRECT number?

    [teaser2649]

     
    • Jim Randell's avatar

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

      See also: Teaser 2859.

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

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

      Run: [ @replit ]

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

      Solution: CORRECT = 124421124.

      We get the following assignments:

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

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

      And one of:

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

      Which give the palindromes:

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

      Liked by 1 person

    • Frits's avatar

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

      Reusing part of Jim’s code.

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

      Like

  • Unknown's avatar

    Jim Randell 4:34 pm on 1 March 2024 Permalink | Reply
    Tags:   

    Teaser 3206: Support measures 

    From The Sunday Times, 3rd March 2024 [link] [link]

    Two buildings on level ground with vertical walls were in need of support so engineers placed a steel girder at the foot of the wall of one building and leaned it against the wall of the other one. They placed a shorter girder at the foot of the wall of the second building and leaned it against the wall of the first one. The two girders were then welded together for strength.

    The lengths of the girders, the heights of their tops above the ground, the distance between their tops and the distance between the two buildings were all whole numbers of feet. The weld was less than ten feet above the ground and the shorter girder was a foot longer than the distance between the buildings.

    What were the lengths of the two girders?

    [teaser3206]

     
    • Jim Randell's avatar

      Jim Randell 4:53 pm on 1 March 2024 Permalink | Reply

      See: Enigma 775 for the calculation of the height of the weld above the ground.

      If the distance between the buildings is d, and the girders have lengths g1 and g2, and the top ends of the girders meet the walls at heights of h1 and h2, and cross each other at a height of H, and the tops are a distance z apart, then we have the following three Pythagorean triangles:

      (d, h1, g1)
      (d, h2, g2)
      (d, h1 − h2, z)

      subject to:

      h1 > h2
      g2 = d + 1
      H = (h1 × h2) / (h1 + h2) < 10

      This Python program considers possible Pythagorean triangles for the girders (up to a reasonable maximum length).

      It runs in 59ms. (Internal runtime is 409µs).

      from enigma import (defaultdict, pythagorean_triples, ihypot, fdiv, printf)
      
      # index pythagorean triples by non-hypotenuse sides
      ts = defaultdict(list)
      for (x, y, z) in pythagorean_triples(250):
        ts[x].append((y, z))
        ts[y].append((x, z))
      
      # consider possible distances
      for (d, vs) in ts.items():
        # the second girder
        for (h2, g2) in vs:
          if not (g2 == d + 1): continue
      
          # the first girder
          for (h1, g1) in vs:
            if not (h1 > h2): continue
            # check the height of the weld is < 10
            if not (h1 * h2 < 10 * (h1 + h2)): continue
            # calculate the distance between the tops
            z = ihypot(h1 - h2, d)
            if z is None: continue
      
            # output solution
            H = fdiv(h1 * h2, h1 + h2)
            printf("g1={g1} g2={g2}; h1={h1} h2={h2} H={H:.2f}; d={d} z={z}")
      

      Solution: The girders were 109 ft and 61 ft long.

      The distance between the buildings is 60 ft, so the girders reach 11 ft and 91 ft up the walls, and the weld is about 9.81 ft above the ground (= 1001/102). The tops of the girders are 100 ft apart.


      Without the restriction on the height of the weld there is a further theoretical solution where the buildings are 6160 ft apart, and the girders have lengths of 9050 ft and 6161 ft (they reach 6630 ft and 111 ft up the buildings, and cross at a height of 109.17 ft).

      But this is the only other solution for girders with lengths up to 10 million feet.

      Like

      • Frits's avatar

        Frits 8:09 am on 2 March 2024 Permalink | Reply

        @Jim, you seem to reject the possibility of h1 = 2 * h2 as then the “ts” entry may only have 2 entries.

        Like

        • Jim Randell's avatar

          Jim Randell 8:18 am on 2 March 2024 Permalink | Reply

          @Frits: Yes, I was supposing the three Pythagorean triangles were all different. Which is not necessarily the case.

          Like

      • Frits's avatar

        Frits 1:42 pm on 2 March 2024 Permalink | Reply

        Using analysis of my other program.

        from enigma import (defaultdict, pythagorean_triples, subsets)
        
        # set of valid widths
        ws = {2 * n * (n + 1) for n in range(1, 10)}
        
        M = 2717 # maximum any length
        # index pythagorean triples by non-hypotenuse sides
        ts = defaultdict(list)
        for (x, y, z) in pythagorean_triples(M):
          if x in ws:
            ts[x].append((y, z))
          if y in ws:
            ts[y].append((x, z))
        
        # smaller height must be less than 20
        for k, vs in sorted(ts.items()):
          svs = sorted(vs)
          # pick two Pythagorean triangles
          for (c1, c2) in subsets(svs, 2):
            if c1[0] > 19: break
            if 2 * c1[0] == c2[0]:
              print(f"answer: {c1[1]} and {c2[1]}")
        
          # pick three Pythagorean triangles
          for (c1, c2 ,c3) in subsets(svs, 3):
            if c1[0] > 19: break
            if c1[0] + c2[0] == c3[0]:
              print(f"answer: {c1[1]} and {c3[1]}")
              if c2[0] < 20:
                print(f"    or  {c2[1]} and {c3[1]}")
        

        Like

      • Frits's avatar

        Frits 2:05 pm on 2 March 2024 Permalink | Reply

        Similar

            
        from enigma import (defaultdict, pythagorean_triples, subsets)
        
        # set of valid widths
        ws = {2 * n * (n + 1) for n in range(1, 10)}
        
        M = 2717 # maximum any length 
        # index pythagorean triples by non-hypotenuse sides
        ts = defaultdict(list)
        for (x, y, z) in pythagorean_triples(M):
          if x in ws: 
            ts[x].append((y, z))
          if y in ws:
            ts[y].append((x, z))
        
        # smaller height must be less than 20
        for k, vs in sorted(ts.items()):
          svs = sorted(vs)
          # pick two Pythagorean triangles
          for (c1, c2) in subsets(svs, 2):
            if c1[0] > 19: break
            if 2 * c1[0] == c2[0]:
              print(f"answer: {c1[1]} and {c2[1]}")
            
            if (c3 := (m := (c1[0] + c2[0]), (k**2 + m**2)**.5)) in vs: 
              print(f"answer: {c1[1]} and {c3[1]}")
              if c2[0] < 20:
                print(f"    or  {c2[1]} and {c3[1]}")
        

        Like

        • Frits's avatar

          Frits 2:23 am on 5 March 2024 Permalink | Reply

          To explore the full solution set we can use maximum length 16199:

          # w^2 + h^2 = z^2   (with even w) 
          # look for hypotenuse <z> so that (z + 1)^2 - z^2 > w^2
          M = (max(ws)**2 - 1) // 2   # maximum any length
          

          Like

    • Frits's avatar

      Frits 8:05 am on 2 March 2024 Permalink | Reply

      There is an upper bound for the width and/or smaller height but not always for the greater height.

          
      from enigma import is_square
      
      #   |\     |        whole numbers: g1, g2, h1, h2, g3 and w
      #   | \    |        g1^2 = h1^2 + w^2
      # h1|  \g1 |        g2^2 = h2^2 + w^2
      #   |   \ _/        g3^2 = (h1 - h2)^2 + w^2
      #   | g2/\ |h2      g2 = w + 1 
      #   |_/___\|        h3 < 10
      #       w            
      
      # h2^2 = g2^2 - w^2 = 2w + 1
      
      # we have:
      # 1/h3 = 1/h1 + 1/h2 with h3 < 10
      
      # 1/h1 + 1/h2 > 0.1  or 10 * (h1 + h2) > h1 * h2)
      # (h2 - 10) * h1 < 10 * h2 or h1 < 10 * h2 / (h2 - 10)
      
      # also h1 > h2 so when is 10 * h2 / (h2 - 10) equal to h2?
      # h2^2 - 20h2 = 0, h2(h2 - 20) = 0 --> h2 = 20 
      # w < 200 and h2 < 20 
      
      # h2 must be odd as h^2 = 2w + 1 --> h2 = 2n + 1, h2 < 20, n < 10
      for n in range(1, 10):
        h2 = 2 * n + 1
        # h2^2 = 4n^2 + 4n + 1 = 2w + 1
        w = 2 * n * (n + 1)
        w2 = w * w
        ub_h1, r = divmod(10 * h2, h2 - 10)
        if not r: 
          ub_h1 -= 1
        if ub_h1 < 0: 
          ub_h1 = 2717 # Burj Khalifa
      
        for h1 in range(h2 + 1, ub_h1 + 1):
          if not (g1 := is_square(h1**2 + w2)): continue
          if not (g3 := is_square((h1 - h2)**2 + w2)): continue
          print(f"answer: {(g2 := w + 1)} and {g1}")
      

      Like

    • GeoffR's avatar

      GeoffR 2:29 pm on 2 March 2024 Permalink | Reply

      Using WP Reader to post a MiniZinc solution.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Assumed UB of 120 for the six integers required
      var 1..120:g1; var 1..120:g2; var 1..120:g3; var 1..120:w;
      var 1..120:h1; var 1..120:h2; var 1.0..10.0:h3;
      
      % Reusing Frits diagram for this solution
      % g1 and g2 are the girder lengths, w is width between walls
      % h1 and h2 are wall heights where girders meet the walls
      % g3 is the distance between their tops, h3 the weld height
      
      constraint g2 == w + 1 /\ g2 < g1;
      constraint g1 * g1 == h1 * h1 + w * w;
      constraint g2 * g2 == h2 * h2 + w * w;
      constraint g3 * g3 == (h1 - h2) * (h1 - h2) + w * w;
      
      solve satisfy;
      
      output["g1, g2, g3 = " ++ show([g1, g2, g3]) ++
      "\n" ++ "h1, h2, w = " ++ show([h1, h2, w]) ];
      

      Like

      • Jim Randell's avatar

        Jim Randell 7:47 am on 3 March 2024 Permalink | Reply

        @GeoffR: I think for a complete solution you also need a constraint for the weld height (although in this case it doesn’t eliminate any solution candidates).

        My MiniZinc specification looks like this:

        %#! python3 -m minizinc use_embed=1
        
        {var("1..250", ["d", "g1", "g2", "h1", "h2", "z"])};
        
        predicate is_pythagorean(var int: x, var int: y, var int: z)
          = (x * x + y * y = z * z);
        
        % first girder
        constraint is_pythagorean(d, h1, g1);
        
        % second girder
        constraint is_pythagorean(d, h2, g2);
        constraint h2 < h1;
        constraint g2 = d + 1;
        
        % weld height H = (h1 * h2) / (h1 + h2)
        constraint h1 * h2 < 10 * (h1 + h2);
        
        % tops
        constraint is_pythagorean(d, h1 - h2, z);
        
        solve satisfy;
        

        Like

    • GeoffR's avatar

      GeoffR 4:31 pm on 3 March 2024 Permalink | Reply

      @Jim:

      A neat MiniZinc solution.

      Yes, the weld height would give a complete solution, although this is not a strict requirement of the teaser description.

      I was more interested in the derivation of your formula for the weld height.

      If x is the distance from left wall h1 to the weld height (h3), by similar triangles:

      h3/x = h2/w and h1/w = h3/(w – x).

      Algebraic manipulation gives 1/h3 = 1/h1 + 1/h2
      or h3 = h1.h2/(h1 + h2).

      As Brian has mentioned to me, this is the same formula in optics for the focal length, object and image distances, or two resistors in parallel.

      Like

    • Frits's avatar

      Frits 7:06 am on 5 March 2024 Permalink | Reply

      I believe this program fully explores the solution set (without calculating complex upper bounds).

      from math import isqrt
      
      # for any pythagorean triangle with side y and hypothenuse z: 
      # if z = y + i then the square of other side = 2 * i * y + i^2
      
      # index pythagorean triples by non-hypotenuse sides
      # set of valid widths (h2 = 2n + 1)
      ts = {w: [(dm[0], dm[0] + i) for i in range(w - 2, 0, -2) 
                if (dm := divmod(w**2 - i**2, 2 * i))[1] == 0] 
                for w in [2 * n * (n + 1) for n in range(1, 10)]}
      
      # smaller height must be less than 20
      for n, (w, vs) in enumerate(ts.items(), start=1):
        # set up pythagorean triangle for smaller height 
        t1 = (2 * n + 1, w + 1) 
        # pick other Pythagorean triangles
        for t2 in vs:
          if t2 == t1: continue
          
          # we have a solution if greater height is double the small height
          if 2 * t1[0] == t2[0]:
            print(f"answer: {t1[1]} and {t2[1]} feet")
          
          # for non-hypotenuse sides h2 and (h1 - h2) check if h1^2 + w^2 is square
          if (z := isqrt((z2 := w**2 + (t1[0] + t2[0])**2)))**2 == z2:
            print(f"answer: {t1[1]} and {z} feet")
            if t2[0] < 20:
              print(f"    or  {t2[1]} and {z} feet")
      

      Like

      • Jim Randell's avatar

        Jim Randell 2:32 pm on 5 March 2024 Permalink | Reply

        @Frits: Well done on reaching a complete solution.

        I have to admit I just did a search for “longest steel girder” and chose a suitable upper value. (And I was surprised that the actual answer involved girders that were so long).

        But I thought I would do a complete solution too:

        If we know one of the non-hypotenuse sides of a Pythagorean triangle, say a in the triple (a, b, c), where c is the hypotenuse, then we have:

        a² = c² − b² = (c + b)(c − b)

        So by dividing a² into 2 divisors (say x and y), then we can find potential (b, c) pairs to make a Pythagorean triple as follows:

        b = |x − y| / 2
        c = (x + y) / 2

        And there are only a finite number of divisor pairs, so there are only a finite number Pythagorean triangles that share a leg.

        We can now provide a complete solution to the problem.

        As Frits notes above, h2 must be an odd number between 3 and 19, and for a given value of h2 we can recover the distance between the buildings d and the length of the shorter girder g2:

        d = (sq(h2) − 1) / 2
        g2 = d + 1

        So we can consider possible values for h2, calculate the corresponding value for d and then generate all possible Pythagorean triangles that have one leg d, and look for those with a longer g1 and corresponding h1, and then we can check for those that give an integer distance between the tops.

        This Python program has an internal runtime of 442µs.

        Run: [ @replit ]

        from enigma import (irange, sq, div, ihypot, divisors_pairs, fdiv, printf)
        
        # h2 is odd, < 20
        for h2 in irange(3, 19, step=2):
          d = div(sq(h2) - 1, 2)
          g2 = d + 1
        
          # look for pythagorean triples with a leg of d
          for (y, x) in divisors_pairs(d, 2):
            (h1, g1) = (div(x - y, 2), div(x + y, 2))
            if (not h1) or (not g1): continue
            # does this give a longer girder
            if not (g1 > g2): continue
            # check weld height H < 10
            if not (h1 * h2 < 10 * (h1 + h2)): continue
            # check distance between tops
            z = ihypot(d, h1 - h2)
            if z is None: continue
            # output solution
            H = fdiv(h1 * h2, h1 + h2)
            printf("g1={g1} g2={g2}; h1={h1} h2={h2} H={H:.2f}; d={d} z={z}")
        

        Like

        • Frits's avatar

          Frits 1:32 am on 8 March 2024 Permalink | Reply

          @Jim, I keep forgetting this trick.

          Processing the divisor pairs in reverse order allows for an early break with respect to the weld height check.

          Like

  • Unknown's avatar

    Jim Randell 9:06 am on 29 February 2024 Permalink | Reply
    Tags:   

    Teaser 2602: Over fifty years 

    From The Sunday Times, 5th August 2012 [link] [link]

    We have now had over fifty years of Teasers and to celebrate this I found three special numbers using only non-zero digits and I wrote the numbers down in increasing order. Then I consistently replaced all the digits by letters, with different letters for different digits.

    In this way the numbers became:

    FIFTY
    YEARS
    TEASER

    In fact the third number was the sum of the other two.

    Which number does TEASER represent?

    The first numbered Teaser puzzle was published 63 years ago on 26th February 1961 (although there was a gap of almost a year in 1978/9). However the earliest Teaser puzzle I have found was published in The Sunday Times on 2nd January 1949.

    [teaser2602]

     
    • Jim Randell's avatar

      Jim Randell 9:07 am on 29 February 2024 Permalink | Reply

      Here is a solution using the [[ SubstitutedExpression ]] solver from the enigma.py library.

      It runs in 64ms. (Internal runtime of the generated program is 445µs).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      --invalid="0,AEFIRSTY"
      
      "FIFTY + YEARS = TEASER"
      
      --extra="F < Y"
      

      Solution: TEASER = 158453.

      The complete sum is:

      62619 + 95834 = 158453

      Liked by 1 person

      • GeoffR's avatar

        GeoffR 3:12 pm on 29 February 2024 Permalink | Reply

        Trying a posting using WP Reader…

        from itertools import permutations
        
        for p1 in permutations ('123456789', 8):
            F, I, T, Y, E, A, R, S = p1
            if int(F) < int(Y):
                FIFTY = int(F + I + F + T + Y)
                YEARS = int(Y + E + A + R + S)
                TEASER = int(T + E + A + S + E + R)
                if FIFTY + YEARS == TEASER:
                    print(f"Sum is {FIFTY} + {YEARS} = {TEASER}")
        
        # Sum is 62619 + 95834 = 158453
        

        Like

  • Unknown's avatar

    Jim Randell 8:13 am on 27 February 2024 Permalink | Reply
    Tags:   

    Brain teaser 980: Is anybody there? 

    From The Sunday Times, 3rd May 1981 [link]

    People often ask me how I first got involved with setting teasers, and the answer is rather interesting. A friend suggested that I should like to visit a medium, who, with spiritual help, would give me some advice for the future. He said that he knew a jolly woman who would probably be able to suggest an exciting change in my career (I thought it more likely that I’d strike a happy medium).

    Anyway, I went along, and found that this medium had an unusual ouija board. It consisted of 24 points equally spaced around the perimeter of a circle. She invited me to write a letter of the alphabet against each of these points. Some letters were used more than once, and some (of course) were not used at all.

    Then, after much concentration, a counter moved from the centre of the circle straight to a letter. It paused, and then went straight to another letter, and so on. When it had completed a word, it went straight back to the centre of the circle, paused, and then started the next word in the same way.

    There were two fascinating things about the message that it spelt out for me. The first was that, each time the counter moved, it moved an exact whole number of inches. The second was that it spelt out the message:

    SET
    A
    SUNDAY
    TIMES
    BRAIN
    TEASER
    IN
    ???

    The last bit consisted of three letters which were the first three letters of a month.

    Which month?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser980]

     
    • Jim Randell's avatar

      Jim Randell 8:15 am on 27 February 2024 Permalink | Reply

      In the 1994 book the words are presented as:

      SET A SUNDAY TIMES BRAINTEASER IN ???

      However there is no solution with BRAINTEASER as a single word.


      See: Teaser 2647.

      We could discard A and IN from the list of words, as they appear as contiguous substrings of other words.

      This Python program constructs possible sets of orbits for the given words, and then attempts to add in an extra 3-letter word corresponding to a month of the year.

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

      Run: [ @replit ]

      from enigma import (update, rev, ordered, unpack, wrap, uniq, join, printf)
      
      # add a word to a collection of (not more than k) orbits
      def add_word(k, word, orbs):
        # collect letters by odd/even parity
        (w0, w1) = (set(word[0::2]), set(word[1::2]))
        if len(w0) > k or len(w1) > k: return
        # consider existing orbits
        for (i, t) in enumerate(orbs):
          for (s0, s1) in [t, rev(t)]:
            orb_ = (s0.union(w0), s1.union(w1))
            if all(len(x) < 4 for x in orb_):
              yield update(orbs, [(i, orb_)])
        if len(orbs) < k:
          # add the word to a new orbit
          yield orbs + [(w0, w1)]
      
      # make <words> using <k> orbits
      def make_words(k, words, orbs=list()):
        # are we done?
        if not words:
          # provide orbits in a canonical order
          yield orbs
        else:
          w = words[0]
          # attempt to add word <w>
          for orbs_ in add_word(k, w, orbs):
            yield from make_words(k, words[1:], orbs_)
      
      fmt_orb = unpack(lambda x, y: join([join(sorted(x)), join(sorted(y))], sep="+"))
      fmt = lambda orbs: join(map(fmt_orb, orbs), sep=", ")
      
      @wrap(uniq)
      def solve():
        # words to spell out
        words = "SET A SUNDAY TIMES BRAIN TEASER IN".split()
        # possible months
        months = "JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC".split()
      
        # make orbits for the given words
        for orbs in make_words(4, words):
          # attempt to also add a month
          for w in months:
            for orbs_ in add_word(4, w, orbs):
              # return orbits in a canonical form
              orbs_ = ordered(*(ordered(ordered(*p0), ordered(*p1)) for (p0, p1) in orbs_))
              yield (w, orbs_)
      
      # solve the puzzle
      for (w, orbs) in solve():
        printf("{w} <- {orbs}", orbs=fmt(orbs))
      

      Solution: The month is March.

      i.e. the final “word” is MAR.

      This can be achieved with the following set of orbits:

      ABN+IMR → A, BRAIN, IN, MAR
      AET+ERS → A, TEASER
      ANS+DUY → A, SUNDAY
      ?EI+MST → SET, TIMES

      One of the letters remains unassigned.

      Like

  • Unknown's avatar

    Jim Randell 9:03 am on 25 February 2024 Permalink | Reply
    Tags: ,   

    Teaser 2572: Abominable 

    From The Sunday Times, 8th January 2012 [link] [link]

    In the art class Pat used five circles for his new design, Snowman. One circle made the head and another touching circle (with radius three times as big) made the body. Two equal circles (of radius one centimetre less than that of the head) made the arms (one on each side) and they touched both the body and the head. The fifth circle enclosed the other four, touching each of them. The radius of the head’s circle was a whole number of centimetres.

    How many centimetres?

    [teaser2572]

     
    • Jim Randell's avatar

      Jim Randell 9:04 am on 25 February 2024 Permalink | Reply

      See also: Teaser 2926.

      The arrangement looks like this:

      If we consider the head, body, one arm and the enclosing circle we have four mutually tangent circles, so we can use Descartes’ Theorem [ @wikipedia ] to express a relationship between their curvatures.

      If the curvatures are represented by k (where: k = 1 / r, i.e. the curvature is the reciprocal of the corresponding radius), then we have:

      (Σk)² = 2Σ(k²)

      In this instance the circles have radii of r, 3r, r − 1, 4r and so the curvatures are:

      k1 = 1/r
      k2 = 1/3r
      k3 = 1/(r − 1)
      k4 = −1/4r

      The curvature of the fourth circle is negative as it circumscribes the other three circles.

      We can solve the puzzle numerically by simply considering possible r values.

      The following program runs in 65ms. (Internal runtime is 102µs).

      Run: [ @replit ]

      from enigma import (Rational, irange, inf, sq, printf)
      
      Q = Rational()
      
      # consider possible values for r
      for r in irange(2, inf):
      
        # calculate the 4 curvatures
        ks = (Q(1, r), Q(1, 3 * r), Q(1, r - 1), Q(-1, 4 * r))
      
        # check Descartes' Theorem
        if sq(sum(ks)) == 2 * sum(map(sq, ks)):
          printf("r = {r}")
          break
      

      Solution: The head has a radius of 13 cm.

      Or we can solve it by finding the roots of a polynomial.

      This Python program runs in 66ms. (Internal runtime is 3.2ms).

      Run: [ @replit ]

      from enigma import (Polynomial, sq, printf)
      
      # consider the numerators of the curvatures, where the denominator is 12r(r-1)
      ks = [
        12 * Polynomial([-1, 1]),  # k1 = 12(r-1) / 12r(r-1) = 1/r
         4 * Polynomial([-1, 1]),  # k2 = 4(r-1) / 12r(r-1) = 1/3r
        12 * Polynomial([ 0, 1]),  # k3 = 12r / 12r(r-1) = 1/(r-1)
        -3 * Polynomial([-1, 1]),  # k4 = -3(r-1) / 12r(r-1) = -1/4r
      ]
      
      # Descartes' Theorem: sq(sum(ks)) = 2 * sum(map(sq, ks))
      p = sq(sum(ks)) - 2 * sum(map(sq, ks))
      
      # solve the polynomial for positive integer solutions
      for r in p.rational_roots(domain='Z', include='+'):
        printf("r = {r}")
      

      Analytically, we find the polynomial we are solving is:

      r² − 26r + 169 = 0

      (r − 13)² = 0
      r = 13

      Like

  • Unknown's avatar

    Jim Randell 4:46 pm on 23 February 2024 Permalink | Reply
    Tags:   

    Teaser 3205: Colum’s columns of Bob’s bobs 

    From The Sunday Times, 25th February 2024 [link] [link]

    Colum played with Grandpa Bob’s collection of shilling coins. He used them to make minted-year columns for all years from 1900 to 1940 inclusive (in order, in line and touching). Exactly twelve columns had just one coin. This arrangement resembled a bar chart. Pleasingly, it was symmetric about the 1920 column (the tallest of all) and each column’s coin tally was a factor of its year. The total tally was a prime number.

    Later, Bob found eight more shilling coins in an old tin. Colum found that these added one to each of eight columns. Curiously, everything stated above about the arrangement still applied. If I told you the year and final tally for one of those eight columns you could be sure of the total final tally.

    Give this final total.

    [teaser3205]

     
    • Jim Randell's avatar

      Jim Randell 5:23 pm on 23 February 2024 Permalink | Reply

      My original brute force solution was quick to write, but slow to run. It ran in 4.6s. But with a few tweaks we can modify the approach to give a program that runs in a much more acceptable time.

      This Python program runs in 210ms.

      Run: [ @replit ]

      from enigma import (
        defaultdict, irange, divisors, update,  is_prime,
        group, item, singleton, subsets, printf
      )
      
      # fill slots 0-20 with possible values
      vs = [None] * 21
      # slot 20 must be an odd divisor of 1920, and be the largest value
      vs[20] = set(x for x in divisors(1920) if x > 1 and x % 2 == 1)
      m = max(vs[20])
      # remaining slots are divisors of 1900 + i and 1940 - i
      for i in irange(20):
        vs[i] = set(x for x in divisors(1900 + i) if x < m and (1940 - i) % x == 0)
      
      # find possible (left half) sequences
      def solve(k, ss):
        # are we done?
        if k == 21:
          # final value must be the largest
          (final, rest) = (ss[-1], ss[:-1])
          if not (final > max(rest)): return
          # total tally must be prime
          t = 2 * sum(rest) + final
          if not is_prime(t): return
          # there must be 12 1's (in the full sequence)
          if rest.count(1) != 6: return
          # return (<tally>, <sequence>)
          yield (t, ss)
        elif ss[k] is not None:
          # move on to the next index
          yield from solve(k + 1, ss)
        else:
          # choose a value for this index
          for v in vs[k]:
            if v == 1 and ss.count(1) > 5: continue
            yield from solve(k + 1, update(ss, [(k, v)]))
      
      # fill out indices with only one value
      seqs = list(singleton(xs) for xs in vs)
      
      # group complete sequences by tally
      g = group(solve(0, seqs), by=item(0), f=item(1))
      
      # record (<pos>, <value2>) -> <total2>
      ss = defaultdict(set)
      # choose the initial number of coins
      for k1 in sorted(g.keys()):
        k2 = k1 + 8
        if not (k2 in g): continue
      
        # look for first sequence
        for vs1 in g[k1]:
          # there must be (at least) 4 indices with a possible +1
          incs = list(j for (j, x) in enumerate(vs1[:-1]) if x + 1 in vs[j])
          if len(incs) < 4: continue
      
          # look for second sequence
          for js in subsets(incs, size=4):
            # find (<index>, <value2>) values for +1 indices
            rs = list((j, vs1[j] + 1) for j in js)
            # record (<index>, <value2>) -> <total2>
            for x in rs: ss[x].add(k2)
      
            # output scenario
            printf("{k1} -> {vs1}")
            printf("{k2} -> {vs2}", vs2=update(vs1, rs))
            printf("{rs}")
            printf()
      
      # look for keys that give a unique final total
      rs = set()
      for k in sorted(ss.keys()):
        printf("{k} -> {vs}", vs=ss[k])
        t = singleton(ss[k])
        if t is not None:
          rs.add(t)
      printf()
      
      # output solution
      printf("final tally = {rs}", rs=sorted(rs))
      

      Solution: The final total was 139 coins.


      There are 13 possible scenarios that fit the before/after requirements. 11 of these have totals of (131, 139), one has totals of (101, 109), and one has totals of (149, 157).

      And we are told the 1908 (or 1932) column was increased from 2 coins to 3 coins, then that narrows down the possibilities to just 8 of the (131, 139) totals, and so the required answer is 139.

      Here is an example scenario:

      1900, 1940: 4→5
      1901, 1939: 1
      1902, 1938: 2→3
      1903, 1937: 1
      1904, 1936: 8
      1905, 1935: 5
      1906, 1934: 2
      1907, 1933: 1
      1908, 1932: 2→3
      1909, 1931: 1
      1910, 1930: 10
      1911, 1929: 3
      1912, 1928: 2
      1913, 1927: 1
      1914, 1926: 2→3
      1915, 1925: 5
      1916, 1924: 2
      1917, 1923: 3
      1918, 1922: 2
      1919, 1921: 1
      1920: 15
      total: 131→139


      Although my program is a generic solution, there are some handy shortcuts that make a manual solution easier:

      We only need to consider the leftmost 21 values (as the arrangement is symmetric), and there are 6 values that can only have the value 1 (i.e. the two years in question are co-prime), and this gives the 12 columns containing just one coin, and so none of the other columns can have the value 1.

      And this leaves gives a further 5 columns that now only have a single candidate value, and one of them must have the value 5, which means column for 1920 (which must be the largest value) can only be 15.

      Of the remaining 8 columns with multiple candidate values, only 4 of them contain consecutive values, and these give the columns that must differ between the two sequences, so all non-consecutive values can be removed from them.

      Of the four columns with consecutive values, only one of them has a choice of values:

      1908/1932 = 2→3 or 3→4

      So this must be the column we are told that gives us a unique final tally.

      We can consider the two cases:

      [A] = [4→5; 1; 2→3; 1; {2, 4, 8}; {3, 5}; 2; 1; 2→3; 1; {2, 5, 10}; 3; {2, 4, 8}; 1; 2→3; 5; {2, 4}; 3; 2; 1; 15]

      [B] = [4→5; 1; 2→3; 1; {2, 4, 8}; {3, 5}; 2; 1; 3→4; 1; {2, 5, 10}; 3; {2, 4, 8}; 1; 2→3; 5; {2, 4}; 3; 2; 1; 15]

      In the first case the minimum tally for the first sequence is 99 and the maximum is 147.

      So the only prime values in this range where (n + 8) is also prime are: 101 and 131.

      101 is the minimum tally +2, so cannot be made from this case, so it seems likely that being told the 1908/1932 column goes from 2→3 means the final tally goes from 131→139, and this provides the answer.

      We just need to show that there is an initial sequence that gives 131 and that the second case gives rise to more than one tally.

      It is easy enough to find an initial sequence that give 131 (the minimum tally +16), here are all 8:

      [4, 1, 2, 1, 2, 3, 2, 1, 2, 1, 10, 3, 8, 1, 2, 5, 4, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 2, 5, 2, 1, 2, 1, 10, 3, 8, 1, 2, 5, 2, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 4, 3, 2, 1, 2, 1, 10, 3, 8, 1, 2, 5, 2, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 4, 5, 2, 1, 2, 1, 10, 3, 4, 1, 2, 5, 4, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 8, 3, 2, 1, 2, 1, 10, 3, 2, 1, 2, 5, 4, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 8, 3, 2, 1, 2, 1, 10, 3, 4, 1, 2, 5, 2, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 8, 5, 2, 1, 2, 1, 2, 3, 8, 1, 2, 5, 4, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 8, 5, 2, 1, 2, 1, 10, 3, 2, 1, 2, 5, 2, 3, 2, 1, 15] → 131

      In the second case the range is 101 .. 149, which gives primes of: 101, 131, 149.

      So immediately we see a sequence that gives a tally of 101, as this is the minimum possible:

      [4, 1, 2, 1, 2, 3, 2, 1, 3, 1, 2, 3, 2, 1, 2, 5, 2, 3, 2, 1, 15] → 101

      And a tally of 149 is also straightforward, as it is the maximum possible:

      [4, 1, 2, 1, 8, 5, 2, 1, 3, 1, 10, 3, 8, 1, 2, 5, 4, 3, 2, 1, 15] → 149

      And this is enough to solve the puzzle, but for completeness here are the 3 sequences that give 131 (min +15):

      [4, 1, 2, 1, 4, 5, 2, 1, 3, 1, 5, 3, 8, 1, 2, 5, 4, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 8, 3, 2, 1, 3, 1, 5, 3, 8, 1, 2, 5, 2, 3, 2, 1, 15] → 131
      [4, 1, 2, 1, 8, 5, 2, 1, 3, 1, 5, 3, 4, 1, 2, 5, 4, 3, 2, 1, 15] → 131

      And together these are the 13 sequences found by my program.

      Like

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