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

  • Unknown's avatar

    Jim Randell 8:40 am on 30 July 2024 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 542: Finger trouble 

    From The Sunday Times, 14th November 1971 [link]

    The Wabu tribesmen, though invariably truthful, are inveterate thieves, and this despite a tribal law which requires any man convicted of theft to have one finger amputated. Few of them now have a full complement and, since they count on their fingers, their answers to numerical questions are confusing to the outsider. (For instance, an 8-fingered man gives his answers to base 8, so that, for example, our figure 100 is for him 144).

    Despite these handicaps, they are keen cricketers, and I recently watched their First Eleven. The Chief told me: “Only little Dojo has 10 fingers; the worst-equipped are Abo, Bunto and Coco with 4 apiece”. (I need hardly add that the Wabu would never elect a Chief who had been convicted of theft).

    Later I asked some members of the team how many fingers (thumbs are, of course, included) the Eleven totalled. Epo said “242”, and Foto agreed; Gobo said “132”, and Hingo agreed. Kinko added: “I have more fingers than Iffo”.

    How many fingers each have Epo, Hingo, Iffo and Jabo (the Wicket-keeper)?

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

    [teaser542]

     
    • Jim Randell's avatar

      Jim Randell 8:41 am on 30 July 2024 Permalink | Reply

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

      The following run file executes in 83ms. (Internal runtime of the generate code is 1.9ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --base=11  # values are between 4 and 10
      --distinct=""
      
      # we are given some values
      --assign="A,4"
      --assign="B,4"
      --assign="C,4"
      --assign="D,10"
      # and the others are between 5 and 9
      --digits="5-9"
      # additional constraints
      "E = F"
      "G = H"
      "K > I"
      
      # total number of fingers
      --macro="@total = (A + B + C + D + E + F + G + H + I + J + K)"
      
      # E (and F) says "242"
      "int2base(@total, base=E) == '242'"
      
      # G (and H) says "132"
      "int2base(@total, base=G) == '132'"
      
      --answer="(E, H, I, J)"
      --template=""
      

      Solution: Epo has 5 fingers; Hingo has 7 fingers; Iffo has 8 fingers; Jabo has 9 fingers.

      The number of fingers for each member of the team are:

      A = 4
      B = 4
      C = 4
      D = 10
      E = 5
      F = 5
      G = 7
      H = 7
      I = 8
      J = 9
      K = 9
      total = 72

      And the total given in the appropriate bases:

      A, B, C → 1020 [base 4]
      D → 72 [base 10]
      E, F → 242 [base 5]
      G, H → 132 [base 7]
      I → 110 [base 8]
      J, K → 80 [base 9]

      Like

      • Ruud's avatar

        Ruud 4:14 pm on 30 July 2024 Permalink | Reply

        It is really simple to solve this one by hand.
        But, here’s my as brute as possible code:

        import itertools
        
        a = b = c = 4
        d = 10
        for e in range(5, 11):
            sum_e = int("242", e)
        
            for g in range(4, 11):
                sum_g = int("132", g)
                if sum_e == sum_g:
                    f = e
                    h = g
                    ijk = sum_e - (a + b + c + d + e + f + g + h)
                    for i, j, k in itertools.product(range(1, 10), repeat=3):
                        if i + j + k == ijk and k > i:
                            print(f"Epo={e} Hingo={h} Iffo={i} Jabo={j}")
        

        Like

    • Ruud's avatar

      Ruud 7:30 pm on 30 July 2024 Permalink | Reply

      Even more brute force:

      import itertools
      
      a = b = c = 4
      d = 10
      for e, f, g, h, i, j, k in itertools.product(range(5, 10), repeat=7):
          if e == f and g == h and (total := int("242", e)) == int("132", g) and k > i and (a + b + c + d + e + f + g + h + i + j + k) == total:
              print(f"Epo={e} Hingo={h} Iffo={i} Jabo={j}")
      

      Like

    • Frits's avatar

      Frits 1:44 pm on 31 July 2024 Permalink | Reply

      from enigma import SubstitutedExpression
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          # G*G + 3*G - 2*(E*E + 2*E) = 0
          "is_square(1 + G * (G + 3) // 2) - 1 = E",
          
          "K > I",
          "(G * (G + 3) + 2) -  (A + B + C + D + E + E + G + G + I + J) = K",
        ],
        answer="(E, G, I, J)",
        distinct="",
        s2d=dict(A=4, B=4, C=4, D=10),
        digits=range(5, 10),
        verbose=0,    # use 256 to see the generated code
      )
      
      names= "Epo Hingo Iffo Jabo".split()
      
      # print answers
      for ans in p.answers():
        print(f"{', '.join(f'{x}: {str(y)}'for x, y in zip(names, ans))}")     
      

      Like

  • Unknown's avatar

    Jim Randell 8:59 am on 21 May 2024 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 350: Catering crisis 

    From The Sunday Times, 21st January 1968 [link]

    The caterer at our Village Hall is in a quandary, as the Hall has been double-booked for next Saturday — by the Cricket Club and the Darts Club.

    Unfortunately the matter cannot be resolved until the return of the Vicar on Saturday morning. He is quite unpredictable in such decisions, and the caterer must order the food by Friday.

    The Cricketers want 100 sausage rolls and 60 meat pies; the Darts Club wants 50 sausage rolls and 90 meat pies.

    The caterer is empowered to spend exactly £6.00. He buys sausage rolls at 3p and sells at 4p; meat pies cost him 5p and sell at 8p. Any left-overs are disposed of at a loss to a local prep school, which pays 2p per sausage roll and 3p a pie.

    What should the caterer order so that he makes the best safe profit no matter which club gets the booking? And what will that profit be?

    This puzzle is included in the book Sunday Times Brain Teasers (1974). The puzzle text is taken from the book.

    [teaser350]

     
    • Jim Randell's avatar

      Jim Randell 8:59 am on 21 May 2024 Permalink | Reply

      This Python program looks at possible orders of sausage rolls and pies that cost the caterer exactly 600p, and then the amount made if these supplies are available for each club, and records the minimum profit made in each case. We then look for the largest of these profits to find the answer to the puzzle.

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

      Run: [ @replit ]

      from enigma import (Accumulator, express, printf)
      
      # amount earned for supply <s>, demand <d>, price <p>, disposal price <x>
      def amount(s, d, p, x):
        return (d * p + (s - d) * x if s > d else s * p)
      
      # calculate amount earned for a supply (s, p) and a demand (S, P)
      # of sausage rolls and pies
      def earned(s, p, S, P):
        return amount(s, S, 4, 2) + amount(p, P, 8, 3)
      
      # find the maximum profit
      r = Accumulator(fn=max, collect=1)
      
      # the caterer spends 600p
      for (s, p) in express(600, [3, 5]):
        # calculate amount earned from each club
        C = earned(s, p, 100, 60)
        D = earned(s, p, 50, 90)
        # record profit
        r.accumulate_data(min(C, D), (s, p, C - 600, D - 600))
      
      # output solution
      for (s, p, C, D) in r.data:
        printf("{s} sausages rolls + {p} pies -> cricket = {C} / darts = {D}")
      

      Solution: The caterer should order 80 sausage rolls and 72 pies. Whichever club arrives he will make a profit of 236p.


      In the originally published puzzle pre-decimal currency was used and the numbers were different:

      The Cricketers want 200 sausage rolls and 120 meat pies; the Darts Club wants 100 sausage rolls and 180 meat pies.

      The caterer is empowered to spend exactly £5 [= 1200 pence].

      And the answer is: 160 sausage rolls, 144 pies. The profit is 472 pence = £1, 19s, 4d.

      Like

      • Frits's avatar

        Frits 10:35 am on 21 May 2024 Permalink | Reply

        This program doesn’t assume that the caterer will spend exactly 600p.

           
        from enigma import SubstitutedExpression, Accumulator
        
        # invalid digit / symbol assignments
        d2i = dict()
        # try to spend close to 600p in order to maximize the profit
        for dgt in range(0, 101):
          vs = set()
          if dgt < 50: vs.update('S')
          if dgt < 60: vs.update('P')
          if dgt > 90: vs.update('P')
          d2i[dgt] = vs
        
        # the alphametic puzzle
        p = SubstitutedExpression(
          [
            # expenditure close to the maximum
            "595 < S * 3 + P * 5 <= 600",
            # calculate profits for cricketers and darters
            "(pc := S + min(P, 60) * 3 - (P - 60) * 2 * (P > 60))",
            "(pd := min(S, 50) + P * 3 - (S - 50) * 1 * (S > 50))",
          ],
          answer="min(pc, pd), S, P",
          base=101,
          d2i=d2i,
          distinct="",
          reorder=0,
          verbose=0,    # use 256 to see the generated code
        )
        
        # find the maximum profit
        r = Accumulator(fn=max).accumulate_from(ans for (_, ans) in p.solve())
        
        # output solution
        w, s, p = r.value
        print(f"he should order {s} sausages and {p} pies for a profit of {w}p")
        

        Like

        • Frits's avatar

          Frits 10:32 am on 22 May 2024 Permalink | Reply

          Assuming that the caterer will spend exactly 600p and that the caterer will order a sensible amount of sausages S (50, 55, 60, …, 95, 100) the profit (if the cricketers get the booking) can be expressed as: 2.2 * S + 60 and 460 – 2.8 * S if the darters get the booking.

          As the first function is increasing and the latter is decreasing for S the safe profit will be maximized if 2.2 * S + 60 = 460 – 2.8 * S or S = 80.

          Liked by 1 person

    • Lise Andreasen's avatar

      Lise Andreasen 2:17 am on 22 May 2024 Permalink | Reply

      Interesting.

      I approached it differently and arrived at 100 sausage rolls and a profit of 280.

      https://onlinephp.io/c/47cce

      Like

      • Lise Andreasen's avatar

        Lise Andreasen 2:23 am on 22 May 2024 Permalink | Reply

        Oh, hang on. Read the puzzle incorrectly.

        Like

      • Jim Randell's avatar

        Jim Randell 12:44 pm on 24 May 2024 Permalink | Reply

        Sorry. For a short while the numbers were mixed up between the original puzzle and the version published in the book. The puzzle text now reflects the version from the book, rather than the originally published version.

        Like

  • Unknown's avatar

    Jim Randell 8:40 am on 4 June 2023 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 568: Batting averages 

    From The Sunday Times, 21st May 1972 [link]

    Allen and Biggin, stalwarts of our village cricket side, have an annual wager: whichever finishes the season with the lower batting average buys the other a dinner.

    Last season the result was in doubt until the last ball of the last match to which Allen, offering no stroke, was adjudged “out”, leg-before-wicket. Had he been given “not out” (which, incidentally, would have been the first “not out” innings either had played) his batting average — a whole number — would have beaten Biggin’s by exactly 0.5 of a run. In the event Biggin won by exactly 0.5 of a run.

    Both players comfortably passed a season’s total of 300 runs but neither reached 500. Biggin had three fewer innings than Allen.

    How many runs did Biggin score during the season?

    [Note for the non-cricketer: batting average is calculated by dividing a player’s total runs by his number of completed innings; i.e., times he was “out”].

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

    [teaser568]

     
    • Jim Randell's avatar

      Jim Randell 8:41 am on 4 June 2023 Permalink | Reply

      If A’s average is A/a and B’s average is B/b, then we have:

      B/b − 1/2 = A/a
      B/b + 1/2 = A/(a − 1) = an integer
      a = b + 3
      300 < A, B < 500

      This Python program runs in 57ms. (Internal runtime is 1.5ms).

      Run: [ @replit ]

      from enigma import (irange, divisors_pairs, div, printf)
      
      # consider values for A's runs
      for A in irange(301, 499):
        # A / (a - 1) is an integer
        for (a1, k) in divisors_pairs(A, every=1):
          a = a1 + 1
          b = a - 3
          if b < 1: continue
      
          # calculate B's runs (B/b - 1/2 = A/a)
          B = div((2 * A + a) * b, 2 * a)
          if B is None or not (300 < B < 500): continue
          # check (B/b + 1/2 = A/(a - 1))
          if not (a1 * (2 * B + b) == 2 * A * b): continue
      
          # output solution
          printf("A={A} B={B} a={a} b={b}")
      

      Solution: Biggin scores 369 runs.

      A is 420 for 21, an average of 20.

      B is 369 for 18, an average of 20.5.

      Had A been out only 20 times, his average would have been 21.

      Like

    • Frits's avatar

      Frits 7:47 pm on 4 June 2023 Permalink | Reply

      Two iterations.

       
      from math import ceil
      
      # A = a(a-1)
      
      # A = a^2 - a > 300, (a - 1/2)^2 > 300.25, a - 1/2 > 17.328, a > 17.828
      # A = a^2 - a < 500, (a - 1/2)^2 < 500.25, a - 1/2 < 22.367, a < 22.867
      
      # make sure B is a whole number (a has to be odd)
      mn = (a := int(300.25**.5 + 0.5)) + (2 if a % 2 else 1)
      mx = ceil(500.25**.5 + 0.5)
      
      # loop over odd a's
      for a in range(mn, mx, 2):
        # B/b - 1/2 = A/a, B = (a - 1/2)b
        B = (a - 0.5) * (a - 3)
        if not (300 < B < 500): continue
        print(f"Biggin scored {int(B)} runs during the season")  
      

      Like

    • GeoffR's avatar

      GeoffR 8:36 pm on 4 June 2023 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 301..499:A; % Allen's total runs in the season
      var 301..499:B; % Biggin's total runs in the season
      
      % 26 weeks looks a reasonable average time for a cricket season (Apr - Sept)
      var 1..26: ai;  % Allen's total innings for the season
      var 1..26: bi;  % Biggin's total innings for the season
      
      % Allen's possible average scores are integers
      constraint A mod ai == 0  /\ A mod (ai - 1) == 0 ;
      
      % Biggin had three fewer innings than Allen
      constraint ai == bi + 3;
      
      % if Biggin wins by 1/2 point
      % B / bi - 1 / 2 = A / ai
      constraint 2 * (ai * B - bi * A) = ai * bi;
      
      % if Allen wins by 1/2 point
      % B / bi + 1 / 2 = A /(ai - 1)
      constraint 2 * (A * bi - B *(ai -  1)) == (ai - 1) * bi;
      
      solve satisfy;
      
      output ["[A, ai, B, bi] = " ++ show( [A, ai, B, bi] )];
      
      % [A, ai, B, bi] = [420, 21, 369, 18]
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:44 am on 26 February 2023 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 845: Hymn boards again 

    From The Sunday Times, 25th September 1977 [link] [link]

    I bade the Admiral good morning as I came out of church on Sunday morning, adding: “Did you notice anything odd about the hymns today?”.

    “Odd?”, he snorted; “they always sound odd to me, and they always will so long as Mrs. Haslet bellows her tuneless version at the back of my neck. A good enough woman in so many respects, but she doesn’t know a B-flat from a bull’s foot”.

    “No”, I said hastily, “I was referring to the mathematical rather than the musical aspect. There were, you will recall, three hymns all of three digits. Each digit appeared once only, and there were no zeros. The sums of the digits in all three hymns were equal and, what is more, the sum of the three hymn-numbers was a perfect square containing THREE digits. Very odd”.

    “Yes, I suppose so”, replied the Admiral, “… I do wish Mrs. Haslet would sit somewhere else”.

    What were the hymn numbers in ascending order (appropriately enough)?

    This puzzle is also included in the book The Sunday Times Book of Brain-Teasers: Book 2 (1981). Where it appears in the following form as Puzzle 50b:

    The Sunday before last the board also provided me with some mathematical interest. There were three hymns and again each digit from 1 to 9 inclusive was used. The sums of the digits in all three hymns were equal and, what is more, the sum of all three hymn numbers was a perfect square containing three digits.

    What were the hymn numbers on that Sunday?

    [teaser845]

     
    • Jim Randell's avatar

      Jim Randell 9:47 am on 26 February 2023 Permalink | Reply

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

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --digits="1-9"
      
      # digit sums are all the same
      "A + B + C == D + E + F"
      "D + E + F == G + H + I"
      
      # sum of the numbers is a 3-digit perfect square
      "9 < is_square(ABC + DEF + GHI) < 32"
      
      # remove duplicate solutions
      "A < D < G"
      
      --answer="(ABC, DEF, GHI)"
      

      Solution: The hymn numbers were: 195, 267, 438.

      Like

    • GeoffR's avatar

      GeoffR 12:01 pm on 26 February 2023 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C; % Hymn ABC digits
      var 1..9:D; var 1..9:E; var 1..9:F; % Hymn DEF digits
      var 1..9:G; var 1..9:H; var 1..9:I; % Hymn GHI digits
      
      constraint all_different([A, B, C, D, E, F, G, H, I])
      /\ A < D /\ D < G;
      
      set of int: sq3 = { pow(x, 2) | x in 10..31 };
      
      constraint A + B + C == D + E + F /\ A + B + C == G + H + I
      /\ (100*A + 10*B + C)  +  (100*D + 10*E + F) + (100*G + 10*H + I) in sq3;
      
      solve satisfy;
      
      output ["Hymn Numbers were \(A)\(B)\(C)" ++  ", \(D)\(E)\(F)"
      ++  " and \(G)\(H)\(I)." ];
      
      % Hymn Numbers were 195, 267 and 438.
      % ----------
      % ==========
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 9:09 am on 2 March 2023 Permalink | Reply

      
      from itertools import permutations
      from enigma import is_square
      
      for p1 in permutations(range(1, 10)):
          A, B, C, D, E, F, G, H, I = p1
          if A < D < G:  # order three hymn numbers
              if not(A + B + C == D + E + F):continue
              if not(A + B + C == G + H + I):continue
              total = 100 *(A + D + G) + 10 * (B + E + H) + (C + F + I)
              if is_square(total) and 100 < total < 1000:
                  ABC, DEF = 100*A + 10*B + C, 100*D + 10*E + F
                  GHI = 100*G + 10*H + I
                  print(f"Hymn Numbers were {ABC}, {DEF}, {GHI}")
      
      

      Like

    • Jim Randell's avatar

      Jim Randell 9:56 am on 2 March 2023 Permalink | Reply

      I thought it would be interesting to ask ChatGPT to write a Python program to solve this puzzle:

      Write a Python program to find a set of three 3-digit numbers, using each of the digits 1-9 exactly once between them. The sum of the digits in each of the three numbers is the same. The sum of the three numbers is a 3-digit perfect square. There is only one set of numbers that satisfies these conditions.

      And it produced this (without further prompting):

      import itertools
      
      # Generate all permutations of the digits 1-9
      perms = itertools.permutations(range(1, 10))
      
      # Loop through each permutation and check if it satisfies the conditions
      for perm in perms:
        # Split the permutation into three 3-digit numbers
        a, b, c = perm[:3], perm[3:6], perm[6:]
      
        # Check that each number contains no repeated digits
        if len(set(a)) == len(a) == len(set(b)) == len(b) == len(set(c)) == len(c):
          # Check that the sum of digits in each number is the same
          if sum(a) == sum(b) == sum(c):
            # Check that the sum of the three numbers is a 3-digit perfect square
            total = int("".join(map(str, a))) + int("".join(map(str, b))) + int("".join(map(str, c)))
            if len(str(total)) == 3 and int(total ** 0.5) ** 2 == total:
              # Print the solution and break out of the loop
              print(a, b, c)
              break
      

      It is hardly an elegant or efficient approach (and the check that each number contains no repeated digits is not necessary), but there is no arguing with the fact that it does correctly find the solution to the puzzle.

      I then tried to get it to improve the program. Firstly by removing the unnecessary check (which it did), and then by performing an exhaustive search in case there are further solutions while avoiding duplicate solutions. It took a bit of discussion to get it to implement this second improvement correctly, but here is the final program:

      import itertools
      
      # Join the digits in the given list of numbers into a single integer
      def join_and_convert(nums):
        return int(''.join(map(str, nums)))
      
      # Generate all permutations of the digits 1-9
      digits = range(1, 10)
      permutations = itertools.permutations(digits)
      
      # Find sets of three numbers with the same digit sum and sum to a perfect square
      solutions = set()
      for p in permutations:
        a, b, c = p[:3], p[3:6], p[6:]
        if sum(a) == sum(b) == sum(c):
          total = join_and_convert(a) + join_and_convert(b) + join_and_convert(c)
          if len(str(total)) == 3 and int(total ** 0.5) ** 2 == total:
            solution = tuple(sorted([a, b, c]))
            if solution not in solutions:
              solutions.add(solution)
              print(solution)
      

      Like

    • GeoffR's avatar

      GeoffR 12:09 pm on 2 March 2023 Permalink | Reply

      @Jim: Very Interesting experiment, both for the original program and the second solution. Would it be possible to ask ChatGPT to write a program in other languages?

      Like

      • Jim Randell's avatar

        Jim Randell 3:10 pm on 2 March 2023 Permalink | Reply

        @GeoffR: I don’t think ChatGPT has been specifically trained in Python (or indeed in programming). But as Python is quite a popular language amount of it was probably present in the data it was trained on.

        What ChatGPT is doing is attempting to provide a likely continuation of the conversation I started with my question. It has been trained on an extremely large data set, so it is able to give a plausible looking answer to a wide variety of questions.

        However it is very much a jack of all trades, master of none. If you have asked a vague question on a subject you are not too familiar the plausible sounding answer feels like it might be right. But if you ask a specific question on something you know a lot about you soon discover that it will confidently state an answer that is fundamentally flawed.

        When it confidently produces a program and claims that it solves the problem you gave it, what it is really saying is: “problems that look similar to the problem you set can be solved by programs that look similar to the program I have given”.

        Jim: Which weighs more, a pound of feathers or 2 pounds of bricks?

        ChatGPT: Both the pound of feathers and the 2 pounds of bricks weigh the same, which is 1 pound.

        The confusion may arise because when we hear “2 pounds of bricks”, our mind may immediately think of the number “2” and assume that it must weigh more than just “1”. However, the key here is to pay attention to the unit of measurement, which is pounds.

        In summary, a pound of feathers and 2 pounds of bricks weigh the same amount, which is 1 pound.

        And you can ask it to justify this incorrect position, and it will produce more paragraphs of plausible sounding rubbish. Sometimes the plausible sounding answer will be a correct response to a question, but it is able to bluff its way though a remarkably wide variety of subjects.

        I have tried asking it to solve a few puzzles before, and this is the first it has actually succeeded on. But you do need to understand the puzzle yourself (and if you have asked it to write a program, the programming language too), to be able to gauge how useful the plausible sounding response it produces is.

        Like

    • GeoffR's avatar

      GeoffR 5:06 pm on 2 March 2023 Permalink | Reply

      @Jim: Thanks for the detailed appraisal.

      I found a link stating that ChatGPT is written in Python and knows 12 programming languages:

      https://seo.ai/blog/how-many-languages-does-chatgpt-support#:~:text=ChatGPT%20is%20mainly%20focused%20on,JavaScript

      Like

  • Unknown's avatar

    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 is included in the book Sunday Times Brain Teasers (1974).

    [teaser195]

     
    • Jim Randell's avatar

      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

  • Unknown's avatar

    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] [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 is included in the book The Sunday Times Book of Brain-Teasers: Book 2 (1981).

    [teaser653]

     
    • Jim Randell's avatar

      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 -rr
      
      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's avatar

      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's avatar

      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

  • Unknown's avatar

    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 is included in the book Sunday Times Brain Teasers (1974).

    [teaser87]

     
    • Jim Randell's avatar

      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

  • Unknown's avatar

    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 is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser890]

     
    • Jim Randell's avatar

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

      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's avatar

        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's avatar

          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

  • Unknown's avatar

    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 is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    It is also included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser905]

     
    • Jim Randell's avatar

      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 -rr
      
      #  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's avatar

        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's avatar

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

          Apologies. Misread it. Yours is correct.

          Like

      • Frits's avatar

        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's avatar

        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's avatar

      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's avatar

      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's avatar

        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's avatar

      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

  • Unknown's avatar

    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 is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser660]

     
    • Jim Randell's avatar

      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 76ms. (Internal runtime of the generated code is 468µs).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      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 = multiset.from_seq"
      "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]) == {1: 10, 2: 9, 3: 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 typed on one row, except for a single letter
      "sorted(count([S, Q, U, A, S, H]).values()) == [1, 5]"
      
      # 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 static block limit
      --denest=-1  # apply fix for CPython
      

      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 can’t tell what order the keys are in on a row.

      Liked by 1 person

      • Frits's avatar

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

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

        Like

        • Jim Randell's avatar

          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's avatar

            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's avatar

              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's avatar

              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's avatar

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

          @Frits: It should be working now.

          Like

          • Frits's avatar

            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's avatar

        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's avatar

          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. To try the other interpretation you can just remove one of the S‘s from the list on line 32 and check for a [1, 4] allocation of letters. 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 -rr 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 -rr 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's avatar

            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's avatar

      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's avatar

      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's avatar

        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's avatar

          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's avatar

            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's avatar

              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's avatar

      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's avatar

      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's avatar

        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's avatar

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

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

          Like

          • John Crabtree's avatar

            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's avatar

      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

  • Unknown's avatar

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

    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 11:43 am on 12 April 2020 Permalink | Reply
    Tags: by: R Postill   

    Brain-Teaser 517: [Rugby table] 

    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?

    This puzzle was originally published with no title.

    [teaser517]

     
    • Jim Randell's avatar

      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

  • Unknown's avatar

    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 is included in the book Sunday Times Brain Teasers (1974).

    [teaser511]

     
    • Jim Randell's avatar

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

      This Python program runs in 73ms.

      from enigma import (defaultdict, 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

  • Unknown's avatar

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

    Brain-Teaser 504: [Scrumps competition] 

    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?

    This puzzle was originally published with no title.

    [teaser504]

     
    • Jim Randell's avatar

      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.

      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

  • Unknown's avatar

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

    Brain-Teaser 497: [Alphabets Cricket Club] 

    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?

    This puzzle was originally published with no title.

    [teaser497]

     
    • Jim Randell's avatar

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

      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

  • Unknown's avatar

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

    Brain-Teaser 488: [Truth and lies] 

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

    This puzzle was originally published with no title.

    [teaser488]

     
    • Jim Randell's avatar

      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

  • Unknown's avatar

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

    Brain-Teaser 482: [Alphabetical cricket] 

    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?

    This puzzle was originally published with no title.

    [teaser482]

     
    • Jim Randell's avatar

      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

  • Unknown's avatar

    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 is included in the book Sunday Times Brain Teasers (1974).

    [teaser473]

     
    • Jim Randell's avatar

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

      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

  • Unknown's avatar

    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 is included in the book Sunday Times Brain Teasers (1974).

    [teaser464]

     
    • Jim Randell's avatar

      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

  • Unknown's avatar

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

    Brain-Teaser 455: [Darts teams] 

    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?

    This puzzle was originally published with no title.

    [teaser455]

     
    • Jim Randell's avatar

      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
Design a site like this with WordPress.com
Get started