Recent Updates Page 52 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 8:10 am on 28 June 2020 Permalink | Reply
    Tags: by: Colin   

    Brainteaser 1822: Teasing triangles 

    From The Sunday Times, 17th August 1997 [link]

    Each of the ten line segments in the picture have been given a different whole number value from 0 to 9. A figure shown in a triangle equals the sum of the three values assigned to the sides of the triangle. The figures which have not been given from the other two triangles are the same as each other and the two add together to give a perfect square.

    What are the numbers assigned to each side?

    This puzzle is included in the book Brainteasers (2002). The puzzle text above is taken from the book.

    [teaser1822]

     
    • Jim Randell's avatar

      Jim Randell 8:11 am on 28 June 2020 Permalink | Reply

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

      The following run file executes in 96ms.

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      # regions given
      "A + F + G = 9"
      "E + F + H = 7"
      "C + I + J = 23"
      
      # remaining regions have the same value...
      "B + G + J == D + H + I"
      # ... and sum to a square
      "is_square(B + D + G + H + I + J)"
      
      # suppress verbose output
      --template=""
      

      Solution: The numbers assigned to each line are shown below:

      Like

      • Jim Randell's avatar

        Jim Randell 12:38 pm on 30 June 2020 Permalink | Reply

        The puzzle as originally published in The Sunday Times was worded as follows:

        Each of the ten line segments in the picture have been given a different whole number value from 0 to 9. A figure shown in a triangle equals the sum of the three values assigned to the sides of the triangle. The figures which have not been given from the other two triangles when added together to give 30. What is the value assigned to CD?

        CD being the external edge of the 23 triangle (which is the value C in my solution).

        We can adjust the run-file accordingly:

        #! python -m enigma -rr
        
        SubstitutedExpression
        
        # regions given
        "A + F + G = 9"
        "E + F + H = 7"
        "C + I + J = 23"
        
        # the other two triangles added together to give 30
        "B + D + G + H + I + J = 30"
        
        --answer="C"
        
        # suppress verbose output
        --template=""
        

        And we find that there are 24 ways to fill out the numbers, but the value on the required edge is always 9.

        Like

    • GeoffR's avatar

      GeoffR 4:09 pm on 29 June 2020 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % using the vertex labels A,B,C,D and E (as original diagram in link)
      % plus F for the central point
      
      % outside edges of the pentagon
      var 0..9:AB; var 0..9:BC; var 0..9:CD; var 0..9:DE; var 0..9:EA;
      
      % internal lines to central point F
      var 0..9:AF; var 0..9:BF; var 0..9:CF; var 0..9:DF; var 0..9:EF;
      
      constraint all_different ([AB, BC, CD, DE, EA, AF, BF, CF, DF, EF]);
      
      % three given triangles are AEF, ABF and CDF
      constraint AF + EF + EA == 7;
      constraint AB + AF + BF == 9;
      constraint CF + DF + CD == 23;
      
      % the other two triangle sides (BCF and DEF) are the same total 
      % and the two add together to give a perfect square
      
      constraint DE + EF + DF == BF + CF + BC;
      
      set of int: sq = {16, 25, 36, 49, 64, 81};
      constraint (DE + EF + DF + BF + CF + BC) in sq;
      
      solve satisfy;
      
      % AB = 0; BC = 3; CD = 6; DE = 5; EA = 1;
      % AF = 2; BF = 7; CF = 8; DF = 9; EF = 4;
      % % time elapsed: 0.06 s
      % ----------
      % ==========
      
      

      Like

    • GeoffR's avatar

      GeoffR 1:46 pm on 8 July 2020 Permalink | Reply

      My solution below is updated for the original Sunday Times description, the solution above being the solution being based on the teaser in the book Brainteasers (2002, edited by Victor Bryant).

      
      % A Solution in MiniZinc - original Sunday Times teaser
      include "globals.mzn";
       
      % using the vertex labels A,B,C,D and E (as original ST diagram in link)
      % plus F for the central point
       
      % outside edges of the pentagon
      var 0..9:AB; var 0..9:BC; var 0..9:CD; var 0..9:DE; var 0..9:EA;
       
      % internal lines to central point F
      var 0..9:AF; var 0..9:BF; var 0..9:CF; var 0..9:DF; var 0..9:EF;
       
      constraint all_different ([AB, BC, CD, DE, EA, AF, BF, CF, DF, EF]);
       
      % three given triangles are AEF, ABF and CDF
      constraint AF + EF + EA == 7;
      constraint AB + AF + BF == 9;
      constraint CF + DF + CD == 23;
      
      % for figures in triangles not given ie triangles DEF and BCF
      constraint DE + EF + DF + BF + CF + BC == 30; 
       
      solve satisfy;
      output ["CD = " ++ show(CD) ];
      
      % CD = 9
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 5:08 pm on 26 June 2020 Permalink | Reply
    Tags:   

    Teaser 3014: Family business 

    From The Sunday Times, 28th June 2020 [link] [link]

    George and Martha run a company with their five daughters. The telephone extensions all have four positive unequal digits and strangely only four digits appear in the seven extensions:

    Andrea: ABCD
    Bertha: ACDB
    Caroline: BACD
    Dorothy: DABC
    Elizabeth: DBCA
    George: CABD
    Martha: CDAB

    They noticed the following:

    Andrea’s and Bertha’s add up to Dorothy’s.
    Bertha’s and Elizabeth’s add up to George’s.
    Caroline’s and Dorothy’s add up to Martha’s.

    What is Andrea’s extension?

    [teaser3014]

     
    • Jim Randell's avatar

      Jim Randell 5:14 pm on 26 June 2020 Permalink | Reply

      Any one of the expressions is sufficient to determine the answer.

      I used the [[ SubstitutedExpression.split_sum() ]] solver from the enigma.py library to evaluate the alphametic expressions.

      The following run file executes in 77ms. (Internal runtime of the generated code is just 39µs).

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      --invalid="0,ABCD"
      
      "ABCD + ACDB = DABC"
      "ACDB + DBCA = CABD"
      "BACD + DABC = CDAB"
      
      --answer="ABCD"
      

      Solution: Andrea’s extension is 2385.


      Here’s my manual solution:

      We can split each of the three sums into columns to give us partial equations, that may have a carry out or a carry in.

      B + D = C appears at both the left- and right-hand side of a sum, so can’t have a carry in or a carry out, so it is a complete equation.

      We then see that the partial sum BC + CD = AB cannot have a carry in, so the sum A + B = D is also a complete equation.

      The sum A + A = D cannot have a carry out, and if it does not have a carry in then we deduce that B = A, which is not possible. So it does have a carry in, and B = A + 1 is a complete equation.

      Using these three complete equations we derive:

      B = A + 1
      D = 2A + 1
      C = 3A + 2

      For digits in the range 1-9 the only viable values are:

      A = 1, B = 2, C = 5, D = 3
      A = 2, B = 3, C = 8, D = 5

      Only one of these assignment works in the original three sums, so we have the answer.

      Or, we can use the partial equation BC + CD = AB, which we now know must have a carry out, to give a fourth complete equation: 9B + 11C + D = 10A + 100. We can then substitute in the other equations to get the value of A, and from that the remaining values:

      9B + 11C + D = 10A + 100
      9(A + 1) + 11(3A + 2) + (2A + 1) = 10A + 100
      44A + 32 = 10A + 100
      34A = 68

      A = 2, B = 3, C = 8, D = 5

      Like

    • GeoffR's avatar

      GeoffR 9:19 am on 27 June 2020 Permalink | Reply

      I simulated a normal addition sum in MiniZinc for the first equation to find an answer.

      The values obtained confirmed the other two equations.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      % Using the first equation only ie Andrea + Bertha = Dorothy
      % 
      %  A B C D
      %  A C D B
      %  -------
      %  D A B C
      %  -------
      
      var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D;
      var 0..1:carry1; var 0..1:carry2; var 0..1:carry3; 
      
      constraint C == (B + D) mod 10 
      /\ carry1 = (B + D) div 10;
      
      constraint B = (carry1 + C + D) mod 10 /\ 
      carry2 = (carry1 + C + D) div 10;
      
      constraint A = (carry2 + B + C) mod 10
      /\ carry3 = (carry2 + B + C) div 10;
      
      constraint D = carry3 + 2*A;
      
      solve satisfy;
      
      output ["A,B,C,D = " ++ show([A,B,C,D]) ];
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 2:41 pm on 27 June 2020 Permalink | Reply

      A short Python programme confirms that the second and third equations give the same value for Andrea’s extension as my MiniZinc programme for the first equation:

      
      from itertools import permutations
      
      # Bertha’s and Elizabeth’s extensions add up to George’s extn.
      # 2nd Equation: ACDB + DBCA = CABD
      
      for p in permutations((1,2,3,4,5,6,7,8,9),4):
          a, b, c, d = p
          acdb = 1000*a + 100*c + 10*d + b
          dbca = 1000*d + 100*b + 10*c + a
          cabd = 1000*c + 100*a + 10*b + d
          if acdb + dbca == cabd:
              # Find Andrea's extension
              abcd = 1000*a + 100*b + 10*c + d
              print("2. Andrea's extension =", abcd)
      
      # Caroline’s and Dorothy’s extensions add up to Martha’s extn.
      # 3rd Equation : BACD + DABC = CDAB
      
      for p in permutations((1,2,3,4,5,6,7,8,9),4):
          a, b, c, d = p
          bacd = 1000*b + 100*a + 10*c + d
          dabc = 1000*d + 100*a + 10*b + c
          cdab = 1000*c + 100*d + 10*a + b
          if bacd + dabc == cdab:
              # Find Andrea's extension
              abcd = 1000*a + 100*b + 10*c + d
              print("3. Andrea's extension =", abcd)
      
      

      Like

  • Unknown's avatar

    Jim Randell 7:16 am on 25 June 2020 Permalink | Reply
    Tags:   

    Brain-Teaser 1: Tall story 

    From The Sunday Times, 26th February 1961 [link]

    “We’re a biggish family in more ways than one”, said Jeremy. “Five, including triplets, and three of us the same height. I’m 6 ft. and aged 72; Jim is three inches taller than John and three years older; and John and Julian’s combined heights are 5 ft. 11 in. more than Joe’s and their combined ages 71 years more than his. Our aggregate height in inches equals our aggregate age in years, but no one’s age in years equals Joe’s height in inches”.

    What were the name, age and height of the tallest of the triplets?

    This was the first of the regular Teaser puzzles published in The Sunday Times. It was accompanied by the following introduction:

    Brain-Teasers: a Weekly Feature

    Great numbers of our readers have found entertainment and interest in the Brain-Teasers, or mathematical problems, which we have published from time to time, usually at holiday week-ends. Now we intend to make them a weekly feature of The Sunday Times.

    Readers themselves have supplied many of the best of the problems in the past, and we invite them to continue to do so. A fee of £10 will be paid for each problem accepted.

    Problems should fulfil the following conditions: Both the problem and the solution must be expressible in reasonably brief compass. No advanced or specialist mathematical techniques should be necessary. Solutions should be unique, or otherwise admit of no uncertainty in judging. Problems must be original. Diagrams are admissible if they are not too complicated.

    The problem below is the invention of a reader who observes: “Any who are stumped by it after the expiry of an hour should feel cause for concern”.

    A prize of £3 was offered.

    [teaser1]

     
    • Jim Randell's avatar

      Jim Randell 7:17 am on 25 June 2020 Permalink | Reply

      We can associate values (A, B, C, D, E) with (Jez, Jim, Jon, Jul, Joe), that correspond to either their heights in inches or their ages in years, both give rise to the same set of equations:

      A = 72
      B = C + 3
      C + D = E + 71
      x = y = z

      where (x, y, z) are some three of (A, B, C, D, E).

      This gives us a set of 5 simultaneous equations in 5 variables.

      This Python program finds solutions to these equations (using the [[ Matrix.linear() ]] solver from the enigma.py library), groups the solutions by the sum of the set of values, and then looks for two sets with the same sum (one set for the ages, and another set for the height) that satisfy the condition that Joe’s height does not appear in the set of ages.

      It runs in 55ms.

      Run: [ @replit ]

      from enigma import (Matrix, subsets, as_int, multiset, group, printf)
      
      # labels for the people involved
      # A = Jez, B = Jim, C = Jon, D = Jul, E = Joe
      labels = (A, B, C, D, E) = (0, 1, 2, 3, 4)
      
      # solve equations specified as: (ps, ms, k)
      # for integer solutions
      def solve(eqs):
        (A, B) = (list(), list())
        for (ps, ms, k) in eqs:
          eq = [0] * 5
          for (i, n) in multiset.from_seq(ps).items(): eq[i] += n
          for (i, n) in multiset.from_seq(ms).items(): eq[i] -= n
          A.append(eq)
          B.append(k)
        try:
          # find values that are positive integers
          return Matrix.linear(A, B, valid=(lambda x: as_int(x, include='+')))
        except ValueError:
          return
      
      # find values that satisfy the equations
      def generate():
        # choose the three with the same value
        for (x, y, z) in subsets(labels, size=3):
          # solve the equations
          r = solve([
            ([A], [], 72), # A = 72
            ([B], [C], 3), # B = C + 3
            ([C, D], [E], 71), # C + D = E + 71
            ([x], [y], 0), # x = y
            ([y], [z], 0), # y = z
          ])
          if r is None: continue
          # check there is a set of triplets
          if not (3 in multiset.from_seq(r).values()): continue
          # return candidate solutions
          yield r
      
      # group sets of solutions by the sum of the values
      d = group(generate(), by=sum)
      
      # consider values for the sum
      for (t, rs) in d.items():
        # choose two sets of values for the ages and heights
        for (age, height) in subsets(rs, size=2, select="M"):
          # no one's age is the same as Joe's height
          if height[E] in age: continue
          for (n, a, h) in zip("Jez Jim Jon Jul Joe".split(), age, height):
            printf("{n}: age {a} yr, height {h} in")
          printf()
      

      Solution: Jim is the tallest of the triplets. His age is 72, his height is 6ft 2in.

      The complete characteristics are:

      Jez: age 72 yr, height 72 in
      Jim: age 72 yr, height 74 in
      Jon: age 69 yr, height 71 in
      Jul: age 74 yr, height 71 in
      Joe: age 72 yr, height 71 in

      So: Jeremy, Jim, Joe are the triplets (same age), and: John, Julian, Joe are the same height.

      There are 7 ways to solve the equations, but there is only one pair that has the same sum, and this gives rise to the solution.

      Like

    • GeoffR's avatar

      GeoffR 4:10 pm on 25 June 2020 Permalink | Reply

      I found three solutions in MinZinc.

      The first solution was invaiid as there was no single maximum height.

      The second solution was the same as Jim’s solution above.

      There appears to be another solution, which gives the maximum height of Jim as 6ft 3 in, with an age of 75 years. If the puzzle had stated that age and height could not be the same, then this last solution could be ruled out – it appears to conform to the stated conditions in the puzzle.

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 10..99: JezAge; var 10..99: JimAge; var 10..99: JonAge; 
      var 10..99: JulAge; var 10..99: JoeAge; 
      
      var 50..80: JezHt; var 50..80: JimHt; var 50..80: JonHt; 
      var 50..80: JulHt; var 50..80: JoeHt; 
      
      % Three of us the same height
      % (A,B,C,D,E) = (Jez, Jim, Jon, Jul, Joe)
      constraint (JezHt == JimHt /\ JimHt == JonHt)  % ABC
      \/ (JezHt == JimHt /\ JimHt == JulHt)   % ABD
      \/ (JezHt == JimHt /\ JimHt == JoeHt)   % ABE
      \/ (JezHt == JonHt /\ JonHt == JulHt)   % ACD
      \/ (JezHt == JonHt /\ JonHt == JoeHt)   % ACE
      \/ (JezHt == JulHt /\ JulHt == JoeHt)   % ADE
      \/ (JimHt == JonHt /\ JonHt == JulHt)   % BCD
      \/ (JimHt == JonHt /\ JonHt == JoeHt)   % BCE
      \/ (JimHt == JulHt /\ JulHt == JoeHt)   % BDE
      \/ (JonHt == JulHt /\ JulHt == JoeHt);  % CDE
      
      % Three of us the same age(triplets)
      constraint (JezAge == JimAge /\ JimAge == JonAge)  % ABC
      \/ (JezAge == JimAge /\ JimAge == JulAge)   % ABD
      \/ (JezAge == JimAge /\ JimAge == JoeAge)   % ABE
      \/ (JezAge == JonAge /\ JonAge == JulAge)   % ACD
      \/ (JezAge == JonAge /\ JonAge == JoeAge)   % ACE
      \/ (JezAge == JulAge /\ JulAge == JoeAge)   % ADE
      \/ (JimAge == JonAge /\ JonAge == JulAge)   % BCD
      \/ (JimAge == JonAge /\ JonAge == JoeAge)   % BCE
      \/ (JimAge == JulAge /\ JulAge == JoeAge)   % BDE
      \/ (JonAge == JulAge /\ JulAge == JoeAge);  % CDE
      
      % Jez is 6 ft. and aged 72
      constraint JezHt == 72 /\ JezAge == 72;
      
      % Jim is three inches taller than John and three years older
      constraint JimHt - 3 == JonHt /\ JimAge - 3 == JonAge;
      
      %  John and Julian’s combined heights are 5 ft. 11 in. more than Joe’s
      %  and their combined ages 71 years more than his
      constraint JonHt + JulHt - 71 == JoeHt;  
      constraint JonAge + JulAge - 71 == JoeAge;
      
      % Our aggregate height in inches equals our aggregate age in years
      constraint  JimHt + JezHt + JulHt + JonHt + JoeHt 
      == JimAge + JezAge + JulAge  + JonAge + JoeAge;
      
      % No one’s age in years equals Joe’s height in inches
      constraint JoeHt != JezAge /\ JoeHt != JimAge
      /\ JoeHt != JonAge /\ JoeHt != JulAge;
      
      solve satisfy;
      
      output ["Ages: " ++ show([JezAge, JimAge, JonAge, JulAge, JoeAge])]++
      ["\nHeights: " ++ show([JezHt, JimHt, JonHt, JulHt, JoeHt])];
      
      %           Jez Jim Jon Jul Joe
      % Ages:    [72, 72, 69, 72, 70]  << no single tallest member 
      % Heights: [72, 72, 69, 72, 70]  << Therefore, an invalid solution
      % ----------
      % Ages:    [72, 72, 69, 74, 72]  <<  Jim is tallest member at 6 ft. 2 in.
      % Heights: [72, 74, 71, 71, 71]  <<  with an age of 72 years
      % ----------
      % Ages:    [72, 75, 72, 72, 73]  << Jim is tallest member at 6 ft. 3 in.
      % Heights: [72, 75, 72, 72, 73]  <<  with an age of 75 years
      % ----------
      % ==========
      
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:15 pm on 25 June 2020 Permalink | Reply

        @Geoff: The condition “no one’s age in years equals Joe’s height in inches”, means that Joe’s age is not equal to Joe’s height. So this disposes of two of your solutions. (And effectively means that the same set of solutions cannot be used for both ages and heights).

        Like

    • GeoffR's avatar

      GeoffR 4:38 pm on 25 June 2020 Permalink | Reply

      @Jim: Fair point. I was interpreting only other people’s ages only could not be equal to Joe’s height. It could have been more clearly stated that this condition also applied to Joe himself!

      Like

    • Frits's avatar

      Frits 3:51 pm on 28 July 2020 Permalink | Reply

      from enigma import SubstitutedExpression, icount #, multiset
      
      # suppose Jez not part of three --> Also either Jon or Jim not part of three 
      #   --> Joe and Jul part of three --> Jez = 72, Jon = 71, Jim 74, Joe = 71, Jul = 71 or
      #       (Jon has to be 71)            Jez = 72, Jon = 71, Jim 74, Joe = 74, Jul = 74 
      # suppose Jez part of three 
      #   --> if both Jim and Jon not part of three --> Jon = 71, Jim = 74
      #       else Jim or Jon is 72 --> Jez = 72, Jon = 72, Jim 75, Jul == Joe - 1 or
      #                                 Jez = 72, Jon = 69, Jim 72, Jul == Joe + 2
      # --> Joe 70-74
      # --> Jim 72,74,75
      # --> Jul 71,72,74
      # --> Jon 69,71,72
          
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          #  Jez Age    Joe Age    Jim Age    Jul Age    Jon Age
          #    AB         CD         EF         GH         IJ     
          #  Jez Hght   Joe Hght   Jim Hght   Jul Hght   Jon Hght
          #    KL         MN         OP         QR         ST   
          
          # Jez is 6 ft. and aged 72
          "AB = 72", "KL = 72",
        
          # Jim is three inches taller than John and three years older
          "IJ + 3 == EF" ,"ST + 3 = OP",
         
          # Jul and Jon's combined heights are 5 ft 11 in more than Joe's
          # and their combined ages 71 years more than his
          "QR + ST == MN + 71", "GH + IJ == CD + 71",
          
          # aggregate height in inches equals aggregate age in years
          "AB + CD + EF + GH + IJ == KL + MN + OP + QR + ST",
          
          # No one's age in years equals Joe's height in inches
          "AB != MN", "CD != MN", "EF != MN", "GH != MN", "IJ != MN",
          
          # Three have the same age, three have same height
          "icount([AB, CD, EF, GH, IJ], (lambda d: v([AB, CD, EF, GH, IJ]) == d)) == 3",
          "icount([KL, MN, OP, QR, ST], (lambda d: v([KL, MN, OP, QR, ST]) == d)) == 3",
          #"sorted(multiset.from_seq([AB, CD, EF, GH, IJ]).values()) == [1, 1, 3]",
          #"sorted(multiset.from_seq([KL, MN, OP, QR, ST]).values()) == [1, 1, 3]",
          
          # ----------------------------
          # try to speed up 
          # ----------------------------
          
          #"CD in ([70,71,72,73,74])",
          #"EF in ([72,74,75])",
          #"GH in ([71,72,74])",
          #"IJ in ([69,71,72])",
          
          #"MN in ([70,71,72,73,74])",
          #"OP in ([72,74,75])",
          #"QR in ([71,72,74])",
          #"ST in ([69,71,72])",
        ],
        distinct="", 
        code="v = lambda x: max(x,key=x.count)",
        answer="AB, CD, EF, GH, IJ, KL, MN, OP, QR, ST"
      )      
                                
      # Print answers
      
      for (_, ans) in p.solve(verbose=0): 
         print("        Age        |       Height") 
         print("Jez Joe Jim Jul Jon Jez Joe Jim Jul Jon")
         print(ans)
         
      # Output:   
      #         Age        |       Height
      # Jez Joe Jim Jul Jon Jez Joe Jim Jul Jon
      # (72, 72, 72, 74, 69, 72, 71, 74, 71, 71) 
      

      Remarks regarding speed:

      "IJ + 3 == EF" ,"ST + 3 = OP",  # Approx 1.3 secs
      "IJ + 3 = EF" ,"ST + 3 == OP",  # Approx 14 secs
      "IJ + 3 = EF" ,"ST + 3 = OP",   # Approx 14 secs
      "IJ + 3 == EF" ,"ST + 3 == OP", # SyntaxError: too many statically nested blocks
      

      The speed up logic I had to invent before I discovered the “IJ + 3 == EF” ,”ST + 3 = OP” equations.
      In theory the multiset equations are too restrictive

      Like

  • Unknown's avatar

    Jim Randell 8:52 am on 23 June 2020 Permalink | Reply
    Tags:   

    Teaser 2525: [Sandwich numbers] 

    From The Sunday Times, 13th February 2011 [link] [link]

    “Sandwich numbers” are those of the form BFB, where B is the bread and is a single digit, and F is the filling of any length: BFB has to be divisible by F. For example, 11371 is a sandwich with filling 137 since 11371 = 83 × 137. Incidentally, all of 21372, 31373, … , 91379 are also sandwich numbers, and these nine are said to make up a “sandwich box”.

    The filling in my sandwich box today is the smallest beginning with a 3.

    What is my filling?

    This puzzle was originally published with no title.

    [teaser2525]

     
    • Jim Randell's avatar

      Jim Randell 8:58 am on 23 June 2020 Permalink | Reply

      My first thought was to generate integers that start with a 3, and then check to see if they make a “sandwich box”. But this was taking a long time, so here’s some analysis to produce a faster program:

      If f is an n digit “filling” number, then the corresponding sandwich numbers in a sandwich box are:

      b.10^(n + 1) + 10.f + b

      for b = 1 .. 9. And each of these must be divisible by f.

      Clearly f will divide the 10.f part, which leaves b(10^(n + 1) + 1), when b = 1, we must have f divides (10^(n + 1) + 1), and if this is the case f will divide the remaining sandwich numbers with b > 1.

      So, f must be a divisor of (10^(n + 1) + 1), and we are looking for the smallest f that starts with a 3.

      f.g = 10^(n + 1) + 1

      and f is an n digit number that starts with 3, so g = 26 .. 33.

      This Python program runs in 49ms. (Internal runtime is 46µs).

      Run: [ @replit ]

      from enigma import (irange, inf, printf)
      
      def solve():
        # consider n digit fillings
        x = 101
        for n in irange(1, inf):
          # f is an n digit divisor of x
          for g in irange(33, 26, step=-1):
            (f, r) = divmod(x, g)
            if r == 0:
              yield f
          x *= 10
          x -= 9
      
      for f in solve():
        printf("f = {f}")
        break
      

      Solution: The smallest filling beginning with 3 is: f = 3448275862069.

      Where: 29f = 10^14 + 1.

      So we see why the simple program was taking so long. (In the end it took 32 minutes).

      In fact there is a family of filling numbers that start with 3 that take the form:

      344827586206 [8965517241379310344827586206] 9

      where the section in brackets can be included 0 or more times, to give fillings with length (13 + 28k) digits.

      We can adapt the program to generate all filling numbers without the restriction that the leading digit is 3.

      from enigma import (irange, inf, match, printf)
      
      # generate "filling" numbers
      def filling():
        # consider n digit fillings
        x = 101
        for n in irange(1, inf):
          # f is an n digit divisor of x
          M = x // 10
          for g in irange(101, 11, step=-1):
            (f, r) = divmod(x, g)
            if r == 0 and f < M <= 10 * f:
              yield f
          x *= 10
          x -= 9
      
      for f in filling():
        printf("f = {f}")
        if match(f, '3*'): break
      

      See: OIES A116436 [ @oeis.org ]

      Like

  • Unknown's avatar

    Jim Randell 9:48 am on 21 June 2020 Permalink | Reply
    Tags:   

    Brain-Teaser 523: [Alphabet cricket] 

    From The Sunday Times, 20th June 1971 [link]

    The Alphabet Cricket Club (26 playing members, every surname having a different initial letter) ran into trouble last season. The selected 1st XI consisted of those with initials A to K; the 2nd XI was to be picked from the remainder, but personal feeling had crept in. (To save space, players are referred to by initials).

    U refused to play in the same team as Z. Q said that he would not be available if either L or M was picked. P announced that he would not turn out (a) unless M was in the side, (b) if either O or W played. Z declined to play in an XI containing more than one of R, S, T. Finally, V agreed to play only with Q.

    The secretary managed to pick an XI but then a further complication arose: two of the 1st XI fell ill and two of the selected 2nd XI were taken to fill the gaps. Fortunately the secretary was still able to field a 2nd XI (which, incidentally, won, M making a century).

    Who were the two promoted to the 1st XI, and who were omitted altogether?

    This puzzle was originally published with no title.

    [teaser523]

     
    • Jim Randell's avatar

      Jim Randell 9:49 am on 21 June 2020 Permalink | Reply

      This Python program runs in 52ms.

      from enigma import (subsets, intersect, diff, join, printf)
      
      players = "LMNOPQRSTUVWXYZ"
      
      # generate possible 2nd XI's
      def generate():
        for ps in subsets(players, size=11):
      
          # verify conditions:
      
          # "U refused to play in the same team as Z"
          if 'U' in ps and 'Z' in ps: continue
      
          # "Q said that he would not be available if either L or M was picked"
          if 'Q' in ps and ('L' in ps or 'M' in ps): continue
      
          # "P announced that he would not turn out (a) unless M was in the
          # side, (b) if either O or W played"
          if 'P' in ps and ('M' not in ps or 'O' in ps or 'W' in ps): continue
      
          # "Z declined to play in an XI containing more than one of R, S, T"
          if 'Z' in ps and len(intersect((ps, "RST"))) > 1: continue
      
          # "V agreed to play only with Q"
          if 'V' in ps and 'Q' not in ps: continue
      
          yield ps
      
      # choose two sets for the second eleven
      for (s1, s2) in subsets(generate(), size=2, select="P"):
      
        # M is in the final set
        if 'M' not in s2: continue
      
        # the two promoted are in the first but not the second
        ps = diff(s1, s2)
        if len(ps) != 2: continue
      
        # the ones who were never picked
        ns = diff(players, s1 + s2)
        printf("promoted={ps} omitted={ns} [{s1} -> {s2}]", ps=join(ps), ns=join(ns), s1=join(s1), s2=join(s2))
      
      

      Solution: Q and V were promoted. P and Z were omitted.

      The original 2nd XI was: N O Q R S T U V W X Y.

      And then Q and V were promoted to the 1st XI. L and M stepped in to take their places.

      So the actual 2nd XI was: L M N O R S T U W X Y.

      P and Z were omitted from both line-ups.

      V will play only with Q, so it makes sense that Q and V were promoted together. (Although I didn’t check that the requirements for the 2nd XI were satisfied by the new 1st XI, but in this case they would be).

      These are the only two possible line-ups allowed for the 2nd XI for the given conditions, so the one with M in must be the actual line-up.

      Like

    • John Crabtree's avatar

      John Crabtree 8:06 pm on 25 June 2020 Permalink | Reply

      Four players were initially not picked.
      From the two statements involving Z, if Z plays, U does not play and at least two of R, S and T do not play.

      The two statements involving Q may be written as:
      Q + ~Q.~V = 1
      Q.~L.~M + ~Q = 1
      Combining them gives Q.~L.~M + ~Q.~V = 1
      Either L and M do not play, or Q and V do not play, and so Z does not play,
      The statements involving P may be written as P.M.~O.~W + ~P = 1
      If P plays, Q, V, O, W and Z do not play. And so P does not play.

      And so Q and V were promoted. P and Z did not play.
      L and M were brought in to the 2nd XI as replacements.

      Like

  • Unknown's avatar

    Jim Randell 7:06 pm on 19 June 2020 Permalink | Reply
    Tags:   

    Teaser 3013: Arian Pen-blwydd 

    From The Sunday Times, 21st June 2020 [link] [link]

    When I thought that my daughter was old enough to be responsible with money I gave her on her next, and all subsequent birthdays, cash amounts (in pounds) which were equal to her birthday age squared.

    On her last birthday her age was twice the number of years for which she received no such presents. I calculated at this birthday that if I had made these gifts on all of her birthdays then she would have received 15% more than she had actually received. I then decided that I would stop making the payments after her birthday when she would have received only 7.5% more if the payments had been made on all of her birthdays.

    What was the amount of the final birthday payment?

    There are now 300 puzzles available on the site.

    [teaser3013]

     
    • Jim Randell's avatar

      Jim Randell 9:43 pm on 19 June 2020 Permalink | Reply

      If she didn’t receive gifts for the first k years, then the “missing” gifts are the sum of the squares of 1 .. k. The amounts actually received are the sum of the squares of (k + 1) .. 2k.

      This Python program finds the value of k when the amount of the missing gifts is 15% of the actual amount, and then continues looking at future gifts until the amount becomes 7.5%.

      It runs in 53ms.

      Run: [ @repl.it ]

      from enigma import irange, inf, printf
      
      # solve the puzzle
      def solve():
        # consider the k years before the gifts started
        for k in irange(1, inf):
          # total before amount
          before = sum(x * x for x in irange(1, k))
          # total after amount
          after = sum(x * x for x in irange(k + 1, 2 * k))
          # before = 0.15 * after
          if not(100 * before == 15 * after): continue
          printf("k = {k}; before = {before}, after = {after}")
      
          # now add in future gifts
          future = after
          for n in irange(2 * k + 1, inf):
            future += n * n
            # before = 0.075 * future
            if not(1000 * before == 75 * future): continue
            printf("-> n = {n}; n^2 = {n2}, future = {future}", n2=n * n)
            return
      
      solve()
      

      Solution: The final gift was £ 1,764.

      The gifts started on the 18th birthday, so the “missing” gifts (years 1 – 17) would amount to £ 1,785.

      The actual gifts between ages 18 and 34 amount to £ 11,900, and 15% of £ 11,900 is £ 1,785.

      The gifts are to continue to age 42, making the total amount £ 23,800, and 7.5% of £ 23,800 is also £ 1,785.

      Which means the final gift is made on the 25th (silver) anniversary of the first gift.


      Analytically:

      (See: Enigma 1086).

      The sum of the first n squares is given by the square pyramidal numbers:

      SP(n) = n (n + 1) (2n + 1) / 6

      So the first part of the puzzle is to solve:

      SP(k) = 0.15 (SP(2k) − SP(k))
      20 SP(k) = 3 (SP(2k) − SP(k))
      23 SP(k) = 3 SP(2k)
      23k (k + 1) (2k + 1) = 6k (2k + 1) (4k + 1)
      23k + 23 = 24k + 6
      k = 17

      The second part is to solve, for n > 34:

      SP(k) = 0.075 (SP(n) − SP(k))
      3 SP(n) = 43 SP(k)
      SP(n) = 25585
      n = 42

      The required answer is then: n² = 42² = 1764.

      Like

      • Jim Randell's avatar

        Jim Randell 11:19 pm on 20 June 2020 Permalink | Reply

        Or, more simply:

        We are looking for values of n, k where:

        SP(k) = (3/43) SP(n)
        SP(2k) = (23/43) SP(n)

        This Python program runs in 51ms.

        Run: [ @repl.it ]

        from enigma import irange, inf, div, printf
        
        # accumulate the square pyramidal numbers, map: SP[n] -> n
        d = dict()
        x = 0
        for n in irange(1, inf):
          # calculate: x = SP(n)
          x += n * n
          d[x] = n
          # can we find a corresponding y = SP(k)?
          y = div(x * 3, 43)
          k = d.get(y, None)
          if k is None: continue
          # and verify z = SP(2k)
          z = div(x * 23, 43)
          k2 = d.get(z, None)
          if k2 is None or k2 != k * 2: continue
          printf("n^2={n2} [n={n} k={k}; SP[{k}]={y} SP[{k2}]={z} SP[{n}]={x}]", n2=n * n)
          break
        

        Like

  • Unknown's avatar

    Jim Randell 7:12 am on 18 June 2020 Permalink | Reply
    Tags:   

    Brainteaser 1819: Early bath 

    From The Sunday Times, 27th July 1997 [link]

    There are 20 teams in one country’s premier league. They each play each other once in the first half of the season, and then they each play each other a second time in the rest of the season. Each team plays each Saturday of the season, earning three points for a win and one point for a draw. At the end of the season the bottom three teams are relegated and the top team wins the league championship.

    Last season was the most boring ever. It was possible to determine the relegated teams well before the end of the season. In fact it would be impossible in any season to be able to determine the three relegated teams in fewer weeks.

    Three further Saturdays after the relegations were determined the league championship was also determined when the league leaders were in a 0-0 draw and then found that they were unassailable. There were great celebrations that night.

    At that time, how many points did the current top two teams have?

    This puzzle is included in the book Brainteasers (2002). The puzzle text above is taken from the book.

    [teaser1819]

     
    • Jim Randell's avatar

      Jim Randell 7:12 am on 18 June 2020 Permalink | Reply

      After a tortuous chain of reasoning we arrive at the answer:

      In the first half of the season there are C(20, 2) = 190 matches, and then another 190 in the second half of the season. Each team plays 19 matches in the first half of the season, and another 19 matches in the second half of the season. The season lasts 38 weeks, with 10 matches played each week.

      At some point there are 3 teams doomed to relegation, as they cannot possibly catch up with any of the remaining 17 teams.

      Suppose in the first half of the tournament there are three teams that lose all their matches, except the matches they play amongst themselves, which are drawn.

      Each of the three doomed teams has only 2 points (from their draws with the other two doomed teams). If all the other 187 matches were won outright there are 187×3 = 561 points to distribute between the remaining 17 teams. This gives an average of 33 points per team. But, if one of the doomed teams were to win all their matches in the second half of the tournament they would end up with 2 + 3×19 = 59 points, so their relegation is not guaranteed by the end of the first half of the tournament.

      If we carry on for k weeks into the second half of the tournament, with each doomed team losing each of their matches, and each of the other matches being won, then how many weeks is it before enough points are accumulated, so that each of the 17 remaining teams are out of reach?

      There are (19 − k) weeks remaining and a doomed team could win all their remaining matches, so they would end with 2 + 3(19 − k) = 59 − 3k points.

      And the total number of points to be shared between the 17 remaining teams would be: 561 + 30k.

      This can happen when:

      (561 + 30k) / 17 > 59 − 3k
      561 + 30k > 1003 − 51k
      81k > 442
      k > 5.46…

      i.e. after the 6th week of the second half of the tournament, at the earliest.

      There would then be 13 weeks remaining, so a doomed team that had a sudden change of fortune could finish with 2 + 3×13 = 41 points. But by that stage in the tournament 561 + 30×6 = 741 points could have been scored by the remaining 17 teams, which is enough for each of them to have accumulated at 43 points each. So the doomed teams can indeed be doomed to relegation.

      Now after three more weeks, i.e. after the 9th week of the second half of the tournament, with 10 weeks remaining, the championship was determined. So the team with the most points must be more than 30 points ahead of the next highest team.

      If we go back to 6th week of the second half of the tournament, then 16 of the 17 non-doomed teams could have 42 points, and the other team could have 69 points. So they are 27 points ahead.

      If they win the following 2 weeks, and the week after that they draw (as we are told in the puzzle text), then after the 9th week they have 76 points, and in order to be unassailable the next highest team can have no more than 45.

      So now we need to make sure none of the other 16 teams can get more than 45 points by the end of week 9. An easy way is to suppose all the matches apart from the ones involving the future champions are drawn.

      Then at the end of the 7th week, the best any of the 16 challengers can do is 43 points. The future champions have 72 points, and there are 12 weeks remaining, so they are assailable.

      At the end of the 8th week, the best any of the challengers can do is 44 points. The future champions have 75 points, and there are 11 weeks remaining, so they are still assailable.

      At the end of the 9th week, the best any of the challengers can do is 45 points. The future champions have 76 points, and there are 10 weeks remaining, so they are now unassailable.

      So this is a viable scenario for the puzzle.

      Solution: The top team had 76 points, and the next highest team had 45 points.

      Like

      • John Crabtree's avatar

        John Crabtree 5:14 pm on 19 June 2020 Permalink | Reply

        This teaser just works. After 6 weeks of the second half, 16 teams have 42 points and one has 69, ie a total of 741 for those teams and the maximum possible.

        Like

  • Unknown's avatar

    Jim Randell 8:42 am on 16 June 2020 Permalink | Reply
    Tags:   

    Teaser 2662: Number please 

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

    I finally got through to the operator of a large company and asked to be connected to an appropriate department. He tried eight different extension numbers before then finding the correct one. The eight numbers were 1933, 2829, 3133, 4630, 5089, 5705, 6358 and 6542. Curiously, each of these wrong numbers did have at least one correct digit of the correct extension in the correct position!

    What was the correct extension number?

    [teaser2662]

     
    • Jim Randell's avatar

      Jim Randell 8:42 am on 16 June 2020 Permalink | Reply

      Here is a recursive program that starts with unassigned digits in all positions, and then at each step looks for a number that doesn’t already match and uses it to fill out one of the unassigned digits. It runs in 53ms.

      Run: [ @repl.it ]

      from enigma import (update, join, printf)
      
      # find sequences that match ns in at least one position
      # ds - candidate digits ('?' for unassigned)
      # ns - remaining numbers to check
      def solve(ds, ns):
        # are we done?
        if not ns:
          yield join(ds)
        else:
          # consider the digits of the next number
          ms = list(zip(ds, ns[0]))
          # if any digits already match move on to the next one
          if any(a == b for (a, b) in ms):
            yield from solve(ds, ns[1:])
          else:
            # set one of the unassigned digits
            for (i, (a, b)) in enumerate(ms):
              if a == '?':
                yield from solve(update(ds, [(i, b)]), ns[1:])
      
      # numbers provided
      numbers = "1933 2829 3133 4630 5089 5705 6358 6542".split()
      
      # solve the puzzle, starting with 4 ?'s
      for n in solve("????", numbers):
        printf("number = {n}")
      

      Solution: The correct extension number is 6739.


      A brute force search of all possible extension numbers is shorter, and only slightly slower:

      from enigma import (subsets, join, printf)
      
      # the numbers tried (each has at least one correct digit in the correct position)
      numbers = "1933 2829 3133 4630 5089 5705 6358 6542".split()
      
      # consider digits for the actual number
      for ds in subsets("0123456789", size=4, select="M"):
        if all(any(a == b for (a, b) in zip(ds, n)) for n in numbers):
          printf("number = {n}", n=join(ds))
      

      Like

    • GeoffR's avatar

      GeoffR 10:29 am on 6 October 2020 Permalink | Reply

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 0..9: A; var 0..9: B; var 0..9: C; var 0..9: D;
      
      constraint all_different ([A,B,C,D]);
      
      % Each of these wrong numbers did have at least one correct
      % digit of the correct extension in the correct position
      
      % Extn Try 1 - 1933 
      constraint sum ([A==1,B==9,C==3,D==3]) > 0;
      
      % Extn Try 2 - 2829 
      constraint sum ([A==2,B==8,C==2,D==9]) > 0;
      
      % Extn Try 3 - 3133 
      constraint sum ([A==3,B==1,C==3,D==3]) > 0;
      
      % Extn Try 4 - 4630 
      constraint sum ([A==4,B==6,C==3,D==0]) > 0;
      
      % Extn Try 5 - 5089 
      constraint sum ([A==5,B==0,C==8,D==9]) > 0;
      
      % Extn Try 6 - 5705 
      constraint sum ([A==5,B==7,C==0,D==5]) > 0;
      
      % Extn Try 7 - 6358  
      constraint sum ([A==6,B==3,C==5,D==8]) > 0;
      
      % Extn Try 8 - 6542 
      constraint sum ([A==6,B==5,C==4,D==2]) > 0;
      
      solve satisfy;
      
      output ["The correct extension number is " ++
      show(A),show(B),show(C),show(D) ];
      
      % The correct extension number is 6739
      % % time elapsed: 0.03 s
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

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

    Teaser 2655: Sudoprime 

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

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

    What are the two largest of the eleven primes?

    [teaser2655]

     
    • Jim Randell's avatar

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

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

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

      The following run file executes in 93ms.

      Run: [ @replit ]

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

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

      The completed grid looks like this:

      Like

    • GeoffR's avatar

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

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

      Like

  • Unknown's avatar

    Jim Randell 4:48 pm on 12 June 2020 Permalink | Reply
    Tags:   

    Teaser 3012: Number blind rage 

    From The Sunday Times, 14th June 2020 [link] [link]

    After school, angry at getting “50 lines”, I kicked my satchel around. Impacts made my 11-digit calculator switch on. An 11-digit number was also entered and the display was damaged. Strangely, I found “dYSCALCULIA” displayed and saved this to memory (as shown).

    After various tests I confirmed that all arithmetic operations were correct and the decimal point would appear correctly if needed. No segments were permanently “on”, two digits were undamaged, and for the other digits, overall, several segments were permanently “off”.

    Retrieving “dYSCALCULIA”, I divided it by 9, then the result by 8, then that result by 7, then that result by 6. No decimal point appeared and the last result (at the right-hand side of the display) had three digits appearing as numerals.

    What number was “dYSCALCULIA”?

    [teaser3012]

     
    • Jim Randell's avatar

      Jim Randell 9:07 pm on 12 June 2020 Permalink | Reply

      I used the standard set of digits (as illustrated in Enigma 1701).

      This Python program runs in 90ms.

      from itertools import product
      from enigma import join, div, printf
      
      # normal digits
      normal = "0123456789"
      
      # map digits to illuminated segments, arranged as:
      #
      #   0
      # 1   2
      #   3
      # 4   5
      #   6
      #
      # map digits to segments
      f = lambda *ss: frozenset(ss)
      seg = {
        # normal digits
        '0': f(0, 1, 2, 4, 5, 6),
        '1': f(2, 5),
        '2': f(0, 2, 3, 4, 6),
        '3': f(0, 2, 3, 5, 6),
        '4': f(1, 2, 3, 5),
        '5': f(0, 1, 3, 5, 6),
        '6': f(0, 1, 3, 4, 5, 6), # or could be (1, 3, 4, 5, 6)
        '7': f(0, 2, 5), # or could be (0, 1, 2, 5)
        '8': f(0, 1, 2, 3, 4, 5, 6),
        '9': f(0, 1, 2, 3, 5, 6), # or could be (0, 1, 2, 3, 5)
        # malformed digits
        'a': f(0, 1, 2, 3, 4, 5),
        'c': f(0, 1, 4, 6),
        'd': f(2, 3, 4, 5, 6),
        'l': f(1, 4, 6),
        'u': f(1, 2, 4, 5, 6),
      }
      # map segments to normal digits
      norm = dict((seg[k], k) for k in normal)
      
      # the display
      display = "d45calcul1a"
      
      # compute possible replacement (superset) digits for the symbols
      r = dict((k, list(d for d in normal if seg[d].issuperset(seg[k]))) for k in set(display))
      
      # choose possible replacement digits for the symbols
      for ds in product(*(r[x] for x in display)):
        if ds[0] == '0': continue
        # two of the digits are unaffected
        if sum(x == y and x in normal for (x, y) in zip(display, ds)) < 2: continue
        # make the number
        n = int(join(ds))
        # and the result
        s = div(n, 9 * 8 * 7 * 6)
        if s is None: continue
        # remove broken segments from the result
        # x = original display, y = original digit, z = result digit
        rs = list(
          norm.get(seg[z].difference(seg[y].difference(seg[x])), '?')
            for (x, y, z) in zip(display[::-1], ds[::-1], str(s)[::-1])
        )
        # there should be 3 normal digits
        if len(rs) - rs.count('?') != 3: continue
        # output solution
        printf("{n} -> {s} -> {rs}", rs=join(rs[::-1]))
      

      Solution: dYSCALCULIA = 84588800688.

      The digits displaying 4 and 5 must be the undamaged ones.

      So the segments that are definitely broken are as shown below:

      There are 22 segments that are definitely broken, and a further 3 that we cannot determine if they are broken or not.

      The result of dividing the number by 9×8×7×6 = 3024 is 27972487, which would look something like this:

      (Only showing the segments that we definitely know to be broken, there are two segments shown lit that may be broken).

      The 2nd digit (7) displays as a 7. The 7th digit (8) displays as a 1. The 8th digit (7) displays as a 7.

      Like

  • Unknown's avatar

    Jim Randell 11:18 am on 11 June 2020 Permalink | Reply
    Tags: by: Ann Kindersley   

    Brain-Teaser 522: [Palindromic phone numbers] 

    From The Sunday Times, 13th June 1971 [link]

    The other day I noticed some strange coincidences about the telephone numbers of my three friends.

    Each number reads the same from left to right and vice versa. The first and third digit formed the owner’s age, as did the sum of all the digits of his number and, furthermore, his number was divisible by his age.

    What were my friends’ ages?

    This puzzle was originally published with no title.

    [teaser522]

     
    • Jim Randell's avatar

      Jim Randell 11:20 am on 11 June 2020 Permalink | Reply

      Assuming the ages cannot have a leading zero (which means the phone numbers can’t either) gives us a unique solution to the puzzle.

      This Python program uses the [[ SubstitutedExpression() ]] solver from the enigma.py library to try 3-, 4- and 5-digit numbers satisfying the required conditions. It runs in 54ms.

      from enigma import (sprintf as f, join, SubstitutedExpression, subsets, all_different, printf)
      
      # alphametic forms for possible palindromic phone numbers
      numbers = [ 'ABA', 'ABBA', 'ABCBA' ]
      
      # collect possible (number, age) solutions
      ss = list()
      
      # consider possible phone numbers
      for n in numbers:
        # age is 1st and 3rd digit
        a = n[0] + n[2]
        
        exprs = [
          # sum of digits is the same as age
          f("{s} = {a}", s=join(n, sep=" + ")),
          # the number itself is divisible by the age
          f("{n} % {a} = 0"),
        ]
      
        # solve the alphametic expressions
        p = SubstitutedExpression(exprs, distinct="", answer=f("({n}, {a})"))
        ss.extend(p.answers(verbose=0))
      
      # choose three solutions with different ages
      for (A, B, C) in subsets(ss, size=3):
        if not all_different(A[1], B[1], C[1]): continue
        printf("A={A} B={B} C={C}")
      

      Solution: The ages are: 21, 23, 27.

      The corresponding phone numbers are: 28182, 28382, 28782.

      These are the only three solutions for 5-digit phone numbers, and there are no solutions for 3- or 4-digit phone numbers.

      Like

    • GeoffR's avatar

      GeoffR 4:48 pm on 16 July 2021 Permalink | Reply

      % A Solution in MiniZinc 
      % assumes 5-digit tel. no. is ABCBA
      include "globals.mzn";
      
      var 1..9:A; var 0..9:B; var 0..9:C; 
      
      var 10..100: age1;
      
      var 10000..99999: ABCBA = 10001*A + 1010*B + 100*C; 
      
      constraint age1 == 10*A + C /\ age1 == 2*A + 2*B + C
      /\ ABCBA mod age1 == 0;
      
      solve satisfy;
      
      output ["Age = " ++ show(age1) ++ ", Tel. No. = " ++ show(ABCBA) ];
      
      % Age = 21, Tel. No. = 28182
      % ----------
      % Age = 23, Tel. No. = 28382
      % ----------
      % Age = 27, Tel. No. = 28782
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:33 am on 9 June 2020 Permalink | Reply
    Tags: by: Ernest J Luery,   

    Brain-Teaser 521: [Names and occupations] 

    From The Sunday Times, 6th June 1971 [link]

    There were six. Messrs. Butcher, Carpenter, Draper, Farmer, Grocer and Miller,  who shared the fire-watching on Friday nights — three this week, three the next. By occupation they were (not necessarily respectively) a butcher, a carpenter, a draper, a farmer, a grocer and a miller.

    Incidents were few and far between, until that Saturday morning when they found the “log” book signed by the Acting Deputy Assistant something or other, as follows: “All three present and fast asleep”.

    Something had to be done about it: they decided to watch, to play whist, and to keep awake in the future. It was arranged thus: four did duty this week, next week two stood down and two others came in, and so on. Each did two turns in three weeks.

    On the first Friday the carpenter, the draper, the farmer and the miller watched. Next week Mr Carpenter, Mr Draper, Mr Farmer and Mr Grocer played. On the third occasion Mr Butcher played against Mr Grocer, Mr Farmer against the butcher, and the miller against the draper. Each night the four cut for partners and kept them till morning.

    If Mr Carpenter’s occupation is the same as the name of the one whose occupation is the same as the name of the one whose occupation is the same as the one whose occupation is the same as the name of the miller:

    What is Mr Miller’s occupation?

    As presented this puzzle has no solutions. In the comments I give a revised version that does have a unique answer.

    This puzzle was originally published with no title.

    [teaser521]

     
    • Jim Randell's avatar

      Jim Randell 9:33 am on 9 June 2020 Permalink | Reply

      I thought the tricky bit in this puzzle would be detangling the long obfuscated condition at the end (and decide if there was a “the name of” missing from it). But it turns out we can show that there are no solutions before we get that far, so the puzzle is flawed.

      In week 3 we have the following pairs (Mr Butcher + Mr Grocer), (Mr Farmer + the butcher), (the miller + the draper).

      But there are only four people (i.e. two pairs), so one of the pairs is repeated. The middle one doesn’t overlap with either of the outer two, so the outer two must refer to the same pair. i.e. (Mr Butcher + Mr Grocer) = (the miller + the draper).

      In week 2 we had Mr Carpenter, Mr Draper, Mr Farmer and Mr Grocer. And Mr Farmer and Mr Grocer stayed on for week 3, which means they can’t have done week 1.

      The jobs missing from week 1 are the butcher and the grocer, so: (Mr Farmer + Mr Grocer) = (the butcher + the grocer).

      But Mr Grocer cannot be one of (the miller + the draper) and also one of (the butcher + the grocer).

      So the described situation is not possible.


      However if we change the names in the second week to match the jobs from the first week (which I think makes for a more pleasing puzzle), and also insert the missing “the name of” into the final obfuscated condition we get the following revised puzzle:

      On the first Friday the carpenter, the draper, the farmer and the miller watched. Next week Mr Carpenter, Mr Draper, Mr Farmer and Mr Miller played. On the third occasion Mr Butcher played against Mr Grocer, Mr Farmer against the butcher, and the miller against the draper. Each night the four cut for partners and kept them till morning.

      If Mr Carpenter’s occupation is the same as the name of the one whose occupation is the same as the name of the one whose occupation is the same as the name of the one whose occupation is the same as the name of the miller:

      What is Mr Miller’s occupation?

      Then we get a puzzle that does have solutions (although not the same as the published solution).

      The following Python program runs in 54ms.

      Run: [ @repl.it ]

      from enigma import (irange, subsets, multiset, map2str, printf)
      
      # labels for the names and jobs
      labels = (B, C, D, F, G, M) = irange(0, 5)
      
      # check the schedule for the 3 weeks
      def check(w1, w2, w3):
        m = multiset()
        for w in (w1, w2, w3):
          w = set(w)
          # 4 different people each week
          if len(w) != 4: return False
          m.update_from_seq(w)
        # each person does 2 duties over the 3 weeks
        return all(v == 2 for v in m.values())
      
      # map: job -> name
      for n in subsets(labels, size=len, select="P"):
      
        # Week 1 = carpenter, draper, farmer, miller
        w1 = [n[C], n[D], n[F], n[M]]
        # Week 2 = Carpenter, Draper, Farmer, Miller
        w2 = [C, D, F, M] # NOT: [C, D, F, G]
        # Week 3 = Butcher + Grocer, Farmer + butcher, miller + draper
        # so: Butcher + Grocer = miller + draper
        if not(set([B, G]) == set([n[M], n[D]])): continue
        w3 = [B, G, F, n[B]]
      
        if not check(w1, w2, w3): continue
      
        # "Mr Carpenter's occupation is the same as the name of the one
        # whose occupation is the same as the name of the one whose
        # occupation is the same as the [name of] one whose occupation is
        # the same as the name of the miller"
        if not(n[n[n[n[n[M]]]]] == C): continue
      
        # output the map
        names = ("Butcher", "Carpenter", "Draper", "Farmer", "Grocer", "Miller")
        jobs = list(x.lower() for x in names)
        printf("{s}", s=map2str(((names[n], j) for (n, j) in zip(n, jobs)), sep="; ", enc=""))
      

      Solution: Mr Miller is the carpenter.

      There are three ways to assign the jobs to the names:

      Butcher=draper; Carpenter=butcher; Draper=farmer; Farmer=grocer; Grocer=miller; Miller=carpenter
      Butcher=miller; Carpenter=butcher; Draper=farmer; Farmer=grocer; Grocer=draper; Miller=carpenter
      Butcher=miller; Carpenter=farmer; Draper=butcher; Farmer=grocer; Grocer=draper; Miller=carpenter

      Like

  • Unknown's avatar

    Jim Randell 5:14 pm on 5 June 2020 Permalink | Reply
    Tags:   

    Teaser 3011: Optical illusion 

    From The Sunday Times, 7th June 2020 [link] [link]

    George and Martha are studying optics. If you place an object a specific distance from a lens, an image will appear at a distance from that lens according the following formula:

    The reciprocal of the object distance plus the reciprocal of the image distance is equal to the reciprocal of the focal length of the lens.

    The object distance was a two-digit whole number of cm (ab). The image distance and the focal length of the lens were also two-digit whole numbers (cd and ef respectively). The six digits were all different and non-zero. Also, the object distance and the focal length were of the same parity and b was an exact multiple of d. Martha pointed out that the sum of those three two-digit numbers was a prime number.

    What was that prime number?

    [teaser3011]

     
    • Jim Randell's avatar

      Jim Randell 5:27 pm on 5 June 2020 Permalink | Reply

      I used uppercase letters for the alphametic symbols, as that is more usual.

      We can use the [[ SubstitutedExpression() ]] solver from the enigma.py library, to solve the relevant alphametic expressions. The following run file executes in 104ms.

      Run: [ @replit ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      --digits="1-9"
      
      "fraction(1, AB, 1, CD) == (1, EF)"
      "B % 2 == F % 2"
      "div(B, D) > 1"
      "is_prime(AB + CD + EF)"
      
      --answer="AB + CD + EF"
      

      Solution: The sum of the numbers is 211.

      The object distance is 78 cm, the image distance is 91 cm and the focal length is 42 cm.

      (1 / 78) + (1 / 91) = (1 / 42)

      The optical constraint and multiple constraint (lines 12 and 14) are sufficient to arrive at a unique answer to the puzzle.


      However, if we remove the constraint that the symbols stand for different digits there is a further solution:

      (1 / 28) + (1 / 21) = (1 / 12)

      In this case the numbers sum to 61.

      Like

    • GeoffR's avatar

      GeoffR 7:33 pm on 5 June 2020 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9: a; var 1..9: b; var 1..9: c; 
      var 1..9: d; var 1..9: e; var 1..9: f;
      
      constraint all_different ([a, b, c, d, e, f]);
      
      var 10..99: ab = 10*a + b;  % object distance
      var 10..99: cd = 10*c + d;  % image distance
      var 10..99: ef = 10*e + f;  % focal length
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x)))))
       ((i < x) -> (x mod i > 0));
       
      % optical formula is  1/ab + 1/cd = 1/ef, so... 
      constraint (cd * ef) + (ab * ef) == (ab * cd);
      
      % parity is same for numbers ab and ef
      constraint ab mod 2 == ef mod 2;
      
      % b was an exact multiple of d
      constraint b div d > 1 /\ b mod d == 0;
      
      % sum of three two-digit numbers was a prime number
      constraint is_prime(ab + cd + ef);
      
      solve satisfy;
      
      output ["Prime number is " ++ show(ab + cd + ef) ++
      "\nab = " ++ show(ab) ++ ", cd = " ++ show(cd) ++ ", ef = " ++ show(ef)];
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 12:06 pm on 4 June 2020 Permalink | Reply
    Tags:   

    Teaser 2860: Cricketing greats 

    From The Sunday Times, 16th July 2017 [link] [link]

    On each of the next seven evenings a different media pundit will advocate the merits of two cricketers. The pundits are Agnew, Blofeld, Dagnall, Mann, Mitchell, Norcross and Smith. The fourteen cricketers to be discussed are Ali, Anderson, Ball, Ballance, Broad, Carberry, Compton, Hales, Kerrigan, Patel, Stokes, Tredwell, Trescothick and Woakes. Each evening, looking at the names of the pundit and the two cricketers, then for any two out of the three names there are just two letters of the alphabet that occur (once or more) in both.

    (a) Which cricketers will Dagnall advocate?
    (b) Which cricketers will Norcross advocate?

    [teaser2860]

     
    • Jim Randell's avatar

      Jim Randell 12:07 pm on 4 June 2020 Permalink | Reply

      See: Teaser 2959.

      I incorporated the [[ gangs() ]] function into the [[ grouping ]] code that is available in the enigma.py library, and we can use it to solve this puzzle.

      The following Python code runs in 53ms.

      Run: [ @repl.it ]

      from enigma import (grouping, diff)
      
      # gang leaders (in solving order)
      pundits = ( 'Dagnall', 'Norcross', 'Agnew', 'Blofeld', 'Mann', 'Mitchell', 'Smith' )
      
      # gang followers
      cricketers = (
        'Ali', 'Anderson', 'Ball', 'Ballance', 'Broad', 'Carberry', 'Compton',
        'Hales', 'Kerrigan', 'Patel', 'Stokes', 'Tredwell', 'Trescothick', 'Woakes'
      )
      
      # selection function
      fn = grouping.share_letters(2)
      
      # find possible 2-gangs for Dagnall and Norcross
      for (g1, g2) in grouping.gangs(2, pundits[0:2], cricketers, fn):
        # check the remaining gangs can be made
        for gs in grouping.gangs(2, pundits[2:], diff(cricketers, g1 + g2), fn):
          grouping.output_gangs(pundits, [g1, g2] + gs)
          # we only need one example
          break
      

      Solution: (a) Dagnall advocates Ball and Broad; (b) Norcross advocates Ballance and Woakes.

      The program checks that the remaining gangs can be formed, and it turns out that there is only one way to do this:

      Dagnall: Ball, Broad
      Norcross: Ballance, Woakes
      Agnew: Carberry, Tredwell
      Blofeld: Patel, Trescothick
      Mann: Anderson, Compton
      Mitchell: Ali, Kerrigan
      Smith: Hales, Stokes

      So we could just use the following even shorter program to find all possible groupings:

      from enigma import grouping
      
      # gang leaders
      pundits = ( 'Dagnall', 'Norcross', 'Agnew', 'Blofeld', 'Mann', 'Mitchell', 'Smith' )
      
      # gang followers
      cricketers = (
        'Ali', 'Anderson', 'Ball', 'Ballance', 'Broad', 'Carberry', 'Compton',
        'Hales', 'Kerrigan', 'Patel', 'Stokes', 'Tredwell', 'Trescothick', 'Woakes'
      )
      
      # find 2-gangs
      for gs in grouping.gangs(2, pundits, cricketers, grouping.share_letters(2)):
        grouping.output_gangs(pundits, gs)
      

      Like

  • Unknown's avatar

    Jim Randell 8:11 am on 2 June 2020 Permalink | Reply
    Tags: , ,   

    Teaser 2566: A fulfilling strategy 

    From The Sunday Times, 27th November 2011 [link] [link]

    I drove down a road with a number of petrol stations whose locations I saw on my map. I decided to check the price at the first station then fill up when I found one offering a lower price (or, failing that, the last one).

    When I got home I noticed that I could arrange the prices (in pence per litre) into an ascending sequence of consecutive whole numbers of pence, plus 0.9p (i.e. 130.9p, 131.9p, 132.2p, etc). I also worked out the average price that I would expect to pay using this strategy, if I were to encounter this set of prices in an unknown order, and I was surprised to find that this value turned out to be a whole number of pence per litre.

    How many petrol stations were there?

    When the puzzle was originally published in The Sunday Times it had been edited into a form that meant there were no solutions. Here I have rephrased the middle paragraph so that it works as the setter originally intended.

    [teaser2566]

     
    • Jim Randell's avatar

      Jim Randell 8:12 am on 2 June 2020 Permalink | Reply

      (See also: Teaser 2988).

      Here is a program that calculates the result directly. It runs in 50ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, inf, factorial, div, printf)
      
      # strategy, take the first amount lower than the first n items
      def strategy(s, n):
        t = min(s[:n])
        for x in s[n:]:
          if x < t: break
        return x
      
      # play the game with k items
      def play(k):
        # collect total amount
        t = 0
        # consider possible orderings of the items
        ps = list(1299 + 10 * p for p in irange(1, k))
        for s in subsets(ps, size=k, select="P"):
          t += strategy(s, 1)
        return t
      
      # consider numbers of items
      for k in irange(2, inf):
        t = play(k)
        # calculate the average expected price (in 10th pence)
        d = factorial(k)
        p = div(t, d)
        if p is not None and p % 10 == 0:
          printf("k={k} -> p={p}")
          break
      

      Solution: There were 5 petrol stations.

      And the expected average fuel price was 132.0 pence.

      It doesn’t actually matter what the prices actually are, all the petrol stations could adjust their prices by the same whole number of pence, and the expected average price would be adjusted by the same amount.


      Analytically:

      As determined in Teaser 2988 we can use the formula:

      S(n, k) = ((2n + 1)k − n(n + 1)) (k + 1) / 2(n + 1)k

      To determine the mean expected value of choosing from k boxes with values from 1..k, using the strategy of picking the next box that is better than any of the first n boxes (or the last box if there are no boxes better than the first).

      In this case n = 1:

      S(1, k) = (3k − 2) (k + 1) / 4k

      If we award ourselves a prize of value k for selecting the cheapest fuel, and a prize of value 1 for selecting the most expensive fuel, then we have the same situation as in Teaser 2988, and if p is expected average prize value, then the corresponding expected average fuel price is (130.9 + k − p).

      And k is an integer, so in order to make the expected average fuel price a whole number of pence we are looking for expected average prize value that is an integer plus 0.9.

      i.e. when the value of 10 S(1, k) is an integer with a residue of 9 modulo 10.

      10 S(1, k) = (15k + 5) / 2 − (5 / k)

      When k is odd, the first part gives us a whole number, so we would also need the (5 / k) part to give a whole number. i.e. k = 1, 5.

      When k is even, the first part gives us an odd number of halves, so we also need (5 / k) to give an odd number of halves. i.e. k = 2, 10.

      And we are only interested in values of k > 1, so there are just 3 values to check:

      k = 5: 10 S(1, k) = 39
      k = 2: 10 S(1, k) = 15
      k = 10: 10 S(1, k) = 77

      The only value for k that gives a residue of 9 modulo 10 is k = 5.

      And in this case our expected average prize is p = 3.9 so our expected average fuel price is 132.0 pence.

      Like

  • Unknown's avatar

    Jim Randell 9:44 pm on 29 May 2020 Permalink | Reply
    Tags:   

    Teaser 3010: Putting game 

    From The Sunday Times, 31st May 2020 [link] [link]

    A putting game has a board with eight rectangular holes, like the example (not to scale), but with the holes in a different order.

    If you hit your ball (diameter 4cm) through a hole without touching the sides, you score the number of points above that hole. The sum of score and width (in cm) for each hole is 15; there are 2cm gaps between holes.

    I know that if I aim at a point on the board, then the ball’s centre will arrive at the board within 12cm of my point of aim, and is equally likely to arrive at any point in that range. Therefore, I aim at the one point that maximises my long-term average score. This point is the centre of a hole and my average score is a whole number.

    (a) Which hole do I aim at?

    (b) Which two holes are either side of it?

    [teaser3010]

     
    • Jim Randell's avatar

      Jim Randell 9:23 am on 30 May 2020 Permalink | Reply

      The 4cm diameter ball is not allowed to touch the sides of the hole, so we can consider the outside 2cm of each hole to be “out of bounds”. Which is the same as if the ball was a point, the gaps between holes are extended 2cm in each direction (so they become 6cm wide), and each hole is reduced by a corresponding 2cm on either edge.

      To be sure I satisfy all the conditions this Python program constructs all possible orderings for the holes, and then for each ordering looks at possible target positions. It looks for orderings that have a unique maximum targeting point that is at the centre of a hole, and that yields an integer average score per shot. Once an ordering passes these tests we record the hole that the target is in, along with the two holes on either side. This gives a unique target + left/right value (although there are many orderings that contain the three holes appropriately positioned).

      It runs in 1.58s, but I think this could be improved with some additional analysis.

      from enigma import (irange, subsets, multiset, ordered, printf)
      
      # available holes (points)
      holes = [ 1, 2, 3, 4, 5, 6, 7, 8 ]
      
      # layout for a sequence of holes
      # return a list of: (region size (in mm), points scored) pairs
      def layout(ps):
        for (i, p) in enumerate(ps):
          if i > 0: yield (60, 0) # gap between holes
          yield (110 - 10 * p, p) # hole size
      
      # generate intervals ((a, b), p) from a layout
      # where a, b are absolute distances, p is number of points scored
      def intervals(ss):
        a = b = 0
        for (d, p) in ss:
          if b == 0:
            (a, b) = (0, d)
          else:
            (a, b) = (b, b + d)
          yield ((a, b), p)
      
      # analyse the layout (working in 5mm steps)
      # return list of (d, (v, r)) values, where:
      # d = absolute target distance
      # v + r/240 = max average score
      def analyse(ss):
        # determine total length
        t = sum(d for (d, _) in ss)
        rs = list()
        # consider target positions (in 5mm steps)
        for x in irange(120, t - 120, step=5):
          # consider each zone
          r = 0
          for ((a, b), p) in intervals(ss):
            # overlap?
            if b < x - 120: continue
            if a > x + 120: break
            d = min(x + 120, b) - max(x - 120, a)
            r += p * d
          rs.append((x, r))
        # find maximum average value
        m = max(r for (_, r) in rs)
        return list((x, divmod(r, 240)) for (x, r) in rs if r == m)
      
      # collect results
      m = multiset()
      
      # choose an order for the holes
      for ps in subsets(holes, size=len, select="P"):
        # but ignore the order in the diagram
        if ps == (6, 3, 8, 7, 2, 5, 1, 4): continue
        # construct the sequence of (regions, points)
        ss = list(layout(ps))
        # analyse it
        rs = analyse(ss)
        # there should only be one max position
        if len(rs) != 1: continue
        (x, (v, r)) = rs[0]
        # and the average score should be an integer
        if r != 0: continue
        # find the interval x is in
        for ((a, b), p) in intervals(ss):
          # and check it is centred
          if p > 0 and a < x < b and b - x == x - a:
            # find the holes on either side
            i = ps.index(p)
            z = ps[i - 1:i + 2]
            printf("[{ps} -> target = {x} mm, avg pts = {v}; segment = {z}]")
            m.add((p, ordered(ps[i - 1], ps[i + 1])))
      
      # output solution
      for ((x, (l, r)), n) in m.most_common():
        printf("centre = {x}, left/right = {l}/{r} [{n} solutions]")
      

      Solution: (a) You should aim at the centre of the 5 point hole. (b) The 6 point and 8 point holes are either side of the 5 point hole.

      Here is a diagram of the arrangement:

      The “out of bounds” areas are indicated in red, and we score zero points if the centre of the ball lands in these.

      In the 24cm section centred on the 5 point hole we see that there is a 3cm zone where we can score 6 points, a 6cm zone where we can score 5 points, and a 3cm zone where we can score 8 points.

      The average expected score is thus: (6×3 + 5×6 + 8×3) / 24 = 72 / 24 = 3.

      Like

      • Jim Randell's avatar

        Jim Randell 6:28 pm on 30 May 2020 Permalink | Reply

        Here is the program adapted to just consider the centre + left/right values. It finds there is only one segment that must appear in any solution (or its reverse), and this gives the required answer. It runs in 54ms.

        from enigma import (irange, subsets, peek, printf)
        
        # available holes (points)
        holes = [ 1, 2, 3, 4, 5, 6, 7, 8 ]
        
        # layout for a sequence of holes (in mm)
        # return a list of: (region size, points scored) pairs
        def layout(ps):
          for (i, p) in enumerate(ps):
            if i > 0: yield (60, 0) # gap between holes
            yield (110 - 10 * p, p) # hole
        
        # generate intervals ((a, b), p) from a layout
        # where a, b are absolute distances, p is the number of points scored
        def intervals(ss):
          a = b = 0
          for (d, p) in ss:
            if b == 0:
              (a, b) = (0, d)
            else:
              (a, b) = (b, b + d)
            yield ((a, b), p)
        
        # analyse the layout (working in 5mm steps)
        # return list of (d, (v, r)) values, where:
        # d = absolute target distance
        # v + r/240 = max average score
        def analyse(ss):
          # determine total length
          t = sum(d for (d, _) in ss)
          rs = list()
          # consider target positions (in 5mm steps)
          for x in irange(120, t - 120, step=5):
            # consider each zone
            r = 0
            for ((a, b), p) in intervals(ss):
              # overlap?
              if b < x - 120: continue
              if a > x + 120: break
              d = min(x + 120, b) - max(x - 120, a)
              r += p * d
            rs.append((x, r))
          # find maximum average value
          m = max(r for (_, r) in rs)
          return list((x, divmod(r, 240)) for (x, r) in rs if r == m)
        
        # find triples with integer scores max average scores at the centre of the centre hole
        
        # choose the centre hole and left/right holes
        for (X, L, R) in subsets(holes, size=3, select="P"):
          if not (L < R): continue
          # create the layout
          ss = list(layout((L, X, R)))
          # analyse it
          rs = analyse(ss)
          # there should be only one max position
          if len(rs) != 1: continue
          (x, (v, r)) = rs[0]
          # and the max average score should be an integer
          if r != 0: continue
          # and it should be centred on X
          ((a, b), _) = peek(intervals(ss), 2)
          if a < x < b and b - x == x - a:
            printf("segment = ({L}, {X}, {R}) -> target = {x} mm, avg pts = {v}")
        

        I suppose really I should demonstrate that we can construct an ordering containing the required segment that satisfies all the requirements, but as my first program finds lots I don’t think it is necessary.

        Like

    • Robert Brown's avatar

      Robert Brown 10:02 am on 30 May 2020 Permalink | Reply

      If the middle & adjacent slots are too small, the ±12 cm range goes into the next slot, which is undefined. This reduces the solution space and quickly leads to what appears to be a unique result. But if you reduce the smaller adjacent slot by 1 and increase the other one, it still works. This would be valid if the smaller slot was the first one on the board, so a possible duplicate result?

      Like

      • Jim Randell's avatar

        Jim Randell 1:11 pm on 30 May 2020 Permalink | Reply

        @Robert: I don’t think it is possible for the target area to extend beyond the centre hole and the holes on either side. The smallest holes are 7cm and 8cm and the distance from the centre of one to the edge of the other is always greater than 12cm, so I think we only need to consider centre + left/right configurations. (Which I’m looking at to give a more efficient program).

        Like

    • Robert Brown's avatar

      Robert Brown 10:52 am on 30 May 2020 Permalink | Reply

      Further to above. The ‘not to scale’ drawing masks the fact that low value slots are quite wide. I assume there are 8 values as per the drawing, with corresponding slot widths.
      So there is a solution where the mid value is low, and the adjacent values can take one of two different pairs (they have the same sum), and where the total length measured from the centre of the mid slot is > 12 in each case. Definitely two sets of results, doesn’t matter where they are on the board. Maybe I’m missing something?

      Like

    • Robert Brown's avatar

      Robert Brown 12:17 pm on 30 May 2020 Permalink | Reply

      This is not a good place to have a conversation. You have my email address, but I don’t have yours!
      Both of the above sets of results work, but each post has typos which I cannot correct. An in depth exploration reveals 5 different solutions, all with 3 different L M R values between 1 & 8, and with average values of 3, 4 or 5. In each case the minimum distance from the centre of the middle value (where he aims for) is at least 15.5 cm before going into the next (unknown) slot, plenty of room for the 2cm radius ball to have it’s centre up to 12 cm from that centre. So no need for any of the slots to be the first one on the board.

      Like

      • Jim Randell's avatar

        Jim Randell 6:55 pm on 1 June 2020 Permalink | Reply

        @Robert: It’s a limitation of WordPress.com I’m afraid, but if there are any corrections you would like to make, just post a followup comment, and I will use it to correct errors in the original.

        Like

    • Frits's avatar

      Frits 1:14 pm on 15 August 2025 Permalink | Reply

      Based on Jim’s diagram of the arrangement.

      # number of holes, sum score+width, gap, ball diameter, aim deviation  
      N, T, G, B, Z = 8, 15, 2, 4, 12
      
      cm = lambda pts: T - pts - 2 * G
      
      # length of deviation range
      dev_cm = 2 * Z
      rng = set(range(1, N + 1))
      # look for a solution L, M, R
      for M in rng:
        mid_cm = cm(M)
        mid_total = mid_cm * M
        # remaining cm to use for left and right holes
        rem_cm = dev_cm - mid_cm - 2 * G - 2 * B
        rem_rng = sorted(rng - {M})
        for R in rem_rng:
          right_cm = cm(R)
          # aim as far right as possible in order to maximise the score
          right = min(rem_cm, right_cm)
          # but aim must also be at the centre of a hole
          if 2 * right != rem_cm: continue
          mid_right_total = mid_total + right * R
          _, r = divmod(mid_right_total, dev_cm)
          left_total_needed  = dev_cm - r
          
          while True:
            L, r = divmod(left_total_needed, right)
            if L >= R: break
            if not r and L != M:
              print(f"answer: (a) {M}, (b) {L} and {R}")
            left_total_needed += dev_cm
      

      Like

  • Unknown's avatar

    Jim Randell 12:53 pm on 28 May 2020 Permalink | Reply
    Tags:   

    Teaser 2863: Little time 

    From The Sunday Times, 6th August 2017 [link] [link]

    Do you have a little time to try this Teaser? I have taken a four-figure number and a six-figure number and I have consistently replaced digits by letters to give the words LITTLE and TIME.

    If you take the digits of TIME and write down all the four-figure numbers which use exactly those digits in some order and then add up all those numbers, then your total will be LITTLE.

    What number is TIME?

    [teaser2863]

     
    • Jim Randell's avatar

      Jim Randell 12:54 pm on 28 May 2020 Permalink | Reply

      The puzzle text doesn’t include the usual alphametic caveat that different letters stand for different digits, only that the digits are replaced consistently by letters (i.e. the same letter is replaced by the same digit). And if there are zero digits involved the 24 rearrangements of the 4 digits of TIME will include some sequences which don’t give 4 valid digit numbers.

      So this Python program considers all 4 digit values for TIME, and totals only those rearrangements corresponding to 4 digit numbers. It runs in 135ms.

      Run: [ @repl.it ]

      from enigma import (irange, nsplit, nconcat, subsets, ordered, cached, printf)
      
      # update a map m consistently with (k, v) pairs in ps
      def update(m, ps):
        m = m.copy()
        for (k, v) in ps:
          x = m.get(k)
          if x is None:
            m[k] = v
          elif x != v:
            return None
        return m
      
      # sum the rearrangements that give 4 digit numbers
      @cached
      def total(ds):
        t = 0
        for s in subsets(ds, size=len, select="mP"):
          if s[0] != 0:
            t += nconcat(s)
        return t
      
      # consider 4 digit numbers for TIME
      for TIME in irange(1000, 9999):
        # this maps the letters T, I, M, E to digits
        ds = nsplit(TIME, 4)
      
        # sum the rearrangements of TIME that give 4 digit numbers
        t = total(ordered(*ds))
      
        # the total is a 6 digit number
        ts = nsplit(t)
        if len(ts) != 6: continue
      
        # map letters to digits
        m = update(dict(zip("TIME", ds)), zip("LITTLE", ts))
        if m is None: continue
      
        # output solution
        printf("TIME={TIME} LITTLE={t}")
      

      Solution: TIME = 3578.

      And so LITTLE = 153318.


      It turns out that using a standard alphametic interpretation, and not worrying about whether the rearrangements always give 4 digit numbers will also give this answer. So:

      If we write down the sum of the 24 permutations of TIME, each letter appears in each column 6 times, the result of the sum is thus:

      6666 × (T + I + M + E) = LITTLE

      Which we can solve as an alphametic expression using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file gives the same solution in 92ms.

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "6666 * (T + I + M + E) = LITTLE"
      
      --answer="(TIME, LITTLE)"
      

      Like

    • GeoffR's avatar

      GeoffR 8:47 am on 29 May 2020 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9: T; var 1..9: I; var 1..9: M;
      var 1..9: E; var 1..9: L; 
      
      var 1000..9999: TIME = 1000*T + 100*I + 10*M + E;
      
      var 100000..999999: LITTLE = 100000*L + 10000*I
      + 1000*T + 100*T + 10*L + E;
      
      % All letters T, I, M, E appear 6 times in 24 permutations
      constraint  6666 * (T + I + M + E) == LITTLE;
      
      solve satisfy;
      
      output ["TIME = " ++ show(TIME) 
      ++ "\nLITTLE = " ++ show(LITTLE)];
      
      % TIME = 3578
      % LITTLE = 153318
      % ----------
      % ==========
      
      
      

      Easy to check the digits in TIME give the digits in LITTLE

      little, cnt = 0, 0
      
      # check value of LITTLE using TIME'S digits of (3,5,7,8)
      from itertools import permutations
      for p in permutations((3,5,7,8)):
          t, i, m, e = p
          time = 1000*t + 100*i + 10*m + e
          # Add current <time> total
          little += time
          # increase permutation count
          cnt += 1
      
      print(f"LITTLE = {little} in {cnt} permutations")
      # LITTLE = 153318 in 24 permutations
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 12:10 pm on 26 May 2020 Permalink | Reply
    Tags:   

    Brain-Teaser 520: [Picnic allocations] 

    From The Sunday Times, 30th May 1971 [link]

    “What’s inside it?” asked the Mole wriggling with curiosity.

    “There’s cold chicken inside it”, replied the Rat briefly: “cold-tongue-cold-ham-cold-beef-pickled-gherkins-salad-french-rolls-cress-sandwiches-potted-meat-ginger-beer-lemonade-soda-water…”

    “Oh, stop”, cried the Mole in ecstasies. “This is too much for one picnic. We can have another tomorrow on what’s left”.

    “Do you really think so?” inquired the Rat seriously. “Let’s see. There’s only salad-pickled-gherkins-french-rolls-and-soda-water enough for two days: so if we have ham today we’ll have beef tomorrow; if we have potted meat today we’ll have cress sandwiches tomorrow; and if we have tongue today we’ll have lemonade tomorrow”.

    “If we save the cress sandwiches for tomorrow we’ll have the beef today; if we keep the potted meat for tomorrow we’ll have the ginger beer today; and if we keep the lemonade for tomorrow we’ll have the ham today”. The Mole was entering into the spirit of the thing.

    “In any event we’ll have the lemonade and ginger beer on different days, and likewise the beef and the chicken”, Rat shrieked excitedly.

    “And if we have the chicken and cress sandwiches together, we’ll have the potted meat the day after we have the tongue”. The Mole rolled on his back at the prospect. “And we’ll eat every scrap”.

    Which of the eight items did they save for the second day?

    This puzzle was originally published with no title.

    [teaser520]

     
    • Jim Randell's avatar

      Jim Randell 12:10 pm on 26 May 2020 Permalink | Reply

      The following Python program checks all possible assignments of days to the 8 items in question, and then checks to find which assignments satisfy all the conditions.

      It runs in 71ms (on my new laptop). (Internal runtime is 192µs).

      from enigma import (subsets, implies, printf)
      
      # choose days for the 8 items in question
      for (CC, CT, CH, CB, CS, PM, GB, LE) in subsets((1, 2), size=8, select="M"):
      
        # check the statements:
      
        # R: "if we have ham today we'll have beef tomorrow"
        if not implies(CH == 1, CB == 2): continue
      
        # R: "if we have potted meat today we'll have cress sandwiches tomorrow"
        if not implies(PM == 1, CS == 2): continue
      
        # R: "if we have tongue today we'll have lemonade tomorrow"
        if not implies(CT == 1, LE == 2): continue
      
        # M: "if we save the cress sandwiches for tomorrow we'll have the beef today"
        if not implies(CS == 2, CB == 1): continue
      
        # M: "if we keep the potted meat for tomorrow we'll have the ginger beer today"
        if not implies(PM == 2, GB == 1): continue
      
        # M: "if we keep the lemonade for tomorrow we'll have the ham today"
        if not implies(LE == 2, CH == 1): continue
      
        # R: "we'll have the lemonade and ginger beer on different days ..."
        if not (LE != GB): continue
      
        # R: "... and likewise the beef and the chicken"
        if not (CB != CC): continue
      
        # M: "if we have the chicken and cress sandwiches together, we'll
        # have the potted meat the day after we have the tongue"
        if not implies(CC == CS, PM == CT + 1): continue
      
        # output solution
        printf("CC={CC} CT={CT} CH={CH} CB={CB} CS={CS} PM={PM} GB={GB} LE={LE}")
      

      Solution: The cold beef, potted meat and lemonade was saved for the second day.

      Like

      • John Crabtree's avatar

        John Crabtree 8:10 pm on 25 June 2020 Permalink | Reply

        There are 9 statements for 8 unknowns. If Ch is true for chicken today, and ~Ch is true for chicken tomorrow,then the statements may be written as boolean equations:
        1. H.~B + ~H = 1
        2. P.~Cr + ~P = 1
        3. T.~L + ~T = 1
        4. ~Cr.B + Cr = 1
        5. ~P.G + P = 1
        6. ~L.H + L = 1
        7. L XOR G
        8. B XOR Ch
        9. [(Ch XNOR Cr).~P.T] + (Ch XOR Cr) = 1

        Combining equations 5, 7, 6, 1, 4, 2 and 8 gives
        1 = (~P.G + P).( L XOR G).(~L.H + L).(H.~B + ~H).(~Cr.B + Cr).(P.~Cr + ~P).(B XOR Ch)
        = ~P.G.( L XOR G).(~L.H + L).(H.~B + ~H).(~Cr.B + Cr).(P.~Cr + ~P).(B XOR Ch) +
        ….P.(P.~Cr + ~P).(~Cr.B + Cr).(H.~B + ~H).(~L.H + L).( L XOR G).(B XOR Ch)
        = ~P.G.~L.H.~B.Cr.Ch + P.~Cr.B.~H.L.~G.~Ch
        Combing this with equation 9 gives ~P.G.~L.H.~B.Cr.Ch.T = 1
        Equation 3 is satisfied, but is not required to solve the teaser by this method

        The items to be saved for tomorrow are given by ~B,~L and ~P, ie cold Beef, Lemonade and Potted meat.

        Starting with any assumption other than T or ~T and working through the equations 1-2, and 4-8 in some order and finally 9 gives either a consistent solution,or a contradiction (reductio ad absurdum). If one starts by assuming T, then equation 3 is used before the others. If ~T is assumed, then equation 9 gives Ch.~Cr + ~Ch.Cr = 1, and both conditions have to be checked to show that ~T is not true

        Like

      • Jim Randell's avatar

        Jim Randell 3:12 pm on 15 November 2025 Permalink | Reply

        Or using the [[ SubstitutedExpression ]] solver from the enigma.py library.

        The code generated from the following run file has an internal runtime of 75µs.

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        --distinct=""
        --digits="1,2"
        
        --macro="@CC = {A}"  # cold chicken
        --macro="@CT = {B}"  # cold tongue
        --macro="@CH = {C}"  # cold ham
        --macro="@CB = {D}"  # cold beef
        --macro="@CS = {E}"  # cress sandwiches
        --macro="@PM = {F}"  # potted meat
        --macro="@GB = {G}"  # ginger beer
        --macro="@LE = {H}"  # lemonade
        
        # R: "if we have ham today we'll have beef tomorrow"
        "implies(@CH == 1, @CB == 2)"
        
        # R: "if we have potted meat today we'll have cress sandwiches tomorrow"
        "implies(@PM == 1, @CS == 2)"
        
        # R: "if we have tongue today we'll have lemonade tomorrow"
        "implies(@CT == 1, @LE == 2)"
        
        # M: "if we save the cress sandwiches for tomorrow we'll have the beef today"
        "implies(@CS == 2, @CB == 1)"
        
        # M: "if we keep the potted meat for tomorrow we'll have the ginger beer today"
        "implies(@PM == 2, @GB == 1)"
        
        # M: "if we keep the lemonade for tomorrow we'll have the ham today"
        "implies(@LE == 2, @CH == 1)"
        
        # R: "we'll have the lemonade and ginger beer on different days ..."
        "@LE != @GB"
        
        # R: "... and likewise the beef and the chicken"
        "@CB != @CC"
        
        # M: "if we have the chicken and cress sandwiches together, we'll
        # have the potted meat the day after we have the tongue"
        "implies(@CC == @CS, @PM == @CT + 1)"
        
        # output day for each dish
        --template="CC=@CC CT=@CT CH=@CH CB=@CB CS=@CS PM=@PM GB=@GB LE=@LE"
        --solution=""
        --verbose="1-H"
        

        Like

  • Unknown's avatar

    Jim Randell 5:08 pm on 22 May 2020 Permalink | Reply
    Tags:   

    Teaser 3009: Head count 

    From The Sunday Times, 24th May 2020 [link] [link]

    My grandson and I play a simple coin game. In the first round we toss seven coins and I predict how many “heads” there will be whilst my grandson predicts the number of “tails”. After the tossing I score a point for each head plus a bonus of ten if my prediction was correct — my grandson scores likewise for the tails. We then repeat this with six coins, then five, and so on down to a single coin. After each round we keep cumulative totals of our scores. In one game, for over half of the pairs of cumulative scores, my grandson’s total was like mine but with the digits in reverse order. In fact he was ahead throughout and at one stage his cumulative total was double mine — and he had an even bigger numerical lead than that on just one earlier occasion and one later occasion.

    List the number of heads in each of the seven rounds.

    [teaser3009]

     
    • Jim Randell's avatar

      Jim Randell 11:03 pm on 22 May 2020 Permalink | Reply

      Originally I missed the significance of the word “numerical”, and got no solutions. But careful interpretation of the puzzle text does lead to a single solution.

      I wrote a recursive function that keeps track of all the conditions as it goes along.

      The following Python 3 program runs in 320ms.

      Run: [ @repl.it ]

      from itertools import product
      from enigma import irange, nsplit, join, cached, printf
      
      # does A mirror B?
      mirror = cached(lambda A, B: nsplit(A) == nsplit(B)[::-1])
      
      # play the game starting with a round of k coins,
      # where A plays heads, B plays tails, and B is always ahead
      # ms = number of points B is ahead of A
      # s = index in ms when B = 2A
      # rv = count number of scores that are reverse of each other
      def play(k, tA=0, tB=0, ms=[], s=None, rv=0, ss=[]):
        # are we done?
        if k == 0:
          if s is not None and rv > 3:
            # there must be exactly 1 round after s where B is further ahead
            if sum(x > ms[s] for x in ms[s + 1:]) == 1:
              yield ss
        else:
          # consider the number of heads, and predictions for heads and tails
          for (n, h, t) in product(irange(0, k), (0, 1), (0, 1)):
            # calculate the points for each player
            a = n + h * 10
            b = k - n + t * 10
            # and the total points
            A = tA + a
            B = tB + b
            m = B - A
            # B is always ahead
            if not(m > 0): continue
            # look for cases where B = 2A
            s_ = s
            if B == 2 * A:
              # it only happens once
              if s is not None: continue
              # there must be exactly 1 previous round where B is further ahead
              if not(sum(x > m for x in ms) == 1): continue
              s_ = len(ms)
            # are A, B mirrored scores?
            rv_ = rv + mirror(A, B)
            # in the final 4 rounds we must have encountered some mirrored scores
            if k < 5 and rv_ < 5 - k: continue
            # play the remaining rounds
            yield from play(k - 1, A, B, ms + [m], s_, rv_, ss + [(n, h, t, A, B)])
      
      # play the game, starting with 7 coins
      for ss in play(7):
        # output the rounds
        (pA, pB) = (0, 0)
        s = list(i for (i, (n, h, t, A, B)) in enumerate(ss) if B == 2 * A)[0]
        for (i, (n, h, t, A, B)) in enumerate(ss):
          k = 7 - i
          fs = [ f"lead = {B - A}" ]
          if i == s: fs.append("double")
          if mirror(A, B): fs.append("mirror")
          printf(
            "{k} coins: heads={n}; predictions = ({h}, {t}); points = ({a}, {b}); totals = ({A},  {B}); {fs}",
            a=A - pA, b=B - pB, fs=join(fs, sep="; "),
          )
          (pA, pB) = (A, B)
        printf()
      

      Solution: The number of heads in each of the rounds was: 0, 2, 4, 4, 3, 1, 1.

      The full description of the rounds is:

      k   h  t  |  H   T  |  a   b  |  A   B
      7:  0  7  |  -  10  |  0  17  |  0  17  [lead >16]
      6:  2  4  | 10   -  | 12   4  | 12  21  [mirror]
      5:  4  1  |  -  10  |  4  11  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  |  4   0  | 20  32
      3:  3  0  |  -   -  |  3   0  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  |  1  10  | 35  53  [mirror, lead >16]
      

      (k = number of coins; h, t = number of heads/tails; H, T = bonus points; a, b = points in round; A, B = cumulative totals)


      However, keeping the cumulative totals always using 2 digits gives us three further solutions where the totals 03 and 30 are used as mirrored pairs:

      k   h  t  |  H   T  |  a   b  |  A   B
      7:  1  6  |  -  10  | 01  16  | 01  16
      6:  2  4  |  -  10  | 02  14  | 03  30  [mirror, lead >16]
      5:  3  2  | 10   -  | 13  02  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  | 04  00  | 20  32
      3:  3  0  |  -   -  | 03  00  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  | 01  10  | 35  53  [mirror, lead >16]
      
      
      k   h  t  |  H   T  |  a   b  |  A   B
      7:  2  5  |  -  10  | 02  15  | 02  15
      6:  1  5  |  -  10  | 01  15  | 03  30  [mirror, lead >16]
      5:  3  2  | 10   -  | 13  02  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  | 04  00  | 20  32
      3:  3  0  |  -   -  | 03  00  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  | 01  10  | 35  53  [mirror, lead >16]
      
      
      k   h  t  |  H   T  |  a   b  |  A   B
      7:  3  4  |  -  10  | 03  14  | 03  14
      6:  0  6  |  -  10  | 00  16  | 03  30  [mirror, lead >16]
      5:  3  2  | 10   -  | 13  02  | 16  32  [double, lead =16]
      4:  4  0  |  -   -  | 04  00  | 20  32
      3:  3  0  |  -   -  | 03  00  | 23  32  [mirror]
      2:  1  1  | 10  10  | 11  11  | 34  43  [mirror]
      1:  1  0  |  -  10  | 01  10  | 35  53  [mirror, lead >16]
      

      The program will find all 4 solutions if we replace the calls to [[ nsplit(x) ]] with [[ nsplit(x, 2) ]] in the function [[ mirror() ]] (line 5).

      Like

    • Robert Brown's avatar

      Robert Brown 11:57 am on 24 May 2020 Permalink | Reply

      Turns out there’s a simple manual solution. After each section (7,6,5 etc) there’s a total sum for all heads & tails. Last digit is different in each case. Adding bonuses makes no difference.
      Need to find 4 sections that end in reversible numbers, so just look for reversible pairs where the sum of the pair has the same last digit. Only works for 4 sections, QED.

      Like

  • Unknown's avatar

    Jim Randell 12:59 pm on 21 May 2020 Permalink | Reply
    Tags:   

    Teaser 2864: Sequence of squares 

    From The Sunday Times, 13th August 2017 [link] [link]

    I started with a rectangle of paper. With one straight cut I divided it into a square and a rectangle. I put the square to one side and started again with the remaining rectangle. With one straight cut I divided it into a square and a rectangle. I put the square (which was smaller than the previous one) to one side and started again with the remaining rectangle. I kept repeating this process (discarding a smaller square each time) until eventually the remaining rectangle was itself a square and it had sides of length one centimetre. So overall I had divided the original piece of paper into squares. The average area of the squares was a two-figure number of square centimetres.

    What were the dimensions of the original rectangle?

    [teaser2864]

     
    • Jim Randell's avatar

      Jim Randell 1:00 pm on 21 May 2020 Permalink | Reply

      If we start with the 1×1 cm square and replace the removed squares, we find the sequence of sizes of squares is:

      1, 1, 2, 3, 5, 8, 13, 21, …

      i.e. a Fibonacci sequence. [ @Wikipedia ]

      So we can start with the two 1×1 cm squares and build up the original rectangle square by square, until find one where the mean area of the squares is a two digit integer as required.

      This Python program runs in 82ms.

      Run: [ @repl.it ]

      from enigma import printf
      
      # initial a x b rectangle, n = number of squares, A = total Area
      (a, b, n, A) = (1, 1, 2, 2)
      while True:
        # calculate the mean area of the squares
        (m, r) = divmod(A, n)
        if m > 99: break
        # construct the rectangle
        (a, b) = (b, a + b)
        if r == 0 and m > 9:
          # output solution, when m is a 2 digit integer
          printf("[n={n}] rectangle = {a} x {b}, mean area = {m}")
        # move on to the next square
        A += b * b
        n += 1
      

      Solution: The original rectangle was 13 cm × 21 cm.

      So in total 6 cuts were made, producing 7 squares.

      The total area of the 7 squares is 273 sq cm, so the mean area is 39 sq cm.


      Manually:

      If:

      F(n) = nth Fibonacci number
      S(n) = sum of squares of F(n) = F(n) F(n + 1)
      M(n) = mean of squares of F(n) = F(n) F(n + 1) / n

      Then we can calculate:

       n  F(n)  S(n)  M(n)
       1    1     1    1
       2    1     2    1
       3    2     6    2
       4    3    15    3.75  
       5    5    40    8
       6    8   104   17.3...
       7   13   273   39
       8   21   714   89.25
       9   34  1870  207.7...
      10   55  ...
      

      And the answer is apparent.

      If we draw a quarter circle through each square we can make a Fibonacci Spiral:

      Liked by 1 person

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