Design a site like this with WordPress.com
Get started

Tagged: by: R Postill Toggle Comment Threads | Keyboard Shortcuts

  • Jim Randell 9:01 am on 6 November 2022 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 195: Common frontier 

    From The Sunday Times, 17th January 1965 [link]

    The adjoining countries of Europhalia and Sopiculia have different standard units of weight and length, but both use the normal units of time. Although both countries use Arabic numerals, neither uses the denary (tens) method of counting, but each has a different integer less than ten as its counting base.

    In reply to my request for more information a Europhalian friend wrote: “Our unit of weight is the Elbo, and there are 42 Elbos to 24 of their Solbos. The length of our common frontier is 21 Emils”. My Sopiculian correspondent replied: “16 Solbos weigh the same as 26 Elbos; the common frontier is 21 Somils long”.

    I later discovered that in both countries there is a speed limit equivalent to our 50 mph. In Sopiculia this is defined by law as 104 Somils per hour.

    What is the Europhalian speed limit?

    This puzzle was included in the book Sunday Times Brain Teasers (1974, edited by Ronald Postill).

    [teaser195]

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

      If the bases are E and S (both less than 10).

      Then the ratio of the weights W = (Elbo/Solbo) is (according to to the two correspondents):

      W = (2E + 4) / (4E + 2)
      W = (S + 6) / (2S + 6)
      ⇒ 4ES + 12E + 8S + 24 = 4ES + 24E + 2S + 12
      ⇒ E = S/2 + 1

      And we see from the digits used, E > 4 and S > 6, and S must be even, so:

      S = 8; E = 5

      We can then translate the correspondents statements into decimal to get:

      Eu: “There are 22 Elbos to 14 of their Solbos [W = 7/11]. The common frontier is 11 Emils”.
      So: “14 Solbos weigh the same as 22 Elbos [W = 7/11]. The common frontier is 17 Somils”.

      And:

      “104 Somils/hour” [base 8]
      = 68 Somils/hour [base 10]
      = 4× (17 Somils)/hour [base 10]
      = 4× (11 Emils)/hour [base 10]
      = 44 Emils/hour [base 10]
      = “134 Emils/hour” [base 5]

      Solution: The Europhalian speed-limit would be expressed as: “134 Emils per hour”.

      Like

  • Jim Randell 7:47 am on 3 November 2022 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 653: Hymn numbers 

    From The Sunday Times, 13th January 1974 [link]

    In Church last Sunday I studied the three numbers on the “service-board”. The first was of the appointed psalm, and the other two of the hymns. They included all the digits from 1 to 9 inclusive and were all prime numbers. (Our service-book contains 666 hymns and the normal 150 psalms).

    What were last Sunday’s numbers?

    This puzzle was included in the book The Sunday Times Book of Brain-Teasers: Book 2 (1981, edited by Victor Bryant and Ronald Postill).

    [teaser653]

     
    • Jim Randell 7:48 am on 3 November 2022 Permalink | Reply

      This puzzle is very similar to Teaser 467 (and the answer is the same), although the conditions are slightly different.

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

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

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      SubstitutedExpression
      
      --digits="1-9"
      
      # the psalm
      "is_prime(ABC)"
      "ABC < 151"
      
      # the hymns
      "is_prime(DEF)"
      "is_prime(GHI)"
      "DEF < GHI < 667"
      
      --answer="(ABC, DEF, GHI)"
      

      Solution: Psalm 149. Hymn 263. Hymn 587.

      Like

    • GeoffR 8:33 am on 3 November 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      set of int: INT = 1..9;
      var INT: A; var INT: B; var INT: C; % Psalm
      var INT: D; var INT: E; var INT: F; % Hymn 1
      var INT: G; var INT: H; var INT: I; % Hymn 2
      
      constraint all_different ([A, B, C, D, E, F, G, H, I]);
      constraint ABC < DEF /\ DEF < GHI;
      
      % Specified ranges of psalm and hymn numbers
      var 100..150:ABC = 100*A + 10*B + C;
      var 100..666:DEF = 100*D + 10*E + F;
      var 100..666:GHI = 100*G + 10*H + I;
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      constraint sum([is_prime(ABC), is_prime(DEF), is_prime(GHI)]) == 3;
      
      solve satisfy;
      output["Sunday's numbers were " ++ show(ABC) ++ " , " ++ show(DEF)
      ++ " and " ++ show(GHI) ];
      
      % Sunday's numbers were 149 , 263 and 587
      % ----------
      % ==========
      
      

      There are many solutions without the restricted psalm and hymn number ranges.

      Like

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

      
      from enigma import is_prime, nsplit, all_different
      
      # Find Psalm number
      for P1 in range(100,150):
          if not (is_prime(P1)): continue
          A,B,C = nsplit(P1)
          if 0 in (A,B,C):continue
          if not all_different(A,B,C):continue
          
          # Find 1st Hymn number
          for H1 in range(151,667):
              if not (is_prime(H1)):continue
              D,E,F = nsplit(H1)
              if 0 in (D,E,F): continue
              if not all_different(A,B,C,D,E,F):continue
              
              # Find 2nd Hymn number
              for H2 in range(H1+1, 667):
                  if not is_prime(H2):continue
                  G,H,I = nsplit(H2)
                  if 0 in (G,H,I):continue
                  if not all_different(A,B,C,D,E,F,G,H,I):continue
                  print(f"Sunday's numbers were {P1}, {H1}, {H2}")
      
      # Sundays numbers were 149, 263, 587
      
      
      
      

      Like

  • Jim Randell 10:28 am on 22 May 2022 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 87: Inter-schools trophy 

    From The Sunday Times, 25th November 1962 [link]

    Annually in the Easter and summer terms from 1957 Ardington and Bradling competed in five matches at different sports. Points were given for win (6 each for cricket and football, 4 each for hockey, swimming and athletics), the points being shared equally in the case of a draw or tie. The total points score decides the annual winner of the Topp Cup.

    From 1957 to 1961 inclusive Ardington, won the cup three times and Bradling won it twice, but their grand totals of points were then equal. The winning margin decreased each year, from 12 points in 1957 to 2 points in 1961.

    In each of the sports there was [exactly] one draw or tie; the hockey being drawn in 1959. Bradling last won the cricket in 1958, a year in which they won the cup. Ardington have never won the swimming but have the better record at athletics (which they won in 1957).

    What were the results of all matches in the series?

    This puzzle was included in the book Sunday Times Brain Teasers (1974, edited by Ronald Postill).

    [teaser87]

     
    • Jim Randell 10:29 am on 22 May 2022 Permalink | Reply

      I made a MiniZinc model to solve this puzzle:

      %#! minizinc -a --solver chuffed --output-mode json
      
      % results for A
      % x[<year>, <event>] = 0 (loss) | 1 (draw) | 2 (win)
      % <year> = 1 (1957) .. 5 (1961)
      % <event> = 1 .. 5 (C, F, H, S, A)
      array [1..5, 1..5] of var 0..2: x;
      
      % points for each year
      function var int: ptsA(var int: i) = 3 * (x[i, 1] + x[i, 2]) + 2 * (x[i, 3] + x[i, 4] + x[i, 5]);
      function var int: ptsB(var int: i) = 24 - ptsA(i);
      
      % cumulative points
      function var int: cumA(var int: y) = sum (i in 1..y) (ptsA(i));
      function var int: cumB(var int: y) = sum (i in 1..y) (ptsB(i));
      
      % A won the cup 3 times, B won the cup twice
      constraint sum (i in 1..5) (ptsA(i) > ptsB(i)) = 3;
      constraint sum (i in 1..5) (ptsB(i) > ptsA(i)) = 2;
      
      % final cumulative total is equal
      constraint cumA(5) = cumB(5);
      
      % winning margin decreased each year ...
      constraint forall (i in 1..4) (abs(ptsA(i) - ptsB(i)) > abs(ptsA(i + 1) - ptsB(i + 1)));
      % ... from 12 points in 1957 ...
      constraint abs(ptsA(1) - ptsB(1)) = 12;
      % ... to 2 points in 1961
      constraint abs(ptsA(5) - ptsB(5)) = 2;
      
      % each sport has exactly one tie
      constraint forall (j in 1..5) (sum (i in 1..5) (x[i, j] = 1) = 1);
      
      % H was drawn in 1959
      constraint x[3, 3] = 1;
      
      % B last won C in 1958
      constraint x[2, 1] = 0;
      constraint forall (i in 3..5) (x[i, 1] > 0);
      
      % B won the cup in 1958
      constraint ptsA(2) < ptsB(2);
      
      % A have never won the swimming
      constraint forall (i in 1..5) (x[i, 4] < 2);
      
      % team A have a better record at event A
      constraint sum (i in 1..5) (x[i, 5]) > 5;
      
      % team A won event A in 1957
      constraint x[1, 5] = 2;
      
      solve satisfy;
      

      And a Python wrapper to format the output (using the minizinc.py library):

      from enigma import join, printf
      from minizinc import MiniZinc
      
      for [x] in MiniZinc("teaser87.mzn").solve(result='x', use_shebang=1):
        printf("      C F H S A | pA pB | cA cB")
        cA = cB = 0
        for (y, vs) in enumerate(x, start=1957):
          # points for A, B
          pA = sum(x * y for (x, y) in zip(vs, [3, 3, 2, 2, 2]))
          pB = 24 - pA
          # cumulative totals
          cA += pA
          cB += pB
          # output the table row
          r = join(("BdA"[x] for x in vs), sep=" ")
          printf("{y}: {r} | {pA:2d} {pB:2d} | {cA:2d} {cB:2d}")
        printf()
      

      Solution: The full results are (d = drawn):

      1957: cricket = A, football = A, hockey = B, swimming = d, athletics = A; cup = A (18 – 6)
      1958: cricket = B, football = d, hockey = B, swimming = B, athletics = A; cup = B (17 – 7)
      1959: cricket = A, football = B, hockey = d, swimming = B, athletics = B; cup = B (16 – 8)
      1960: cricket = A, football = A, hockey = B, swimming = B, athletics = d; cup = A (14 – 10)
      1961: cricket = d, football = A, hockey = B, swimming = B, athletics = A; cup = A (13 – 11)

      At the end of the 5 years each team has a cumulative total of 60 points each.

      The winning margin for each year is: 12, 10, 8, 4, 2.

      Like

  • Jim Randell 4:35 pm on 14 December 2021 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 890: Round pond boat race 

    From The Sunday Times, 27th August 1978 [link]

    In our park is a circular pond exactly 50 metres in diameter, which affords delight to small boys who go down with ships.

    Two such youngsters, Arthur and Boris, took their model motor-cruisers there the other morning, and decided on a race. Each was to start his boat from the extreme North of the pond at the same moment, after which each was to run round the pond to await his boat’s arrival. The moment it touched shore its owner was to grab it by the bows and run with it directly to the South gate of the park, situated exactly 27 metres due South of the Southern edge of the pond. Both boats travelled at the same speed (1 metre in 3 seconds) but both owners, burdened with their respective craft, could manage only 3 metres in 4 seconds over the rough grass.

    When the race started, Arthur’s boat headed due South, but that of Boris headed somewhat East of South and eventually touched shore after travelling 40 metres in a straight line.

    Who arrived first at the gate, and by what time margin (to the nearest second)?

    This puzzle was included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980, edited by Victor Bryant and Ronald Postill). The puzzle text above is taken from the book.

    [teaser890]

     
    • Jim Randell 4:36 pm on 14 December 2021 Permalink | Reply

      The boats start at S and travel to A and B, and are then carried to the gate at G. O is the centre of the pond, GX is tangent to the pond.

      For A, the distance travelled by the boat is SA = 50m, and the distance on foot is AG = 27m.

      So the total time taken is: 50 × 3 + 27 × 4/3 = 186 seconds.

      For B, we see that the triangle ABS is inscribed in a semicircle, so has a right angle at B, and the sides SA = 50m, SB = 40m (so AB = 30m), so if θ is the angle ASB we have:

      cos(θ) = 40/50 = 4/5

      We can then calculate the straight line distance BG using the cosine rule on triangle GSB:

      (BG)² = (GS)² + (BS)² − 2(GS)(BS)cos(θ)
      (BG)² = 77² + 40² − 2×77×40×4/5
      (BG)² = 2601
      BG = 51

      For B, the distance travelled by the boat is SB = 40m, and the straight line distance on foot is BG = 51m.

      So the total time taken is: 40 × 3 + 51 × 4/3 = 188 seconds.

      So it looks like A beats B by 2 seconds.

      However looking at the diagram, we note that the line BG actually passes through the pond, and so B would have to run around the circumference of the pool (along the arc BX), until he can make a straight line to G (along XG, a tangent to the circle).

      The straight line distance along the tangent, XG, is:

      (XG)² + 25² = (25 + 27)²
      (XG)² = 2079
      XG = 3√(231)

      We can calculate the angle SOB using the cosine rule:

      (SB)² = (OS)² + (OB)² − 2(OS)(OB)cos(α)
      cos(α) = (2×25² − 40²) / 2×25²
      cos(α) = −7/25

      The amount of arc travelled, φ (the angle BOX), is then given by:

      φ = ψ − (α − π/2)

      Where ψ is the angle OGX, which we can calculate using the right angled triangle OXG:

      cos(ψ) = XG / (OB + BG)
      cos(ψ) = 3√(231)/52

      As expected, the difference made by following the arc instead of the straight line is not enough to change the result (which is given to the nearest second).

      Run: [ @replit ]

      from math import acos
      from enigma import fdiv, sq, sqrt, pi, printf
      
      # boat time
      boat = lambda d: d * 3
      
      # foot time
      foot = lambda d: fdiv(d, 0.75)
      
      # given distances
      R = 25
      SA = 2 * R
      SB = 40
      AG = 27
      
      # exact calculation?
      exact = 1
      
      # calculate time for A
      A = boat(SA) + foot(AG)
      printf("A = {A:.6f} sec")
      
      # calculate time for B
      SG = SA + AG
      cos_theta = fdiv(SB, SA)
      BG = sqrt(sq(SG) + sq(SB) - 2 * SG * SB * cos_theta)
      
      B = boat(40) + foot(BG)
      printf("B = {B:.6f} sec (approx)")
      
      if exact:
        # calculate XG
        XG = sqrt(sq(R + AG) - sq(R))
      
        # alpha is the angle SOB
        cos_alpha = fdiv(2 * sq(R) - sq(SB), 2 * sq(R))
        
        # psi is the angle OGX
        cos_psi = fdiv(XG, R + AG)
      
        # calculate phi (amount of arc)
        psi = acos(cos_psi)
        alpha = acos(cos_alpha)
        phi = psi - (alpha - 0.5 * pi)
      
        # calculate the exact distance for B
        BX = R * phi
        B = boat(40) + foot(BX + XG)
        printf("B = {B:.6f} sec (exact) [extra distance = {xB:.6f} m]", xB=BX + XG - BG)
        
      # calculate the difference
      d = abs(A - B)
      printf("diff = {d:.6f} sec")
      
      

      Solution: Arthur beats Boris by approximately 2 seconds.

      Following the arc is 3.95cm longer than the straight line distance, and adds 53ms to B’s time.

      The actual shortest distance from B to G can be expressed as:

      3\sqrt{231}\; +\; 25\cos ^{-1}\left( \frac{7}{52}\; +\; \frac{18\sqrt{231}}{325} \right)

      which is approximately 51.039494 m, compared to the straight line distance BG of 51 m.

      Like

      • galoisest 4:28 pm on 15 December 2021 Permalink | Reply

        Nice analysis Jim, I completely missed that the straight line goes through the pond.

        The simplest expression I can get for the difference including the arc is:

        100/3*(pi-acos(25/52)-2*acos(3/5)) + 4sqrt(231) – 66

        Like

        • Jim Randell 4:37 pm on 15 December 2021 Permalink | Reply

          It was more obvious in my original diagram (which had the line BG, instead of XG).

          But in this case it doesn’t matter if you spot this or not – the answer is unaffected. (I wonder if the setter intended us to calculate the exact distance, or just work out the length of BG).

          Like

  • Jim Randell 10:16 am on 28 October 2021 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 905: Prime square 

    From The Sunday Times, 25th November 1979 [link]

    “Here”, said Uncle Henry to the twins, “is a magic square which I’ve started for you”:

    “You must complete it by putting eight different prime numbers in the eight empty squares, so that the [rows], the columns and the diagonals add up to the same total; and it must be the smallest possible total under the conditions.”

    There followed half an hour of comparative peace… after which the twins could bear it no longer.

    “Oh. Uncle”, complained Betty, “It looks so easy and yet it’s much too difficult”. And Brian fervently agreed.

    “All right”, said Uncle Henry, “I’ll tell you a couple more things: there is one such magic square where the number in the middle square is the average of the two numbers directly above and below it; the third largest number is not in the right-hand column; and each square contains one or two digits”.

    After a further ten minutes, the twins managed to produce the right solution.

    Can you complete the magic square?

    This puzzle was included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980, edited by Victor Bryant and Ronald Postill). The puzzle text above is taken from the book.

    It was also included in the book The Sunday Times Book of Brainteasers (1994, edited by Xerxes).

    [teaser905]

     
    • Jim Randell 10:17 am on 28 October 2021 Permalink | Reply

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

      The following run file executes in 64ms.

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      #  AB  CD  EF
      #  GH  IJ  KL
      #  MN  01  PQ
      
      SubstitutedExpression
      
      --distinct=""
      --invalid=""
      
      # missing numbers are different 1 or 2 digit primes
      "is_prime(AB)"
      "is_prime(CD)"
      "is_prime(EF)"
      "is_prime(GH)"
      "is_prime(IJ)"
      "is_prime(KL)"
      "is_prime(MN)"
      "is_prime(PQ)"
      "all_different(AB, CD, EF, GH, IJ, KL, MN, PQ)"
      
      # in an additive magic square the magic constant is 3 times the centre value (= 3 * IJ)
      "2 * IJ - 1 = CD"
      "2 * IJ - AB = PQ"
      "2 * IJ - EF = MN"
      "2 * IJ - GH = KL"
      "3 * IJ - AB - CD = EF"
      "3 * IJ - MN - 1 = PQ"
      "3 * IJ - AB - GH = MN"
      "3 * IJ - EF - KL = PQ"
      
      # third largest number is _not_ in the RH column
      "ordered(1, AB, CD, EF, GH, IJ, KL, MN, PQ)[-3] not in {EF, KL, PQ}"
      
      --template="(AB CD EF) (GH IJ KL) (MN 01 PQ)"
      --solution=""
      

      Solution: Here is a diagram of the completed square:

      Like

      • Mark Valentine 1:21 pm on 28 October 2021 Permalink | Reply

        Third largest number can’t be in right-hand column. Need to flip it.

        Like

        • Mark Valentine 1:23 pm on 28 October 2021 Permalink | Reply

          Apologies. Misread it. Yours is correct.

          Like

      • Frits 3:41 pm on 28 October 2021 Permalink | Reply

        Using “3 * IJ – AB – MN = GH” instead of “3 * IJ – AB – GH = MN” you don’t have to internally loop over G and H.

        Like

      • Jim Randell 12:35 pm on 29 October 2021 Permalink | Reply

        And here is a Python program that can be used to generate magic squares with larger magic constants:

        from enigma import primes, ordered, arg, printf
        
        # the magic square is:
        #
        #  A B C
        #  D E F
        #  G 1 H
        #
        # so the magic constant k = 3E
        
        # generate magic squares
        def generate():
          # consider values for E
          for E in primes.range(3):
            k = 3 * E
            B = k - E - 1
            if B not in primes: continue
        
            # choose a value for A
            for A in primes.range(3, k - E):
              # calculate the remaining values
              H = k - A - E
              if H not in primes: continue
              C = k - A - B
              if C not in primes: continue
              F = k - C - H
              if F not in primes: continue
              G = k - C - E
              if G + 1 + H != k or G not in primes: continue
              D = k - E - F
              if A + D + G != k or D not in primes: continue
              # check conditions
              if len({A, B, C, D, E, F, G, H}) != 8: continue
              ns = ordered(A, B, C, D, E, F, G, 1, H)
              if ns[-3] in {C, F, H}: continue
              # valid layout
              yield (A, B, C, D, E, F, G, H)
        
        N = arg(1, 0, int)
        for (n, (A, B, C, D, E, F, G, H)) in enumerate(generate(), start=1):
          printf("[ {A} {B} {C} | {D} {E} {F} | {G} 1 {H} ] {k}", k=3 * E)
          if n == N: break
        

        Like

    • Hugh Casement 2:47 pm on 28 October 2021 Permalink | Reply

      It’s superfluous information (though perhaps helpful) that the middle column is an arithmetic progression.
      Rather than say “the third largest number is not in the right-hand column” it would have been simpler to tell us that the smallest prime is in the left-hand column (otherwise we find a reflection in the vertical axis).
      Without the 1 we would need at least two 3-digit primes to make a magic square.

      Like

    • GeoffR 4:12 pm on 28 October 2021 Permalink | Reply

      My solution shows that the 3rd highest number must not be in the right hand column is a definite requirement, as shown in the note at the end of the programme output. I initially wrote a programme without this constraint. I am not sure how this constraint would be programmed in MiniZinc.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      %  A  B  C
      %  D  E  F
      %  G  H  I
      
      var 2..97:A; var 2..97:B; var 2..97:C; 
      var 2..97:D; var 2..97:E; var 2..97:F;
      var 2..97:G; var 2..97:I;
      
      int: H == 1;
      constraint all_different([A, B, C, D, E, F, G, H, I]);
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
      
      % All values of the grid (except H) are primes
      constraint is_prime(A) /\ is_prime(B) /\ is_prime(C)
      /\ is_prime(D) /\ is_prime(E) /\ is_prime(F)
      /\ is_prime(G) /\ is_prime(I);
      
      % All rows, columns and diagonals add to the same total
      constraint A + B + C == D + E + F /\ A + B +  C == G + H + I
      /\  A + B + C == A + D + G /\  A + B + C == B + E + H 
      /\  A + B + C == C + F + I /\  A + B + C == A + E + I
      /\  A + B + C == C + E + G;
      
      % the middle square is the average of the two numbers directly above and below it
      constraint 2 * E == B + H \/ 2 * D == A + G \/ 2 * F == C + I;
      
      %  the smallest possible total
      solve minimize(A + B + C);
      
      output [ "[A,B,C,D,E,F,G,H,I] = " ++ show([A,B,C,D,E,F,G,H,I] ) ];
      
      % [A, B, C, D, E, F, G, H, I] = [31, 73, 7, 13, 37, 61, 67, 1, 43]
      % ----------
      % ==========
      % Analysis of this solution
      % -------------------------
      % the third highest number (61) must not be in the third column
      % it is moved to the left hand column by transposing left and right columns
      % 31  73  7   =>   7  73  31
      % 13  37  61  =>  61  37  13
      % 67   1  43  =>  43   1  67
      
      
      
      

      Like

      • Frits 10:28 am on 29 October 2021 Permalink | Reply

        @GeoffR, you can declare an array, fill it with same elements as the original array and add an “increasing” constraint.

          
        % A Solution in MiniZinc
        include "globals.mzn";
         
        %  A  B  C
        %  D  E  F
        %  G  H  I
         
        var 2..97:A; var 2..97:B; var 2..97:C; 
        var 2..97:D; var 2..97:E; var 2..97:F;
        var 2..97:G; var 2..97:I;
         
        int: H == 1;
        constraint all_different([A, B, C, D, E, F, G, H, I]);
        
        array[1..9] of var 1..97: nbrs = [A, B, C, D, E, F, G, H, I];
        array[1..9] of var 1..97: sorted;
        
        % make sure both arrays contain same elements
        constraint forall(X in nbrs)(
                      exists(i in 1..9) ( X = sorted[i])
        );
        
        % make sure array "sorted" is sorted
        constraint increasing(sorted);
         
        predicate is_prime(var int: x) = x > 1 /\ 
        forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
         
        % All values of the grid (except H) are primes
        constraint is_prime(A) /\ is_prime(B) /\ is_prime(C)
        /\ is_prime(D) /\ is_prime(E) /\ is_prime(F)
        /\ is_prime(G) /\ is_prime(I);
         
        % All rows, columns and diagonals add to the same total
        constraint A + B + C == D + E + F /\ A + B + C == G + H + I
        /\  A + B + C == A + D + G /\  A + B + C == B + E + H 
        /\  A + B + C == C + F + I /\  A + B + C == A + E + I
        /\  A + B + C == C + E + G;
         
        % the middle square is the average of the two numbers
        % directly above and below it
        constraint 2 * E == B + H;
        
        % third largest number is not in the RH column
        constraint C != sorted[7] /\ F != sorted[7] /\ I != sorted[7];
         
        %  the smallest possible total
        solve minimize(A + B + C);
         
        output [show([A,B,C]) ++ "\n" ++ show([D,E,F]) ++ "\n" ++ show([G, H, I])];
        

        Like

    • GeoffR 11:00 am on 29 October 2021 Permalink | Reply

      @Frits:

      Yes, that is a neat solution to ensure that the third largest number is not in the right-hand column.

      Like

  • Jim Randell 9:04 am on 12 January 2021 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 660: An efficient type 

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

    My typewriter had the standard keyboard:

    row 1: QWERTYUIOP
    row 2: ASDFGHJKL
    row 3: ZXCVBNM

    until I was persuaded by a time-and-motion expert to have it “improved”. When it came back I found that none of the letters was in its original row, though the number of letters per row remained unchanged. The expert assured me that, once I got used to the new system, it would save hours.

    I tested it on various words connected with my occupation — I am a licensed victualler — with the following results. The figures in parentheses indicate how many rows I had to use to produce the word:

    BEER (1)
    STOUT (1)
    SHERRY (2)
    WHISKY (3)
    HOCK (2)
    LAGER (2)
    VODKA (2)
    CAMPARI (2)
    CIDER (3)
    FLAGON (2)
    SQUASH (2, but would have been 1 but for a single letter)

    Despite feeling a trifle MUZZY (a word which I was able to type using two rows) I persevered, next essaying CHAMBERTIN.

    Which rows, in order, did I use?

    This puzzle was included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980, edited by Victor Bryant and Ronald Postill). The puzzle text above is taken from the book.

    [teaser660]

     
    • Jim Randell 9:05 am on 12 January 2021 Permalink | Reply

      We can use the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this puzzle. Although the program generated does not run under the standard CPython interpreter, as it exceeds the limit on statically nested blocks, but it runs fine with the PyPy interpreter, which does not have this limitation, or we can use the recently added [[ --denest ]] option, which generates code that is not as deeply nested, and allows the program to run under the standard Python interpreter.

      The following run file executes in 93ms.

      Run: [ @replit ]

      #! python -m enigma -r
      
      SubstitutedExpression
      
      --distinct=""
      --digits="1,2,3"
      
      # no letter is in its original row
      --invalid="1,QWERTYUIOP"
      --invalid="2,ASDFGHJKL"
      --invalid="3,ZXCVBNM"
      
      # number of letters per row remained unchanged
      --code="count = lambda s: tuple(s.count(x) for x in (1, 2, 3))"
      "count([A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]) == (10, 9, 7)"
      
      # rows used for each word
      "len({B, E, E, R}) = 1"
      "len({S, T, O, U, T}) = 1"
      "len({S, H, E, R, R, Y}) = 2"
      "len({W, H, I, S, K, Y}) = 3"
      "len({H, O, C, K}) = 2"
      "len({L, A, G, E, R}) = 2"
      "len({V, O, D, K, A}) = 2"
      "len({C, A, M, P, A, R, I}) = 2"
      "len({C, I, D, E, R}) = 3"
      "len({F, L, A, G, O, N}) = 2"
      "len({S, Q, U, A, S, H}) = 2"
      "len({M, U, Z, Z, Y}) = 2"
      
      # SQUASH is all in one row, except for a single letter
      "1 in multiset([S, Q, U, A, S, H]).values()"
      
      # answer is the rows involved in typing CHAMBERTIN
      --answer="(C, H, A, M, B, E, R, T, I, N)"
      
      # [optional] less verbose output
      --template=""
      
      # [experimental] work around statically nested block limit
      --denest
      

      Solution: The rows involved in typing CHAMBERTIN are: 1, 3, 1, 2, 2, 2, 2, 3, 2, 1.

      The new assignment of rows to keys is:

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

      Although we don’t know what order the keys are in in a row.

      Liked by 1 person

      • Frits 9:54 am on 12 January 2021 Permalink | Reply

        @Jim, repl.it isn’t working for this puzzle

        Like

        • Jim Randell 10:07 am on 12 January 2021 Permalink | Reply

          @Frits: Odd. It works for me if I’m logged in, but not if I try to run the repl anonymously. You have to jump through some hoops to get PyPy running in repl.it, so it is not surprising the end result is a bit fragile.

          Like

          • Frits 10:21 am on 12 January 2021 Permalink | Reply

            Yesterday I updated PyPy. I didn’t realize repl.it depended on it.

            (error: Makefile:52: recipe for target ‘exec_pip’ failed). I’ll try to reinstall Pip under PyPy.

            Have you already used the 64 bit PyPy nightly build?

            Liked by 1 person

            • Jim Randell 10:49 am on 12 January 2021 Permalink | Reply

              @Frits: I don’t think your local installation of PyPy will affect repl.it. I think the problem is running PyPy under repl.it (which is not directly supported). Maybe the template I used to allow you to do this only works for the owner of the repl. (You could try forking it and see if that works for you).

              Like

            • Frits 10:57 am on 12 January 2021 Permalink | Reply

              I have now installed the latest nightly build of PyPy ([PyPy 7.3.4-alpha0 with MSC v.1927 64 bit (AMD64)] on win32).

              It seems to be a recent error in PyPy.

               
              https://foss.heptapod.net/pypy/pypy/-/issues/3342 
              

              “pypy3 -m ensurepip” doesn’t give errors anymore but repl.it still fails.

              Like

        • Jim Randell 2:09 am on 13 January 2021 Permalink | Reply

          @Frits: It should be working now.

          Like

          • Frits 10:53 am on 13 January 2021 Permalink | Reply

            Thanks, it works but it takes approx 30 seconds.
            The program itself takes 39ms.

            Like

      • Frits 10:14 am on 12 January 2021 Permalink | Reply

        @Jim,

        If the S of SQUASH was the single letter in a different row the multiset line would not recognize it (it depends how you interpret “a single letter”).

        Do you know an easy way how I can execute the run file like a normal py file in a Command Prompt (without creating a main each and every time)?

        Like

        • Jim Randell 10:41 am on 12 January 2021 Permalink | Reply

          @Frits: I would have thought if the S was on a different line then typing SQUASH would involve typing 2 letters that were on a different row. But the puzzle text is open to interpretation there. You can just remove one of the S‘s from the list on line 32 to try the other interpretation. Fortunately it doesn’t change the answer.


          On Unix like operating systems the #! line tells the OS how to execute a file, so the file just needs to be executable. (And a while ago I made a command called run, to make it even easier to run such files).

          Otherwise you can just run the command manually. So for this file:

          % pypy -m enigma -r teaser660.run
          

          Or if you haven’t put enigma.py in your $PYTHONPATH, just put them in the same directory (or specify full paths):

          % pypy enigma.py -r teaser660.run
          

          Alternatively you can run it from the Python shell, using the [[ enigma.run() ]] function:

          % pypy
          >>>> from enigma import run
          >>>> run("teaser660.run")
          A=1 B=2 C=1 D=3 E=2 F=1 G=1 H=3 I=2 J=1 K=1 L=1 M=2 N=1 O=3 P=2 Q=3 R=2 S=3 T=3 U=3 V=1 W=2 X=1 Y=2 Z=2
          (C, H, A, M, B, E, R, T, I, N) = (1, 3, 1, 2, 2, 2, 2, 3, 2, 1) [1 solution]
          

          (I have [[ from enigma import * ]] in my .pythonrc file, so code from enigma.py is always available in interactive Python shells, and I don’t need to import them separately).

          Like

          • Frits 11:08 am on 12 January 2021 Permalink | Reply

            @Jim, thanks.

            It doesn’t work if the filetype is py but that’s OK.

            Like

    • Frits 11:05 am on 13 January 2021 Permalink | Reply

      @Jim, 4th run still takes 15 second, it doesn’t matter.

      I am busy with a program to solve the puzzle without using brute force but am kind of stuck….

      Like

    • Jim Randell 6:18 pm on 13 January 2021 Permalink | Reply

      People who use the [[ SubstitutedExpression ]] solver from the enigma.py library might be interested in the experimental version of enigma.py in this repl [ @repl.it ].

      It adds the denest parameter to [[ SubstitutedExpression]] (which available as --denest or -X from the command line or in run files), that changes the way the code is generated to reduce the amount of nesting in the generated code.

      The upshot of this is, if you are using [[ SubstitutedExpression ]] and you run foul of the "SyntaxError: too many statically nested blocks" issue, you can just use [[ SubstitutedExpression(..., denest=1) ]] (or add the --denest or -X parameter to a run file or on the command line).

      The run file given above, with the additional --denest parameter runs in 93ms using CPython 2.7.

      Assuming no problems show up in further testing, this will be rolled out in the next enigma.py update.

      Update: I have rolled this out in the 2021-01-14 version of enigma.py.

      Like

      • Frits 7:05 pm on 13 January 2021 Permalink | Reply

        @Jim Thanks, it works.

        However, –verbose=256 doesn’t seem to work properly anymore.

        Like

        • Frits 10:55 pm on 16 January 2021 Permalink | Reply

          @Jim, I don’t know if this is caused by your latest update.

          from enigma import SubstitutedExpression
          
          # the alphametic puzzle
          p = SubstitutedExpression(
            [
            "WXYZ = 3713",
            ],
            distinct="",
            digits="1,3,5,7,9",
            answer="W, XYZ",
            d2i={},
            verbose=256
            )   
          
          for (_, ans) in p.solve():  
            print(f"{ans}")
          
          

          No solution is given, see generated code:

           
          [SubstitutedExpression: replacing ({WXYZ} = 3713) -> (3713 = {WXYZ})]
          -- [code language="python"] --
          def _substituted_expression_solver1():
            try:
              _x_ = int(3713)
            except NameError:
              raise
            except:
              raise
            if _x_ >= 0:
              _Z = _x_ % 10
              if _Z != 0 and _Z != 1 and _Z != 2 and _Z != 3 and _Z != 4 and _Z != 5 and _Z != 6 and _Z != 7 and _Z != 8 and _Z != 9:
                _x_ //= 10
                _Y = _x_ % 10
                if _Y != 0 and _Y != 1 and _Y != 2 and _Y != 3 and _Y != 4 and _Y != 5 and _Y != 6 and _Y != 7 and _Y != 8 and _Y != 9:
                  _x_ //= 10
                  _X = _x_ % 10
                  if _X != 0 and _X != 1 and _X != 2 and _X != 3 and _X != 4 and _X != 5 and _X != 6 and _X != 7 and _X != 8 and _X != 9:
                    _x_ //= 10
                    _XYZ = _Z + _Y*10 + _X*100
                    _W = _x_ % 10
                    if _W != 0 and _W != 1 and _W != 2 and _W != 3 and _W != 4 and _W != 5 and _W != 6 and _W != 7 and _W != 8 and _W != 9:
                      _x_ //= 10
                      if _x_ == 0:
                        _WXYZ = _Z + _Y*10 + _X*100 + _W*1000
                        _r_ = (_W), (_XYZ)
                        yield ({ 'W': _W, 'X': _X, 'Y': _Y, 'Z': _Z }, _r_)
          

          Like

          • Jim Randell 11:08 pm on 16 January 2021 Permalink | Reply

            @Frits

            The digits parameter should be a collection of permissible digits (not a string), e.g.:

            digits=(1,3,5,7,9)
            

            Like

            • Frits 11:40 pm on 16 January 2021 Permalink | Reply

              Thanks, I copied it from the run version (something which I normally don’t do).

              Like

    • Frits 6:17 pm on 14 January 2021 Permalink | Reply

      Value 0b110 means letter can reside in row 1 or 2.

      from itertools import combinations as comb
      
      letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      
      orig = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"]
      sol  = ["ACFGJKLNVX", "BEIMPRWYZ", "DHOQSTU"]     # only used for printing
      
      common1 = [set("BEER"), set("STOUT")]
      common2 = [set("LAGER"), set("CAMPARI"), set("SHERRY"), set("HOCK"), 
                 set("VODKA"), set("FLAGON"), set("SQUASH"), set("MUZZY")]
      common3 = [set("WHISKY"), set("CIDER")]           
      
      r = {4 : "1", 2 : "2", 1 : "3"}   # row number
      
      # return 0 if all letters have been uniquely placed in a row
      def unsolved():
        c = 0
        for let in letters:
          c += d[let]  
          if d[let] not in {1, 2, 4}:
            return c
        return 0
        
      # remove <val> from letter <let> 
      def remove(let, val, text = ""):
        if d[let] & val == 0: return   # already removed
        
        d[let] = d[let] & (7 - val)
        if text != "":
          print(f"remove letter {let} from row {r[val]} {text}")
      
      # one row in common
      def cond1(s):
        # skip if all letters in <s> are known 
        if all(d[x] in {1, 2, 4} for x in s): return
        
        # check if letters in <s> have only one common bit
        common = 7
        for x in s:
          common &= d[x]
        
        if common in {1, 2, 4}:
          print(f"place letters {','.join(s)} in row {r[common]} ", end = "")
          print("(as they have to share 1 row)")
          for x in s:
            d[x] = common   # set all letters to the same value
            
      # two rows in common
      def cond2(s):
        known = [x for x in s if d[x] in {1, 2, 4}]
        rows_used = set(d[x] for x in known)
        
        if len(rows_used) == 2:
          # we have solved letters in 2 rows
          # so all letters in <s> have to be in these 2 rows
          twoRows = sum(rows_used)
          missing_row = 7 - twoRows
          
          for x in s:
            if x in known: continue
            # letter can be in either one of the 2 rows?
            if d[x] == twoRows: continue
            
            rows_used = sorted(list(rows_used), reverse=1)
            text = "(letters "+ ",".join(s) +" must occur in rows " 
            text += ", ".join(r[x] for x in rows_used) + ")"
            remove(x, missing_row, text)
       
      # three rows in common
      def cond3(s):
        # for each row check candidates
        for i in [1, 2, 4]:
          li = [x for x in s if d[x] & i]
           
          if len(li) == 1 and d[li[0]] != i:   # one candidate for row i
            print(f"place letter {li[0]} in row {r[i]} (other ", end = "")
            print(f"letters in {','.join(s)} are not possible in row {r[i]})")
            d[li[0]] = i
            return
      
      # check if all letters in a row are known
      def row_full():
        for i, n in enumerate([4, 2, 1]):
          c = 0
          for let in letters:
            if d[let] == n:
              c += 1
          if c == len(orig[i]):
            # row <i> is full so remove <i> from other candidates
            for let in letters:
              if d[let] == n: continue
              remove(let, n, "(already " + str(len(orig[i])) + 
                     " letters have to be in row " + r[n] +")")
      
      # check whether the number of letters in bottom 2 rows exceeds 16        
      def bottom2(c1 ,c2):  
        common_letters = c1 & c2  
        if len(common_letters) < 2: return False
        
        known = [x for x in common_letters if d[x] in {1, 2, 4}]
        rows_used = list(set(d[x] for x in known))
        if len(rows_used) != 1: return False
        
        bot2 = [x for x in common_letters if x != known[0] and 
                x not in orig[0] and   
                d[x] & d[known[0]] == 0] # different row from known
                
        # known[0] in one bottom row and <bot2> in the other bottom row?       
        if len(bot2) > 0:
          # suppose <bot2> is not in top row, so <bot2> and <known> encompass 
          # 2 rows, this means that all letters in c1 and c2 are in bottom 2 rows
          # check if letters in bottom 2 rows exceeds 16 (9 + 7)
          
          # count entries in 2 bottom rows if <bot2> is not in top row
          li = [x for x in letters 
                if x in orig[0] or        # the 10 QWERTYUIOP entries
                ((d[x] & 4) == 0 or       # not in top row
                  x in (c1 | c2) )]       # in c1 or in c2
                  
          max = len(orig[1]) + len(orig[2])
          if len(li) > max:      
            # put QWERTY... up front
            notTop = {x for x in orig[0]} | {x for x in letters if d[x] & 4 == 0}
            # letter <bot2[0]> can not be in bottom 2 rows
            text = "(assuming letter " + bot2[0] + " in bottom 2 rows leads to\n"
            text += "letters " + ",".join(notTop | c1 | c2) 
            text += " in bottom 2 rows (exceeding total of " + str(max) + "))"
            remove(bot2[0], 1, text)
            remove(bot2[0], 2, text)
            return True
            
        return False   
           
      # print rows  
      def report(li):
        print()
        for i, x in enumerate(li): 
          for y in x: 
            if d[y] == (1 << (2 - i)):  # letter is known
              print(f"{y} = {d[y]:<5}", end=" ")
            else:  
              print(f"{y} = {d[y]:#05b}", end=" ")   
          print()
        print()  
      
      
      d = dict()
      # initialize letters 
      for x in letters:
        d[x] = 7
        
      # remove original row number for each letter
      for i, y in enumerate(orig):
        for x in y:
          remove(x, (1 << (2-i)))
       
      prev = 0
      for loop in range(99):
        for x in common1:
          cond1(x)
        for x in common2:
          cond2(x)
        for x in common3:
          cond3(x)
         
        row_full()         # check if all letters in a row are known
         
        nr_unsolved = unsolved()
        if nr_unsolved == 0:   # are we done?
          report(sol)
          
          squash_rows = sorted(d[x] for x in "SQUASH")
          if squash_rows[0] != squash_rows[1] or \
             squash_rows[-1] != squash_rows[-2]:
            print("letters in SQUASH are all in one row but for a single letter")
          break
        
        report(sol)
       
        if nr_unsolved == prev:    # nothing has been solved in this loop 
          # check all combinations of words which reside on 2 rows
          for c1, c2 in comb(common2, 2):
            # check whether the number of letters in bottom 2 rows exceeds 16
            if bottom2(c1, c2): break
        prev = nr_unsolved
      

      Like

    • John Crabtree 1:04 am on 15 January 2021 Permalink | Reply

      Q, W, E, R, T, Y U, I, O, P (10)
      A, S, D, F, G, H, J, K, L (9)
      Z, X, C, V, B, N, M (7)

      BEER (1) B, E, R -> row 2
      STOUT (1) S, T, O, U -> row 3
      only 3 more letters can go to row 3, and from CIDER, at least one must be D or I
      LAGER (2) A, G, L -> row 1
      SQUASH (2*) Q, H -> row 3 (full except for D or I)
      first row P, W, Y – > row 2
      second row F, J, K -> row 1
      HOCK (2) C -> row 1
      CIDER (3) D -> row 3 (full), I -> row 2
      MUZZY (2) M, Z -> row 2 (full)
      third row N, X, V -> row 1

      CHAMBERTIN comes from rows 1, 3, 1, 2, 2, 2, 2, 3, 2, 1

      In this method WHISKY (3), VODKA (2), CAMPARI (2) and FLAGON (2) are redundant

      Like

      • Jim Randell 5:22 am on 15 January 2021 Permalink | Reply

        If I remove the WHISKY, VODKA, CAMPARI, FLAGON conditions in my program it finds 4 possible solutions. So they can’t all be redundant.

        But adding just CAMPARI back in gets us back to a unique solution.

        Like

        • Frits 9:52 am on 15 January 2021 Permalink | Reply

          True, the problem lies with the CIDER (3) D …. deduction.

          Like

          • John Crabtree 3:33 pm on 15 January 2021 Permalink | Reply

            Jim and Frits, thank you. originally I used CAMPARI. In my incorrect CIDER (3) deduction I thought that E and R were in row 1. 🙂

            Like

    • GeoffR 4:13 pm on 15 January 2021 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      set of int: rows = 1..3;
      
      var rows: A; var rows: B; var rows: C; var rows: D;
      var rows: E; var rows: F; var rows: G; var rows: H;
      var rows: I; var rows: J; var rows: K; var rows: L;
      var rows: M; var rows: N; var rows: O; var rows: P;
      var rows: Q; var rows: R; var rows: S; var rows: T;
      var rows: U; var rows: V; var rows: W; var rows: X;
      var rows: Y; var rows: Z; 
      
      % Standard keyboard layout
      % row1 = Q, W, E, R, T, Y, U, I, O, P (10 letters)
      % row2 = A, S, D, F, G, H, J, K, L  (9 letters)
      % row3 = Z, X, C, V, B, N, M  (7 letters)
      
      % 1st row letters must be in a different row
      constraint Q != 1 /\ W != 1 /\ E != 1 /\ R != 1
      /\ T != 1 /\ Y != 1 /\ U != 1 /\ I != 1
      /\ O != 1 /\ P != 1;
      
      % 2nd row letters must be in a different row
      constraint A != 2 /\ S != 2 /\ D != 2 /\ F != 2
      /\ G != 2 /\ H != 2 /\ J != 2 /\ K != 2 /\ L != 2;
      
      % 3rd row letters must be in a different row
      constraint Z != 3 /\ X != 3 /\ C != 3 /\ V != 3
      /\ B != 3 /\ N != 3 /\ M != 3;
      
      % Letter constraints for three new rows
      % New 1st row
      constraint count_eq ( [A,B,C,D,E,F,G,H,I,J,K,L,M,N,
      O,P,Q,R,S,T,U,V,W,X,Y,Z], 1, 10); 
      
      % New 2nd row
      constraint count_eq ( [A,B,C,D,E,F,G,H,I,J,K,L,M,N,
      O,P,Q,R,S,T,U,V,W,X,Y,Z], 2, 9);
      
      % New 3rd row
      constraint count_eq ( [A,B,C,D,E,F,G,H,I,J,K,L,M,N,
      O,P,Q,R,S,T,U,V,W,X,Y,Z], 3, 7); 
      
      % (Rows) I had to use to produce the words
      % BEER(1)
      constraint card({B,E,E,R}) == 1;
      % STOUT (1)
      constraint card ({S,T,O,U,T}) == 1;
      % SHERRY (2)
      constraint card({S,H,E,R,R,Y}) == 2;
      % WHISKY (3)
      constraint card({W,H,I,S,K,Y}) == 3;
      % HOCK (2)
      constraint card({H,O,C,K}) == 2;
      % LAGER (2)
      constraint card( {L,A,G,E,R}) == 2;
      % VODKA (2)
      constraint card({V,O,D,K,A}) == 2;
      % CAMPARI (2)
      constraint card ({C,A,M,P,A,R,I}) == 2;
      % CIDER (3)
      constraint card ({C,I,D,E,R}) == 3;
      % FLAGON (2)
      constraint card({F,L,A,G,O,N}) == 2;
      
      % SQUASH (2, but would have been 1 but for a single letter)
      
      % There are two rows for these letters. 
      % If four letters in 'QUASH' (i.e. missing first S out)
      % have a cardinality of 1, the other letter one must be
      % in a different row on its own. 
      
      constraint 
           (card ({U,A,S,H}) == 1 \/ card ({Q,A,S,H}) == 1
           \/ card ({Q,U,S,H}) == 1 \/ card ({Q,U,A,H}) == 1
           \/ card ({Q,U,A,S}) == 1);
      
      % MUZZY(2 rows)
      constraint card({M,U,Z,Z,Y}) == 2;
      
      output [ "CHAMBERTIN rows are " ++ 
      show([C,H,A,M,B,E,R,T,I,N]) ++ "\n"
      ++ "\nLETTERS : [A, B, C, D, E, F, G, H, I, J, K, L, M]"
      ++ "\nROWS :    " ++ show( [A, B, C, D, E, F, G, H, I, J, K, L, M])
      
      ++ "\nLETTERS : [N, O, P, Q, R, S, T, U, V, W, X, Y, Z]"
      ++ "\nROWS :    " ++ show([N, O, P, Q, R, S, T, U, V, W, X, Y, Z])
      ++ "\n"
      ++ "\n" ++ "New Row 1 is [A,C,F,G,J,K,L,N,V,X] " 
      ++ "\n" ++ "New Row 2 is [B,E,I,M,P,R,W,Y,Z] " 
      ++ "\n" ++ "New Row 3 is [D,H,O,Q,S,T,U]" ];
      
      % CHAMBERTIN rows are [1, 3, 1, 2, 2, 2, 2, 3, 2, 1]
      
      % LETTERS : [A, B, C, D, E, F, G, H, I, J, K, L, M]
      % ROWS      [1, 2, 1, 3, 2, 1, 1, 3, 2, 1, 1, 1, 2]
      % LETTERS : [N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
      % ROWS      [1, 3, 2, 3, 2, 3, 3, 3, 1, 2, 1, 2, 2]
      % New rows follow from above values
      % New Row 1 is [A,C,F,G,J,K,L,N,V,X] 
      % New Row 2 is [B,E,I,M,P,R,W,Y,Z] 
      % New Row 3 is [D,H,O,Q,S,T,U]
      % ----------
      % ==========
      

      Like

  • Jim Randell 9:48 am on 21 June 2020 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 523 

    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?

    [teaser523]

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

      This Python program runs in 52ms.

      Run: [ @repl.it ]

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

  • Jim Randell 11:43 am on 12 April 2020 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 517 

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

    The final table of the inter-zone Rugby championships of Pongoland reads:

    Each tribe used only one goal-kicker; C’s kicked most points and A’s least. C had only one penalty scored against them.

    The only types of score were:

    try converted to goal (5 points)
    try unconverted (3 points)
    penalty goal (3 points)

    How were the two sides’ scores made up in the match Aboma vs. Bwaga?

    [teaser517]

     
    • Jim Randell 11:44 am on 12 April 2020 Permalink | Reply

      It seems that the points scored by the kicker are: 2 points for a conversion; 0 points for a try; 3 points for a penalty goal.

      I wrote a MiniZinc model to solve the problem:

      %#! minizinc -a
      
      % xYZ = number of score type x (= c (5), t (3), p (3)) by Y against Z
      
      var 0..2: cAB;
      var 0..4: tAB;
      var 0..4: pAB;
      var 0..2: cBA;
      var 0..4: tBA;
      var 0..4: pBA;
      
      var 0..3: cAC;
      var 0..5: tAC;
      var 0..5: pAC;
      var 0..1: cCA;
      var 0..3: tCA;
      var 0..3: pCA;
      
      var 0..2: cBC;
      var 0..4: tBC;
      var 0..4: pBC;
      var 0..1: cCB;
      var 0..3: tCB;
      var 0..3: pCB;
      
      % total points
      function var int: points(var int: c, var int: t, var int: p) = 5 * c + 3 * t + 3 * p;
      
      % kicked points
      function var int: kicks(var int: c, var int: t, var int: p) = 2 * c + 3 * p;
      
      % point values in the table
      constraint points(cAB, tAB, pAB) + points(cAC, tAC, pAC) = 18;
      constraint points(cBA, tBA, pBA) + points(cCA, tCA, pCA) = 12;
      
      constraint points(cBA, tBA, pBA) + points(cBC, tBC, pBC) = 14;
      constraint points(cAB, tAB, pAB) + points(cCB, tCB, pCB) = 13;
      
      constraint points(cCA, tCA, pCA) + points(cCB, tCB, pCB) = 9;
      constraint points(cAC, tAC, pAC) + points(cBC, tBC, pBC) = 16;
      
      % A won both their games, C lost both their games
      constraint points(cAB, tAB, pAB) > points(cBA, tBA, pBA); % A beat B
      constraint points(cAC, tAC, pAC) > points(cCA, tCA, pCA); % A beat C
      constraint points(cBC, tBC, pBC) > points(cCB, tCB, pCB); % B beat C
      
      % kicked points: C > B > A
      constraint kicks(cAB, tAB, pAB) + kicks(cAC, tAC, pAC) < kicks(cBA, tBA, pBA) + kicks(cBC, tBC, pBC);
      constraint kicks(cBA, tBA, pBA) + kicks(cBC, tBC, pBC) < kicks(cCA, tCA, pCA) + kicks(cCB, tCB, pCB);
      
      % C had 1 penalty scored against them
      constraint pAC + pBC = 1;
      
      solve satisfy;
      

      Solution: Aboma scored 2 conversions (for 10 points). Bwaga scored 1 try and 1 penalty (for 6 points)

      The results are:

      A vs. B: 10 pts (2c) vs. 6 pts (t + p)
      A vs. C: 8 pts (c + t) vs. 6 pts (2p)
      B vs. C: 8 pts (c + p) vs. 3 pts (p)

      A’s kicker made 6 points.
      B’s kicker made 8 points.
      C’s kicker made 9 points.

      Like

  • Jim Randell 2:31 pm on 26 November 2019 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 511: What’s my age? 

    From The Sunday Times, 28th March 1971 [link]

    I recently had an odd letter from a puzzle addict, who wrote:

    “I know that you are between 25 and 80, and I’ve made a bet (at fair odds) that I can deduce your age from your Yes/No answers to the following questions:

    1. Are you under 55?
    2. Is your age a prime number?
    3. If the digits of your age are reversed, is the result prime?
    4. Is the digital root of your age even?

    Please reply on enclosed stamped postcard.”

    I did so, and had a reply a few days later:

    “Many thanks. As soon as I read your first three answers I knew that the fourth answer must lead me to your age. You are …” (and he gave a figure wrong by well over 20 years).

    Puzzled, I rechecked my four answers and found to my horror that I had carelessly transposed two of them and so had misled him.

    How old am I?

    This puzzle was included in the book Sunday Times Brain Teasers (1974, edited by Ronald Postill).

    [teaser511]

     
    • Jim Randell 2:32 pm on 26 November 2019 Permalink | Reply

      This Python program runs in 73ms.

      Run: [ @repl.it ]

      from collections import defaultdict
      from enigma import irange, is_prime, nreverse, digrt, subsets, update, printf
      
      # collect <answers> -> <ages>
      d = defaultdict(list)
      
      # consider possible ages
      for n in irange(25, 80):
        # the statements
        ss = (
          # 1. "Age is under 55?"
          (n < 55),
          # 2. "Age is prime?"
          is_prime(n),
          # 3. "Reverse of age is prime?"
          is_prime(nreverse(n)),
          # 4. "Digital root of age is even?"
          (digrt(n) % 2 == 0),
        )
        d[ss].append(n)
      
      # generate sequences with two (different) values swapped
      def swap(s):
        s = list(s)
        # swap two of the values
        for ((i, x), (j, y)) in subsets(enumerate(s), size=2):
          if x != y: yield tuple(update(s, [(i, y), (j, x)]))
      
      # find values for the first three questions where the final question gives a definite answer
      Bool = (True, False)
      for k in subsets(Bool, size=3, select="M"):
        # possible mistaken keys
        ks = list(k + (x,) for x in Bool)
        # each must lead to a single answer
        if not all(len(d[k]) == 1 for k in ks): continue
        # consider possible mistaken keys
        for k in ks:
          m = d[k][0] # mistaken age
          # look for possible real keys
          for r in swap(k):
            # possible real age
            for n in d[r]:
              # must be more than 20 years difference
              if abs(n - m) > 20:
                printf("age = {n}, real = {r}; swap = {k}, age = {m}")
      

      Solution: The setter’s age is 71.


      There is only one set of values for the answers to the first three questions that lead to two ages that can be differentiated by the answer to the fourth question.

      If all the first three questions are answered “Yes”, then:

      If the answer to the fourth question is “Yes”, the age is 31.
      If the answer to the fourth question is “No”, the age is 37.

      The setter then discovers that he has exchanged the places of two of the answers.

      So the answer sent cannot be: (“Yes”, “Yes”, “Yes”, “Yes”), as swapping any two of them would make no difference.

      So the sent answers must have been: (“Yes”, “Yes”, “Yes”, “No”). Leading the puzzler to believe that the setters age was 37.

      But this is incorrect “by well over 20 years”, so the setter must be much less than 17 (not possible) or much more than 57.

      If we move the “No” into different positions we get:

      (“No”, “Yes”, “Yes”, “Yes”) → 71
      (“Yes”, “No”, “Yes”, “Yes”) → 35, 38
      (“Yes”, “Yes”, “No”, “Yes”) → 29, 47, 53

      Only the first of these gives an age much more than 57, so the answers for the first and fourth question were accidentally swapped.

      Like

  • Jim Randell 8:58 am on 6 October 2019 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 504 

    From The Sunday Times, 7th February 1971 [link]

    The inter-house Scrumps competition has just ended at Marlinghurst School. The four houses play each other once and the scoring is on a league basis (win 2, draw 1, lose 0). Arne played Bach in the 1st round, Chopin in the 2nd and Debussy in the 3rd.

    In the 1st round no two houses scored the same number of “scrumps” (a scrump is a sort of goal scored by forcing an opponent through a hole in the wall).

    In the 2nd and 3rd rounds every house scored exactly twice as many scrumps as its current opponent had scored in the previous round.

    Chopin and Debussy tied on points, but a better scrump average (i.e. ratio of scrumps for to scrumps against) gave Debussy the Pergolesi Bowl. Arne and Bach also tried, but Arne’s lower scrump average put them fourth in the final table.

    Although the total number of scrumps scored in the competition fell short of the school record (55) it was higher than last year’s total (44).

    What was:

    (a) Arne’s scrump average?
    (b) the score in the Bach-Chopin match?

    [teaser504]

     
    • Jim Randell 9:00 am on 6 October 2019 Permalink | Reply

      If we know the scores in the first round (say: a, b, c, d scrumps, scored by A, B, C, D respectively), then the scores in each match are:

      (1) A vs B = a – b; C vs D = c – d
      (2) A vs C = 2c – 2a; B vs D = 2d – 2b
      (3) A vs D = 4b – 4c; B cs C = 4a – 4d

      So the total number of scrumps scored is: 7(a + b + c + d).

      And this lies between 44 and 55, so it must be 49, hence a + b + c + d = 7, and the individual totals must be 0, 1, 2, 4 in some order.

      This Python program runs in 86ms.

      Run: [ @repl.it ]

      from enigma import compare, subsets, printf
      
      # points for team X in the X vs Y match, if the score if x - y
      pts = lambda x, y: compare(x, y, vs=(0, 1, 2))
      
      # consider possible scores in round 1
      for (a1, b1, c1, d1) in subsets((0, 1, 2, 4), size=len, select="P"):
        # calculate the scores in the other rounds
        (a2, b2, c2, d2) = (2 * x for x in (c1, d1, a1, b1))
        (a3, b3, c3, d3) = (2 * x for x in (d2, c2, b2, a2))
      
        # calculate the points
        A = pts(a1, b1) + pts(a2, c2) + pts(a3, d3)
        B = pts(b1, a1) + pts(b2, d2) + pts(b3, c3)
        C = pts(c1, d1) + pts(c2, a2) + pts(c3, b3)
        D = pts(d1, c1) + pts(d2, b2) + pts(d3, a3)
        # C and D tied on points for 1st/2nd
        # A and B tied on points for 3rd/4th
        if not(C == D and A == B and C > A): continue
      
        # calculate goals for/against
        (fA, aA) = (a1 + a2 + a3, b1 + c2 + d3)
        (fB, aB) = (b1 + b2 + b3, a1 + d2 + c3)
        (fC, aC) = (c1 + c2 + c3, d1 + a2 + b3)
        (fD, aD) = (d1 + d2 + d3, c1 + b2 + c3)
        # D had a better average than C
        # B had a better average than A
        if not(fD * aC > fC * aD and fB * aA > fA * aB): continue
      
        # output solution
        printf("(a) {fA}/{aA}; (b) {b3}-{c3}; [a={a1} b={b1} c={c1} d={d1}; A={A} B={B} C={C} D={D}]")
      

      Solution: (a) Arne’s scrump average was: 12/17 (≈ 0.71); (b) The score in the Bach-Chopin match was 16 – 0.

      The scores in the three rounds were:

      (1) A vs B = 4 – 1; C vs D = 2 – 0
      (2) A vs C = 4 – 8; B vs D = 0 – 2
      (3) A vs D = 4 – 8; B cs C = 16 – 0

      Like

  • Jim Randell 9:50 am on 3 September 2019 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 497 

    From The Sunday Times, 6th December 1970 [link]

    All 26 members of the Alphabets Cricket Club (all surnames starting with a different letter of the alphabet) turned up for the annual general meeting at the Ugly Duck. Afterwards they divided into 5 teams of 5 and played a quick game of skittles, the lowest-scoring team to buy beer for the rest. (P. L. Zeigler sportingly stood down).

    Each player had just one ball at the 9 skittles, scoring, of course, a point for each skittle knocked down. The team captains varied in performance: Thomson got 9, Knight 5, Oldwhistle 3, C. F. Arrowroot 2, and the luckless Fisher 0. (Fisher’s team moved to have all captains’ scores ignored, but the plea was rejected; it was pointed out that anyhow Fisher’s weren’t due to buy the beer).

    The team totals were all different, as indeed they would have been had captains’ scores been ignored. No other player made the same score as any Captain, and no two team members of the same team made the same score. Oldwhistle’s team won.

    What was the order of finishing, and what were the individual scores of the team buying the beer?

    [teaser497]

     
    • Jim Randell 9:50 am on 3 September 2019 Permalink | Reply

      This Python program considers the set of scores for the winning team, and then looks for possible scores for the remaining teams that gives the teams different scores (both including and excluding the captains scores).

      Run: [ @repl.it ]

      from enigma import subsets, irange, printf
      
      # we can identify the teams by the captains scores (winning captain first)
      caps = [ 3, 9, 5, 2, 0 ]
      
      # team names (by captains score)
      team = dict(zip(caps, "OTKAF"))
      
      # possible remaining scores
      scores = set(irange(0, 9)).difference(caps)
      
      # solve for captains score in caps
      # t1 = winning teams total
      # t0 = winning teams total without captains score
      # ss = scores so far
      # t1s = team totals (with captains score)
      # t0s = team totals (without captains score)
      def solve(caps, t1, t0, ss=[], t1s=[], t0s=[]):
        # are we done?
        if not caps:
          yield ss
        else:
          # consider scores for the next team
          x = caps[0]
          for s in subsets(scores, size=4):
            s0 = sum(s)
            s1 = s0 + x
            # check scores are less than the winner
            if not(s1 < t1 and s0 < t0): continue
            # and also not repeated
            if s1 in t1s or s0 in t0s: continue
            # solve for the remaining captains
            yield from solve(caps[1:], t1, t0, ss + [s], t1s + [s1], t0s + [s0])
            
      
      # find scores for the winning team
      for s in subsets(scores, size=4):
        t = sum(s)
        # solve for the remaining teams
        for ss in solve(caps[1:], caps[0] + t, t):
      
          # record (0=team, 1=captain score, 2=other scores, 3=total score (with cap), 4=total score (without cap))
          rs = [ ('O', caps[0], s, caps[0] + t, t) ]
          for (c, x) in zip(caps[1:], ss):
            rs.append((team[c], c, x, c + sum(x), sum(x)))
          # sort according to total score (with cap)
          rs.sort(key=lambda t: t[3], reverse=1)
      
          # team F did not come last
          if rs[-1][0] == 'F': continue
      
          # output the table
          for (x0, x1, x2, x3, x4) in rs:
            printf("{x0}: {x1} + {x2} = {x3} ({x4})")
          printf()
      

      Solution: The finishing order was: 1st = Oldwhistle, 2nd = Thomson, 3rd = Knight, 4th = Fisher, 5th = Arrowroot. The individual scores of the team buying the beer were: 1, 2 (Arrowroot), 4, 6, 8.

      The complete set of results is:

      O: 3 + (4, 6, 7, 8) = 28 (25) = 1st (1st)
      T: 9 + (1, 4, 6, 7) = 27 (18) = 2nd (5th)
      K: 5 + (1, 4, 7, 8) = 25 (20) = 3rd (3rd)
      F: 0 + (1, 6, 7, 8) = 22 (22) = 4th (2nd)
      A: 2 + (1, 4, 6, 8) = 21 (19) = 5th (4th)

      There is also a “near miss” solution:

      O: 3 + (4, 6, 7, 8) = 28 (25) = 1st (1st)
      T: 9 + (1, 4, 6, 7) = 27 (18) = 2nd (5th)
      K: 5 + (1, 4, 7, 8) = 25 (20) = 3rd (3rd)
      A: 2 + (1, 6, 7, 8) = 24 (22) = 4th (2nd)
      F: 0 + (1, 4, 6, 8) = 19 (19) = 5th (4th)

      But this is disallowed as team F is last (and wouldn’t be last if the captains scores are ignored).

      Like

  • Jim Randell 8:38 am on 21 July 2019 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 488 

    From The Sunday Times, 4th October 1970 [link]

    The villagers of Titterton always tell the truth; those from Lilling always lie; those from Trulham alternate between truth and lie, starting with a truth; those from Litford also alternate, but start with a lie.

    I recently met four men, one from each village:

    Alf: “I’m from Trulham.”
    Bert: “Alf’s from Lilling.”
    Charlie: “Bert’s from Lilling.”
    Dan: “Charlie’s from Lilling.”
    Alf: “Dan’s from Titterton.”

    As I was sorting this out, in came three more men, each from one of other of the villages:

    Edgar: “I’m from Dan’s village.”
    Frank: “There are more chaps here from my village than from any other.”
    George: “Frank’s lying. I’m from Bert’s village.”

    Name the men from Titterton, Trulham and Litford. (I should perhaps add that I live in Totworth and tell the truth invariably).

    [teaser488]

     
    • Jim Randell 8:40 am on 21 July 2019 Permalink | Reply

      I assumed the puzzle text itself was a completely true account, and that the “alternators” were at the beginning of their cycle with their first quoted statements.

      This Python program runs in 85ms.

      Run: [ @repl.it ]

      from enigma import subsets, printf
      
      # bT = always true (= Titterton)
      def bT(ss):
        return all(ss)
      
      # bF = always false (= Lilling)
      def bF(ss):
        return not any(ss)
      
      # bTF = alternate, T then F (= Trulham)
      def bTF(ss):
        return all(ss[0::2]) and not any(ss[1::2])
      
      # bFT = alternate, F then T (= Litford)
      def bFT(ss):
        return all(ss[1::2]) and not any(ss[0::2])
      
      # possible behaviours
      fns = (bT, bF, bTF, bFT)
      
      # solve the puzzle
      def solve():
        # choose behaviours for A, B, C, D
        for (A, B, C, D) in subsets(fns, size=len, select='P'):
      
          # A: "I am from Trulham", "D is from Titterton"
          if not A([A is bTF, D is bT]): continue
      
          # B: "A is from Lilling"
          if not B([A is bF]): continue
      
          # C: "B is from Lilling"
          if not C([B is bF]): continue
      
          # D: "C is from Lilling"
          if not D([C is bF]): continue
      
          # choose behaviours for E, F, G
          for (E, F, G) in subsets(fns, size=3, select='M'):
      
            # E: "I am from D's village"
            if not E([E == D]): continue
      
            # F: "There are more from my village than any other"
            s = (A, B, C, D, E, F, G)
            n = dict((k, s.count(k)) for k in s)
            f = all(n[F] > n[x] for x in s if x != F)
            if not F([f]): continue
      
            # G: "F is lying" "I am from B's village"
            if not G([not(f), G == B]): continue
      
            yield s
      
      for s in solve():
        (A, B, C, D, E, F, G) = (b.__name__ for b in s)
        printf("A={A} B={B} C={C} D={D} E={E} F={F} G={G}")
      

      Solution: The affiliations are:

      Titterton: Charlie
      Lilling: Bert, Edgar
      Trulham: Alf, George
      Litford: Dan, Frank

      Like

  • Jim Randell 1:07 pm on 6 June 2019 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 482 

    From The Sunday Times, 23rd August 1970 [link]

    The Alphabet CC ended last season with a fine win against the Early Closers XI. Declaring at 155 for six they dismissed their opponents with only minutes to spare. The Early Closers’ captain, Bond, made a spirited 71 not out.

    “Nice gesture of the opposition to bat in alphabetical order”, said C. F. Arrowroot, the Alphabet president; “And did you notice that each one of the first three wickets fell when the total was the product of Bond’s score and that of the outgoing batsman?”

    “Even odder”, commented B. P. Biggin, “except when Collins was out the total at the fall of every one of their wickets was the square of the dismissed batsman’s score.”

    No one made a “duck” and there was just one “extra”. I should perhaps add that no two Early Closers had the same surname initial.

    How many did Collins score, and what was Bond’s score at the fall of the seventh wicket?

    [teaser482]

     
    • Jim Randell 1:09 pm on 6 June 2019 Permalink | Reply

      Although not explicitly mentioned in the puzzle text, presumably we are talking about a cricket match.

      B is one of the first pair to go into bat, and at the 10th dismissal he is “not out”, so he is paired with every other batsman. If there is an A, he would be in the first pair (and the first to be dismissed), and C would be next (and be the second to be dismissed). If there isn’t an A, C would be in the first pair and be the first to be dismissed. So C is either the first or second batsman to be dismissed.

      I tackled this problem in 2 parts. The first part deals with the first three dismissals, where the score is always the product of B’s score and the dismissed batsman’s score (and two of the scores have to be the square of the dismissed batsman’s score – the one that isn’t identifies C).

      The second part deals with the remaining dismissals (4th – 10th) the scores have to be the square of the dismissed batsman’s score.

      When the 10th batsman is dismissed the total score must be a square less than 155, so not more than 12².

      This program runs in 80ms.

      Run: [ @repl.it ]

      from enigma import irange, div, printf
      
      # solve the first three dismissals, at each the total score is the
      # product of B's score and the dismissed batsman's score and all,
      # except C, the total is the square of the dismissed batsman's score.
      # return ([(<B's score>, <dismissed score>, <extras>), ...], <C>)
      # t = total so far
      # b = B's current score
      # ds = scores at dismissals
      # x = current extras
      # c = identify C
      def solve1(t=0, b=0, ds=[], x=0, c=None):
        n = len(ds)
        # are we done?
        if n == 3:
          # check C has been allocated
          if c in (0, 1):
            yield (ds, c)
        else:
          # possible extra
          for x2 in irange(x, 1):
            # possible current score for B
            for b2 in irange(b, 71):
              # calculate the score for the dismissed batsman (d)
              #   t2 = t + (b2 - b) + d + (x2 - x) = b2 * d
              # so:
              #   d = (t + b2 + x2 - b - x) / (b2 - 1)
              d = div(t + b2 + x2 - b - x, b2 - 1)
              if d is None or d < 1: continue
              t2 = b2 * d
              # is the total the square of d?
              c2 = c
              if t2 != d * d:
                # this must be C
                if c is not None: continue
                c2 = n
              yield from solve1(t2, b2, ds + [(b2, d, x2)], x2, c2)
      
      # solve the remaining dismissals, at each the total score is the
      # square of the dismissed batman's score
      # t = total score so far
      # b = B's current score
      # ds = scores of B and dismissed batsman
      # x = extra (0 or 1)
      def solve2(t=0, b=0, ds=[], x=0):
        n = len(ds)
        # are we done?
        if n == 10:
          # check an extra has been scored and B's score is 71
          if x == 1 and ds[-1][0] == 71:
            yield ds
        else:
          # possible extra
          for x2 in irange(x, 1):
            # possible scores for next batsman
            for d in irange(1, 12):
              # the new total is...
              t2 = d * d
              if not(t < t2 < 155): continue
              # calculate the current score for B
              b2 = t2 + b + x - t - d - x2
              if b2 < b: continue
              yield from solve2(t2, b2, ds + [(b2, d, x2)], x2)
                
      # solve for the first 3 dismissals
      for (ds1, c) in solve1():
        (b, d, x) = ds1[-1]
        # solve for the remaining dismissals
        for ds in solve2(b * d, b, ds1, x):
          # output the solution
          printf("[ds={ds}]")
          printf("-> C={C} [i={c}]; B[i=6]={B}", C=ds[c][1], B=ds[6][0])
      

      Solution: Collins scored 5. Bond had scored 41 at the 7th dismissal.

      The scores for the batsman (in dismissal order) were:

      2, 5 (Collins), 4, 5, 6, 8, 9, 10, 11, 12, 71 (not out, Bond)

      And the total scores at each dismissal were:

      4, 10 (Collins), 16, 25, 36, 64, 81, 100, 121, 144

      So there is an A (who was dismissed first). The extra was scored between A’s dismissal and C’s dismissal.

      Bond’s scores at each dismissal were:

      2, 2, 4, 8, 13, 33, 41, 50, 60, 71

      Like

  • Jim Randell 8:02 am on 16 April 2019 Permalink | Reply
    Tags: by: R Postill,   

    Brain-Teaser 473: Gouttes d’Or 

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

    The conical glasses at the Hôtel d’Or hold one eighth of a litre and are embellished from base to brim with a picture of Bacchus. In making the hotel’s famous Gouttes d’Or, sold at NF 5.20, it was customary to fill the glass up to Bacchus’ neck (4/5th of the way up the glass) and then add an olive; the profit on raw materials was a modest 100 per cent.

    Gaston, the barman, has decided that he must make the more realistic profit of 400 per cent. So now the olive is put in first and then liquor is added up to the level of Bacchus’ chest (3/5ths of the way up the glass). The price has been reduced to NF 4.80.

    Gaston explained: “Ze olives are standard sized and cost me one centime a cc. Yes, I could put in ze olive after measuring out the liquid — but zen it would be necessary to charge …”

    How much?

    This is a corrected version of the originally published puzzle. In the original version the price of the first glass was incorrectly given as NF 5.30 (instead of NF 5.20). The puzzle can still be solved in the same way, but the numbers don’t work out as nicely.

    This puzzle was included in the book Sunday Times Brain Teasers (1974, edited by Ronald Postill).

    [teaser473]

     
    • Jim Randell 8:03 am on 16 April 2019 Permalink | Reply

      If the conical glass has a height of h and a radius (at the top) of r, then a completely full glass will have a volume of:

      πr²h / 3 = 125

      So, if was filled to a height of (k/5)h (and hence a radius of (k/5)r, then the volume of liquid is:

      π(kr/5)²(kh/5) / 3 = (k/5)³ 125 = k³

      So a 4/5 full glass contains 64 cc, and a 3/5 full glass 27 cc.

      If olives have a volume of x cc (and cost 1 centime per cc) and the liquor has a cost of y centimes per cc, and the profit is 100% (i.e. the cost of the drink is twice the cost of the raw materials), then for the first glass we have:

      520 = 2(x + 64y)
      260 = x + 64y

      And for the second glass we have a 400% profit (i.e. the cost of the drink is 5 times the cost of the raw materials), on a drink with the olive added before the liquor we have:

      480 = 5(x + (27 − x)y)
      96 = x + 27y − xy

      (assuming the olive is submerged, and so displaces its own volume of liquid).

      These two equations have a reasonable solution at x = 4, y = 4. (And an unreasonable solution where x = 219, which is a very large olive, and wouldn’t fit in the glass).

      So the cost of a 3/5 glass, if the olive was added last, and the profit multiple is 5, would be:

      5(x + 27y) = 560

      Solution: The drink would cost NF 5.60.

      Here is a Python program that solves the puzzle numerically. It runs in 81ms.

      Run: [ @repl.it ]

      from enigma import Polynomial, fdiv, find_zero, printf
      
      # for a 4/5 full glass, with olive added last, the cost is k1, profit multiple is m1
      # for a 3/5 full glass, with olive added first, the cost is k2, profile multiple is m2
      # return (<olive volume>, <liquor cost>, <answer>)
      def solve(k1, m1, k2, m2):
      
        # volumes for a 4/5 and 3/5 glass
        (v4, v3) = (4 ** 3, 3 ** 3)
      
        # create a polynomial that gives the cost of liquor in terms of the volume of an olive
        # using the first equation:
        # k1 = m1 * (x + v4 * y)
        q = Polynomial([fdiv(k1, m1 * v4), fdiv(-1, v4)])
      
        # create the polynomial whose roots are the volume of an olive
        # using the second equation:
        # k2 = m2 * (x + (v3 - x) * y)
        p = q * Polynomial([v3, -1]) - Polynomial([fdiv(k2, m2), -1])
      
        # find a solution with a reasonably sized olive
        r =  find_zero(p, 0, 10)
        x = r.v
        y = q(x)
        # cost of a 3/5 glass, with an olive last, profit multiple m2
        k = m2 * (x + v3 * y)
        return (k, x, y)
      
      # solve for the required amounts
      (k, x, y) = solve(520, 2, 480, 5)
      
      printf("cost = NF {f:.02f} [x = {x:.02f}, y = {y:.02f}]", f=0.01 * k)
      

      If we change the first argument to [[ solve() ]] on line 30 to 530 we get the solution to the puzzle as originally set: approximately NF 5.72.

      Like

  • Jim Randell 8:04 am on 16 March 2019 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 464: Home meadow 

    From The Sunday Times, 19th April 1970 [link]

    The triangular home meadow at Cowpleasure Farm is bounded West and South by fences running due north and due east from the farmhouse; its other fence forms one side of a square field known as Starvecrow. The south fence of Home Meadow is the shared north boundary of two contiguous square fields, Paddock and Rookery, whose total area is just half that of Starvecrow.

    Farmer Nettle has just refenced the whole outer perimeter (i.e. excluding fences common to two fields). He used 146 equal sections of fencing, none of which needed bending or cutting.

    He plans to replace the other fences next year using the same type of section.

    How many will he need? (Don’t worry about gates; they are incorporated in some of the standard sections).

    This puzzle was included in the book Sunday Times Brain Teasers (1974, edited by Ronald Postill).

    [teaser464]

     
    • Jim Randell 8:06 am on 16 March 2019 Permalink | Reply

      There’s nothing to distinguish the fields P and R, so we’ll call the smaller one P.

      For the square fields P, R, S, we’ll indicate the size of one side also by P, R, S, and the remaining boundary of H can be Q.

      The amount of perimeter fencing (shown in red) required is X:

      X = 3S + 2P + 2R + (R − P) + Q = 3(S + R) + P + Q

      And the amount of internal fencing (the remaining black lines) required is Y:

      Y = 2P + R + S

      This Python program uses the [[ pythagorean_triples() ]] routine (see Teaser 2910) from the enigma.py library to find possible dimensions of field H, and then determines the dimensions of the other fields.

      It runs in 77ms.

      Run: [ @repl.it ]

      from enigma import pythagorean_triples, irange, printf
      
      for (x, y, z) in pythagorean_triples(48):
        # S is the hypotenuse
        S = z
        # one of the other two sides is P + R
        for (PR, Q) in [(x, y), (y, x)]:
          # suppose P < R
          for P in irange(1, PR // 2):
            R = PR - P
            # P^2 + R^2 = S^2 / 2
            if not(2 * (P * P + R * R) == S * S): continue
            # total sum of perimeter fences is 146
            X = 3 * (S + R) + P + Q
            if not(X == 146): continue
            # sum of the internal fences
            Y = S + PR + P
            # output solution
            printf("Y={Y}, S={S} P={P} R={R} Q={Q}")
      

      Solution: He will need 57 sections.

      Like

  • Jim Randell 10:07 am on 26 February 2019 Permalink | Reply
    Tags: by: R Postill,   

    Brain-Teaser 455 

    From The Sunday Times, 1st February 1970 [link]

    Every week we field a village darts team of 3 men, one from each of our pubs (Plough, Queen’s Head, and Roebuck). Altogether we can call on 14 players (whose names, luckily, start respectively with the first 14 letters of the alphabet): five of them frequent the Plough, six the Queen’s Head and seven the Roebuck, the apparent discrepancy is explained by the thirsts of Ernie, Fred, Len and Mark, all of whom are “two-pub men” and are thus eligible to represent either of their haunts.

    For over three years we have picked a different team each week and have just exhausted all 162 possible combinations. The last two teams were:

    Joe, Nigel, Charlie
    Charlie, Fred, Harry

    For the next seven weeks we are waiving the one-man-per-pub rule and have picked teams which have not so far played together. The are:

    Ernie, Len, Mark
    Ian, Fred, Alan
    Joe, Fred, George
    Len, Mark, Keith
    Fred, Keith, Nigel
    Ernie, Len, Nigel
    Ian, Joe, and one other to be picked from a hat on the night.

    Which darts players frequent the Roebuck?

    [teaser455]

     
    • Jim Randell 10:07 am on 26 February 2019 Permalink | Reply

      I found two solutions for this puzzle, although with a slight change in the wording we could arrive at a unique solution.

      This Python program runs in 226ms.

      Run: [ @repl.it ]

      from itertools import product, permutations, combinations
      from enigma import icount, unpack, printf
      
      # all the players
      players = "ABCDEFGHIJKLMN"
      
      # players with dual allegiance
      duals = "EFLM"
      
      # the pubs
      pubs = set("PQR")
      
      # do the three sets form a team
      def team(a, b, c):
        for (x, y, z) in permutations(pubs):
          if x in a and y in b and z in c: return True
        return False
      
      # choose the "missing" pub for the duals
      for s1 in product(pubs, repeat=4):
        (E, F, L, M) = (pubs.difference([x]) for x in s1)
      
        # ELM do not form a team
        if team(E, L, M): continue
      
        # choose (single) pubs for K, N
        for s2 in product(pubs, repeat=2):
          (K, N) = (set([x]) for x in s2)
      
          # KLM, FKN, ELN do not form teams
          if team(K, L, M) or team(F, K, N) or team(E, L, N): continue
      
          # CJN do form a team
          for s3 in permutations(pubs.difference(N)):
            (C, J) = (set([x]) for x in s3)
      
            # remaining assignments for given combinations, AGHI
            for s4 in product(pubs, repeat=4):
              (A, G, H, I) = (set([x]) for x in s4)
      
              # check the remaining combinations
              if not(team(C, F, H)) or team(A, F, I) or team(F, G, J): continue
      
              # which leaves B and D
              for s5 in product(pubs, repeat=2):
                (B, D) = (set([x]) for x in s5)
      
                table = (A, B, C, D, E, F, G, H, I, J, K, L, M, N)
      
                # P, Q, R should have teams of 5, 6, 7
                (P, Q, R) = (icount(table, (lambda x: p in x)) for p in 'PQR')
                if (P, Q, R) != (5, 6, 7): continue
      
                # count the total possible teams
                t = icount(combinations(table, 3), unpack(team))
                # there should be 162
                if t != 162: continue
      
                # find how many unused IJ? combinations there are
                ijs = list(p for (p, x) in zip(players, table) if p not in 'IJ' and not team(I, J, x))
                # there should be at least 2
                if len(ijs) < 2: continue
      
                # who plays for R
                Rs = list(p for (p, x) in zip(players, table) if 'R' in x)
      
                printf("Rs = {Rs}")
                printf("  A={A} B={B} C={C} D={D} E={E} F={F} G={G} H={H} I={I} J={J} K={K} L={L} M={M} N={N}")
                printf("  ijs={ijs}")
                printf()
      

      Solution: A, B, D, F, G, I, J are on the Roebuck team.

      This solution assumes that the final team member of the I+J+? team that is picked out of the hat can be any of the other players (i.e. I and J have never played together on a team before).

      The team allegiances in this case are:

      P = C, E, F, L, M [5]
      Q = E, H, K, L, M, N [6]
      R = A, B, D, F, G, I, J [7]

      However, I took the construction of final I+J+? team by picking a name out of a hat to require only that there should be more than one candidate to placed in the hat. If we have the following team allegiances:

      P = E, F, J, L, M [5]
      Q = E, H, K, L, M, N [6]
      R = A, B, C, D, F, G, I [7]

      Then this is also a solution to the puzzle. In this case the remaining possible partners for I+J+? are: A, B, C, D, F, G.

      Like

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel
%d bloggers like this: