Updates from October, 2024 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 11:18 am on 1 October 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 545: Gold Cup 

    From The Sunday Times, 5th December 1971 [link]

    Although 42 horses (numbered 1 to 42) were originally entered for the Teaser Gold Cup, only 38 took part in the race.

    The number of the horse which came second in the race exceeded the number of the winner by the same number as that by which the number of the third horse exceeded the number of the second.

    When the numbers of the 4 non-runners were placed in numerical order, the difference between each number and the next was the same in every case, and that difference was the same as the difference between the number of the horse which won the race and the number of the horse which came second.

    The sum of the numbers of the highest and lowest of the four non-runners was equal to the sum of the numbers of the horses which came first, second, and third.

    One of the first three horses was numbered 15. Horses numbered 24 and 28 fell at the third fence, and were not remounted.

    What were the numbers of the 4 non-runners?

    [teaser545]

     
    • Jim Randell's avatar

      Jim Randell 11:18 am on 1 October 2024 Permalink | Reply

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

      It runs in 90ms. (Internal runtime of the generated program is 12ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # suppose the first three horses are: A, B, C (ordered by number)
      # and the four non-runners are: W, X, Y, Z (ordered by number)
      
      --base=43
      --digits="1-42"
      --distinct="ABCWXYZ"
      --invalid="24|28,ABCWXYZ"
      
      # A, B, C are ordered by number
      "A < B" "B < C"
      # and form an arithmetic progression (common diff = D)
      "B - A = D"
      "C - B = D"
      # one of them is 15
      "15 in {A, B, C}"
      
      # W, X, Y, Z are ordered by number
      "W < X" "X < Y" "Y < Z"
      # and form an arithmetic progression (common diff = D)
      "X - W = D"
      "Y - X = D"
      "Z - Y = D"
      
      # equal sums
      "W + Z == A + B + C"
      
      --template=""
      

      Solution: The 4 non-runners were: 12, 19, 26, 33.

      Which forms an arithmetic progression with common difference of 7.

      The horses in the first 3 places were: 8, 15, 22.

      Which also form an arithmetic progression with a common difference of 7.

      The sum of the numbers of the first 3 horses is 8 + 15 + 22 = 45, the same as the sum of the highest and lowest numbered non-runners 12 + 33 = 45.

      Like

      • Ruud's avatar

        Ruud 4:36 pm on 1 October 2024 Permalink | Reply

        import itertools
        
        
        for non_runners in itertools.combinations(set(range(1, 43)) - {15, 24, 28}, 4):
            if (diff := non_runners[1] - non_runners[0]) == non_runners[2] - non_runners[1] == non_runners[2] - non_runners[1] == non_runners[3] - non_runners[2]:
                runners123 = set(range(1, 43)) - set(non_runners) - {24, 28}
                for first in runners123:
                    second = first + diff
                    third = second + diff
                    if second in runners123 and third in runners123 and 15 in {first, second, third} and first + second + third == non_runners[0] + non_runners[3]:
                        print(f"{non_runners=} {first=} {second=} {third=}")
        

        , which prints:

        non_runners=(12, 19, 26, 33) first=8 second=15 third=22
        

        Like

    • GeoffR's avatar

      GeoffR 7:23 pm on 1 October 2024 Permalink | Reply

      
      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..42:NR1; var 1..42:NR2; var 1..42:NR3; var 1..42:NR4;
      var 1..42:W1; var 1..42:W2; var 1..42:W3;
      
      constraint all_different ([NR1, NR2, NR3, NR4, W1, W2, W3]);
      
      constraint W2 - W1 == W3 - W2;
      
      constraint  NR1 < NR2 /\ NR2 < NR3 /\ NR3 < NR4;
      
      constraint NR2 - NR1 == NR3 - NR2 /\ NR3 - NR2 == NR4 - NR3;
      
      constraint NR2 - NR1 == W2 - W1;
      
      constraint NR1 + NR4 == W1 + W2 + W3;
      
      constraint sum ([W1 == 15, W2 == 15, W3 == 15]) == 1;
      
      var set of int: horses = {NR1, NR2, NR3, NR4, W1, W2, W3};
      
      constraint card ({24, 28} intersect horses) == 0;
      
      solve satisfy;
      
      output ["Numbers of non-runners = " ++ show([NR1, NR2, NR3, NR4]) ++
      "\nWinning numbers = " ++ show([W1, W2, W3]) ];
      
      % Numbers of non-runners = [12, 19, 26, 33]
      % Winning numbers = [8, 15, 22]
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

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

    Brain-Teaser 231: Holidays abroad 

    From The Sunday Times, 26th September 1965 [link]

    My old friends Alf, Bert, Charlie, Duggie and Ernie went with their wives for holidays abroad last year, to Andorra, Boulogne, Calais, Dunkirk and Ethiopia. I knew that the names of the five wives were Agnes, Beatrice, Clarissa, Daphne and Ethel, but the only information I had about who was married to whom was that for each pair the names of the husband, the wife and last year’s holiday destination all began with different letters.

    In conversation with the ladies, Beatrice told me that she was not married to Alf, and that she had heard from Ernie that Charlie went to Dunkirk last year.

    Daphne, however, firmly informed me that Charlie went to Ethiopia and that Beatrice went to Dunkirk. “Unlike some people I could mention”, she added darkly, “Alf always tells the truth”.

    Clarissa said that when her husband was asked whether Ethel was married to Charlie, he replied: “No”. She went on to tell me that Duggie went to Boulogne.

    Of each of these married couples one member always told the truth and the other never did.

    Name each man’s wife and holiday resort.

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

    [teaser231]

     
    • Jim Randell's avatar

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

      I used the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this puzzle.

      I assigned 5 slots (1 – 5), each of which contains one wife, husband, destination and trait (for the wife).

      The following run file fills out the slots. It runs in 77ms. (Internal runtime of the generated code is 631µs).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # let A = 1, B = 2, C = 3, D = 4, E = 5
      #
      #  slot       : 1 2 3 4 5
      #  wife       : A B C D E
      #  husband    : F G H I J  {Alf, Bert, Charlie, Duggie, Ernie}
      #  destination: K L M N P  {Andorra, Boulogne, Calais, Dunkirk, Ethiopia}
      #  wife trait : V W X Y Z  {0 = false or 1 = true}
      --base=6
      --distinct="FGHIJ,KLMNP,FK,GL,HM,IN,JP"
      --invalid="0,FGHIJKLMNP"
      --invalid="2|3|4|5,VWXYZ"
      --invalid="1,FK"
      --invalid="2,GL"
      --invalid="3,HM"
      --invalid="4,IN"
      --invalid="5,JP"
      
      --macro="@hs = (F, G, H, I, J)"  # husbands
      --macro="@ds = (K, L, M, N, P)"  # destinations
      --macro="@ts = (V, W, X, Y, Z)"  # traits
      
      # find a slot with value v
      --code="slot = lambda vs, v: vs.index(v)"
      
      # check statements truth value "x" says "y"
      --code="check = lambda x, y: bool(x) == bool(y)"
      
      # Beatrice says (Beatrice is not married to Alf)
      "check(W, G != 1)"
      
      # Beatrice says (Ernie says Charlie went to Dunkirk)
      "check(W, check(@ts[slot(@hs, 5)] ^ 1, @ds[slot(@hs, 3)] == 4))"
      
      # Daphne says (Charlie went to Ethiopia)
      "check(Y, @ds[slot(@hs, 3)] == 5)"
      
      # Daphne says (Beatrice went to Dunkirk)
      "check(Y, L == 4)"
      
      # Daphne says (Alf tells the truth)
      "check(Y, @ts[slot(@hs, 1)] == 0)"
      
      # Clarissa says (her husband says (Ethel is not married to Charlie))
      "check(X, check(X ^ 1, J != 3))"
      
      # Clarissa says (Duggie went to Boulogne)
      "check(X, @ds[slot(@hs, 4)] == 2)"
      
      --template="(F G H I J) (K L M N P) (V W X Y Z)"
      --solution=""
      

      And this following Python program formats the output using the appropriate labels:

      from enigma import (SubstitutedExpression, printf)
      
      p = SubstitutedExpression.from_file("teaser231.run",
        # return (<husbands>, <destinations>, <traits>)
        ["--answer=(@hs, @ds, @ts)"],
      )
      
      make_map = lambda vs: dict(enumerate(vs.split(), start=1))
      husbands = make_map("Alf Bert Charlie Duggie Ernie")
      wives = make_map("Agnes Beatrix Clarissa Daphne Ethel")
      destinations = make_map("Andorra Boulogne Calais Dunkirk Ethiopia")
      traits = { 0: 'False', 1: 'True' }
      
      for ans in p.answers(verbose=0):
        # collect the slots [wife, husband, destination, trait]
        d = dict((k, [v]) for (k, v) in wives.items())
        for (vs, labels) in zip(ans, [husbands, destinations, traits]):
          for (k, v) in enumerate(vs, start=1):
            d[k].append(labels[v])
        # output the slots
        for k in sorted(d.keys(), key=(lambda k: d[k][1])):
          (W, H, D, T) = d[k]
          printf("{H} and {W} ({T}) -> {D}")
        printf()
      

      Solution: Alf and Clarissa → Dunkirk; Bert and Daphne → Ethiopia; Charlie and Ethel → Andorra; Duggie and Agnes → Boulogne; Ernie and Beatrix → Calais

      We know:

      Alf and Clarissa → Dunkirk; (Alf = F; Clarissa = T)
      Bert and Daphne → Ethiopia; (Bert = T, Daphne = F)
      Charlie and Ethel → Andorra
      Duggie and Agnes → Boulogne
      Ernie and Beatrix → Calais (Ernie = F, Beatrix = T)

      We cannot determine the traits for Charlie and Ethel or for Duggie and Agnes.

      Like

      • Frits's avatar

        Frits 11:02 am on 24 September 2024 Permalink | Reply

        @Jim,

        I have a different naming convention for storing Python programs.
        One idea might be to use something like (assuming the filename doesn’t contain periods):

        import os
        
        runfile = os.path.basename(__file__).split(".")[0] + ".run"
        p = SubstitutedExpression.from_file(runfile, [
          # return (<husbands>, <destinations>, <traits>)
          "--answer=(@hs, @ds, @ts)",
        ])
        

        Like

        • Jim Randell's avatar

          Jim Randell 3:28 pm on 26 September 2024 Permalink | Reply

          I’ve added a [[ parsepath() ]] function to enigma.py that can be used to extract the following components of a path:

          {path} = the full path name = "{dir}/{file}"
          {stem} = path without extension = "{dir}/{name}"
          {dir}  = directory containing the file
          {file} = the filename = "{name}{ext}"
          {name} = the filename without extension
          {ext}  = the extension
          

          For example:

          {path} = "/users/jim/puzzles/enigma/enigma123.py"
          {stem} = "/users/jim/puzzles/enigma/enigma123"
          {dir}  = "/users/jim/puzzles/enigma"
          {file} = "enigma123.py"
          {name} = "enigma123"
          {ext}  = ".py"
          

          You can then call this to make a new path name from an existing one.

          For example to make a path in the same directory:

          path = parsepath("{dir}/enigma123.run", __file__)
          

          Or to just replace the extension:

          path = parsepath("{dir}/{name}.run", __file__)
          
          path = parsepath("{stem}.run", __file__)
          

          As an added bonus [[ SubstitutedExpression.from_file() ]] will automatically pass the arguments to [[ parsepath() ]] if a non-string is provided as a path, to save you the bother:

          p = SubstitutedExpression.from_file(["{stem}.run", __file__])
          

          Like

  • Unknown's avatar

    Jim Randell 11:18 am on 20 August 2024 Permalink | Reply
    Tags:   

    Brain Teaser: Circular tour 

    From Sunday Times Brain Teasers 1974 [link]

    Arley, Barley, Carley, Darley, Earley and Farley are six villages connected, in that order, by a circular bus service. A passenger is allowed to board at any village and alight at any other or complete the whole circuit. The distances from any village to any other village on the route are all different and all an exact number of miles, consisting of every possible whole number from 1 mile up to the distance for the whole circuit.

    Arley to Barley is 1 mile. The distance from Farley to Arley is greater than that from Barley to Carley but less than that from Earley to Farley.

    What is the distance from Earley to Farley?

    This puzzle is taken from the book Sunday Times Brain Teasers (1974), but it is not a puzzle that originally appeared in The Sunday Times.

    [teaser-book-1974-p95]

     
    • Jim Randell's avatar

      Jim Randell 11:20 am on 20 August 2024 Permalink | Reply

      We have encountered puzzles involving magic circles before (see: Teaser 1986, Enigma 985, Puzzle #171), and developed tools for generating them

      This Python program considers magic circles of size 6, and then looks for ones that meet the specified requirements.

      It runs in 75ms. (Internal runtime is 4.1ms).

      Run: [ @replit ]

      from magic_circles import magic_circle
      from enigma import printf
      
      # look for a size 6 magic circle (including reflections)
      for ds in magic_circle(6, refl=1):
        # distance AB = 1
        assert ds[0] == 1
        # distance EF > distance FA > distance BC
        if not (ds[4] > ds[5] > ds[1]): continue
        # output solution
        printf("distance EF = {ds[4]} [ds = {ds}]")
      

      Solution: The distance from Earley to Farley is 12 miles.

      And we have the following distances around the circle:

      A→B = 1
      B→C = 2
      C→D = 7
      D→E = 4
      E→F = 12
      F→A = 5

      Like

      • Ruud's avatar

        Ruud 2:09 pm on 21 August 2024 Permalink | Reply

        A bit verbose and certainly very brute force:

        import itertools
        
        ab = 1
        for bc, cd, de, ef, fa in itertools.permutations(range(2, 20), 5):
            ac = ab + bc
            ad = ac + cd
            ae = ad + de
            af = ae + ef
            bd = bc + cd
            be = bd + de
            bf = be + ef
            ba = bf + fa
            ce = cd + de
            cf = ce + ef
            ca = cf + fa
            cb = ca + ab
            df = de + ef
            da = df + fa
            db = da + ab
            dc = db + bc
            ea = ef + fa
            eb = ea + ab
            ec = eb + bc
            ed = ec + cd
            fb = fa + ab
            fc = fb + bc
            fd = fc + cd
            fe = fd + de
        
            if ef > fa > bc and af+fa == 31:
                if {ab, ac, ad, ae, af, bc, bd, be, bf, ba, cd, ce, cf, ca, cb, de, df, da, db, dc, ef, ea, eb, ec, ed, fa, fb, fc, fd, fe} == set(range(1, 31)):
                    print(ab, ac, ad, ae, af, bc, bd, be, bf, ba, cd, ce, cf, ca, cb, de, df, da, db, dc, ef, ea, eb, ec, ed, fa, fb, fc, fd, fe)
                    print(ef)
        

        Like

    • Lise Andreasen's avatar

      Lise Andreasen 10:27 pm on 20 August 2024 Permalink | Reply

      So… The bus travels 31 miles on 1 circuit. And there’s also 2 villages, 31 miles apart on the circuit. Is this the distance from a village to itself?

      Like

      • Jim Randell's avatar

        Jim Randell 8:14 am on 21 August 2024 Permalink | Reply

        That’s right. A complete circuit is 31 miles, but we can also travel all the distances between 1 and 30 miles.

        A→B = 1; B→A = 30
        B→C = 2; C→B = 29
        A→C = 3; C→A = 28
        D→E = 4; E→D = 27
        F→A = 5; A→F = 26
        F→B = 6; B→F = 25
        C→D = 7; D→C = 24
        F→C = 8; C→F = 23
        B→D = 9; D→B = 22
        A→D = 10; D→A = 21
        C→E = 11; E→C = 20
        E→F = 12; F→E = 19
        B→E = 13; E→B = 18
        A→E = 14; E→A = 17
        F→D = 15; D→F = 16

        Like

        • Lise Andreasen's avatar

          Lise Andreasen 6:29 pm on 21 August 2024 Permalink | Reply

          Just an odd way to describe the 31 miles “distance”.

          “The distances from any village to any other village on the route are all different and all an exact number of miles, consisting of every possible whole number from 1 mile up to the distance for the whole circuit.”

          Like

    • GeoffR's avatar

      GeoffR 9:48 am on 21 August 2024 Permalink | Reply

      There are 6 single trips between villages and 6 trips for each of 2,3,4 and 5 bus stops between villages e.g. AB, BC, CD, DE, EF, FA for single stops between villages. There is only 1 circular bus trip visiting all 6 villages i.e. AB + BC + CD + DE + EF + FA.
      In total, therefore, there are 5 X 6 + 1 = 31 total bus trips.
      This MiniZinc solution enumerates all possible bus trips.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      % Villages are A,B,C,D,E,F
      
      % Assumed upper bounds of six distances between villages
      var 1..15:AB; var 1..15:BC; var 1..15:CD; var 1..15:DE; var 1..15:EF; var 1..15:FA;
      
      constraint all_different ([AB, BC, CD, DE, EF, FA]);
      
      constraint FA > BC /\ FA < EF;
      constraint AB == 1; 
      
      % There are 31 possible distances between villages, as shown below:
      var set of int: soln_nums == {AB, BC, CD, DE, EF, FA,  % 1 stop
      AB+BC, BC+CD, CD+DE, DE+EF, EF+FA, FA+AB,              % 2 stops
      AB+BC+CD, BC+CD+DE, CD+DE+EF, DE+EF+FA, EF+FA+AB, FA+AB+BC,  % 3 stops
      AB+BC+CD+DE, BC+CD+DE+EF, CD+DE+EF+FA, DE+EF+FA+AB, EF+FA+AB+BC, FA+AB+BC+CD, % 4 stops
      AB+BC+CD+DE+EF, BC+CD+DE+EF+FA, CD+DE+EF+FA+AB, DE+EF+FA+AB+BC, % 5 stops
      EF+FA+AB+BC+CD, FA+AB+BC+CD+DE, 
      AB+BC+CD+DE+EF+FA};  % 6 stops
      
      set of int: req_nums = 1..31;
       
      constraint soln_nums == req_nums;
      
      constraint card(soln_nums) == AB + BC + CD + DE + EF + FA;
      
      solve satisfy;
      
      output["[AB, BC, CD, DE, EF, FA] = " ++ show( [AB, BC, CD, DE, EF, FA] )
      ++"\n" ++ "Distance from Earley to Farley =  "  ++ show(EF) ++ " miles."];
      
      % [AB, BC, CD, DE, EF, FA] = [1, 2, 7, 4, 12, 5]
      % Distance from Earley to Farley =  12 miles.
      % ----------
      % ==========
      
      
      

      Like

    • Ruud's avatar

      Ruud 11:24 am on 22 August 2024 Permalink | Reply

      A bit less verbose:

      import itertools
      
      succ = dict(zip("abcdef", "bcdefa"))
      
      for x5 in itertools.permutations(range(2, 20), 5):
          dist = dict(zip("ab bc cd de ef fa".split(), [1, *x5]))
          for d in "abcdef":
              d1 = succ[d]
              for _ in range(4):
                  dist[d + succ[d1]] = dist[d + d1] + dist[d1 + succ[d1]]
                  d1 = succ[d1]
          if dist["ef"] > dist["fa"] > dist["ac"] and set(dist.values()) == set(range(1, 31)):
              print(dist["ef"])
      

      Like

    • Frits's avatar

      Frits 4:10 pm on 22 August 2024 Permalink | Reply

      Using my Puzzle #171 code but now using decomposition.

           
      from itertools import permutations
      
      # for a list with n numbers check if the set of sums of 1...n-1 adjacent items
      # (also circular) is complete (with n.(n-1) different sums)
      def allOccurOnce(s, ln):                                         
        sms = set(s)
        for i in range(ln):
          for j in range(2, ln):
            if (sm := sum((s * 2)[i:i + j])) in sms: 
              return False
            sms.add(sm)
        return True  
      
      # decompose <t> into <k> increasing numbers from <ns>
      def decompose(t, k, ns, s=[]):
        if k == 1:
          if t in ns:
            yield s + [t]
        else:
          for i, n in enumerate(ns):
            # make sure we don't overshoot
            if 2 * t < (2 * n + k - 1) * k: break
            yield from decompose(t - n, k - 1, ns[i + 1:], s + [n])
      
      N = 6
      T = N**2 - N + 1
      H = int(T // 2)
      
      # for a magic circle with N numbers and total sum T
      # the highest possible circle number is T - N(N-1)/2 which equals H + 1
      
      # exclude mandatory numbers 1 and 2 from the decomposition
      for dc in decompose(T - 3, N - 2, range(3, H + 2)):
        # weave in the number 2
        for p in permutations([2] + dc):
          p1 = (1, ) + p
          
          # distance EF > distance FA > distance BC
          if not (p1[4] > p1[5] > p1[1]): continue
          # can we make N.(N - 1) different sums?
          if not allOccurOnce(p1, N): continue
          print(f"answer: {p1[4]} miles")
      

      Like

  • Unknown's avatar

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

    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 10:49 am on 25 July 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 416: Sharing sweets 

    From The Sunday Times, 27th April 1969 [link]

    The Binks family had a birthday party recently for Ann, the youngest child. A tin of toffees was shared among the seven youngest members of the family, whose ages were all different and less than 20.

    It was decided that the younger children should have more than the older; so Mr Binks suggested that each should receive the number obtained by dividing the total number of toffees by his or her age. This was done and it so happened that all the divisions worked out exactly and no toffees were left over.

    Mary received 18 sweets.

    How much older was she than Ann?

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

    [teaser416]

     
    • Jim Randell's avatar

      Jim Randell 10:51 am on 25 July 2024 Permalink | Reply

      Labelling the children in age order A, …, G, so A is Ann, and the total number of toffees is T, we have:

      T = T/A + T/B + T/C + T/D + T/E + T/F + T/G

      1 = 1/A + 1/B + 1/C + 1/D + 1/E + 1/F + 1/G

      So we need to find 7 different reciprocals (with denominators less than 20) that sum to 1. And we can use the [[ reciprocals() ]] function from the enigma.py library to do that (originally written for Enigma 348).

      We can then assign one of the values T/B, …, T/G to 18 (for Mary), and check that all the remaining T/X values give a whole number.

      The following Python program runs in 66ms. (Internal runtime is 753µs).

      Run: [ @replit ]

      from enigma import (reciprocals, printf)
      
      # find 7 different reciprocals that sum to 1
      for rs in reciprocals(7, 1, 1, M=19, g=1):
        # Ann is the youngest
        A = rs.pop(0)
        # Mary received 18 sweets
        for M in rs:
          T = M * 18
          if not all(T % r == 0 for r in rs): continue
          # output solution
          printf("A={A} M={M} [T={T} rs={rs}]", rs=[A] + rs)
      

      Solution: Mary (10) is 7 years older than Ann (3).

      There is only one set of 7 reciprocals that works:

      1 = 1/3 + 1/4 + 1/9 + 1/10 + 1/12 + 1/15 + 1/18

      There are 180 toffees, and the ages are:

      3 → 60 toffees (Ann)
      4 → 45 toffees
      9 → 20 toffees
      10 → 18 toffees (Mary)
      12 → 15 toffees
      15 → 12 toffees
      18 → 10 toffees
      total = 180 toffees

      Like

    • GeoffR's avatar

      GeoffR 1:32 pm on 25 July 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..19:A; var 1..19:B; var 1..19:C; var 1..19:D;
      var 1..19:E; var 1..19:F; var 1..19:G;
      
      constraint all_different ([A, B, C, D, E, F, G]);
      % Order childrens ages
      constraint A < B /\ B < C /\ C < D /\ D < E /\ E < F /\ F < G;
      
      var 28..200:T;  % total number of toffees
      
      % Distribute toffees to seven children, with none left over
      constraint T  mod A == 0 /\ T  mod B == 0 /\ T  mod C == 0 /\ T  mod D == 0 
      /\ T  mod E == 0 /\ T  mod F == 0 /\ T  mod G == 0;
      
      constraint T == T div A  + T div B + T div C + T div D 
      + T div E + T div F + T div G;
      
      solve satisfy;
      
      output ["Childrens ages = " ++ show ( [A,B,C,D,E,F,G] )
      ++ "\n" ++ "Number of toffees = " ++ show(T) ];
      
      % Childrens ages = [3, 4, 9, 10, 12, 15, 18]
      % Number of toffees = 180
      % ----------
      % ==========
      

      As Mary received 18 sweets, she was 10 years old, as 10 * 18 = 180.
      As the youngest was Ann, she was 3 years old, 7 years younger than Mary.

      Like

    • Ruud's avatar

      Ruud 6:40 am on 26 July 2024 Permalink | Reply

      [removed]

      Like

      • Jim Randell's avatar

        Jim Randell 9:21 am on 26 July 2024 Permalink | Reply

        @Ruud: Can you explain the reasoning behind your code? I tried changing the 18 to 24 to solve a variation of the puzzle, but it did not give a correct answer. (I also added the missing imports to your program).

        Like

        • ruudvanderham's avatar

          ruudvanderham 12:02 pm on 26 July 2024 Permalink | Reply

          @Jim
          My solution was incorrect. Could you remove it?
          Here’s my revised one, which is not much different from yours:

          import itertools
          import math
          
          number_of_toffee_mary = 18
          for ages in itertools.combinations(range(1, 20), 7):
              if math.isclose(sum(1 / age for age in ages), 1):
                  for age in ages:
                      if all(divmod(age * number_of_toffee_mary, try_age)[1] == 0 for try_age in ages):
                          print(f"{ages=}. Number of toffees={number_of_toffee_mary*age}. Mary is {age-ages[0]} years older than Ann")

          Like

    • Frits's avatar

      Frits 4:59 pm on 27 July 2024 Permalink | Reply

      from itertools import combinations
      
      M = 19
      N = 7
      
      # return divisors of <n> between 2 and <m>
      def divs(n, m=M):
        lst = [d for d in range(2, int(n**.5) + 1) if n % d == 0 and d <= m]
        return lst + [d for x in lst[::-1] if x * x != n and (d := n // x) <= m]
      
      # reciprocal sum
      def rs(s): 
        i, t = 0, 0.0
        ln = len(s)
        while t < 1 and i < ln:
          t += 1 / s[i]
          i += 1
        return i == ln and round(t, 5) == 1
      
      # Mary received 18 sweets
      for m in range(2, M + 1):
        # pick <N> divisors
        for c in combinations(divs(18 * m), N):
          # reciprocal sum must be 1
          if rs(c) == 1: 
            print(f"answer: {c}")
      

      Like

  • Unknown's avatar

    Jim Randell 8:19 am on 18 June 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 339: Cross country 

    From The Sunday Times, 5th November 1967 [link]

    Tom, Dick and Harry had come up the school together and in three successive years had competed in the Junior, Colts and Senior, cross-country races.

    Every year they finished in the same positions but interchanged so that no boy came in the same position twice. The same applied to the numbers on their vests, all six numbers being different, odd, and greater than 1.

    When each boy multiplied his position number by the number he was wearing at the time, the nine results were all different and together totalled 841.

    Dick beat the other two in the Junior race, but the number he was wearing was smaller than his position number; but he was wearing the highest card number in the Senior race, and this was [also] smaller than his position number.

    Harry’s three products had a sum smaller than that of either of the other two.

    What were the three boys’ positions in the Colts race and what numbers were they wearing?

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

    [teaser339]

     
    • Jim Randell's avatar

      Jim Randell 8:20 am on 18 June 2024 Permalink | Reply

      Let the finishing positions be (best to worst) (a, b, c), and the runner numbers be (smallest to largest) (x, y, z).

      These six numbers are all different, odd, and greater than 1.

      Taking the cartesian product of these sets we get:

      {a, b, c} × {x, y, z} = {ax, ay, az, bx, by, bz, cx, cy, cz}

      And for each boy in each race the product of his runner number and his finishing position gives a unique value, so they correspond directly with the 9 values produced by this cartesian product.

      The following Python program considers divisor pairs of 841, and then splits each divisor into 3 odd numbers meeting the requirements. We then use the [[ SubstitutedExpression() ]] solver from the enigma.py library to assign runner numbers and finishing positions in each race.

      The program runs in 116ms. (Internal runtime is 38ms).

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, divisors_pairs, decompose, div, cproduct, sprintf as f, join, printf)
      
      # find 3 odd numbers that give the required total
      def numbers(t):
        r = div(t - 3, 2)
        if r:
          for (a, b, c) in decompose(r, 3, min_v=1, sep=1):
            yield (2 * a + 1, 2 * b + 1, 2 * c + 1)
      
      # solve for positions <pos>, numbers <num>
      def solve(pos, num):
        # let the positions and numbers in the races be:
        #           pos   | num
        #           T D H | T D H
        #  junior:  A B C | D E F
        #  colt:    G H I | J K L
        #  senior:  M N P | Q R S
      
        # an expression to calculate the sum of products
        def fn(ps, ns):
          ps = join((f("pos[{x}]") for x in ps), sep=", ", enc="[]")
          ns = join((f("num[{x}]") for x in ns), sep=", ", enc="[]")
          return f("dot({ps}, {ns})")
        # construct an alphametic puzzle
        exprs = [
          # "D beat T and H in the junior race ..."
          "pos[B] < pos[A]",
          "pos[B] < pos[C]",
          # "... but his runner number was less than his position number"
          "num[E] < pos[B]",
          # "D has the largest runner number in the senior race ..."
          "num[R] > num[Q]",
          "num[R] > num[S]",
          # "... but his runner number was less than his position number"
          "num[R] < pos[N]",
          # "the sum of H's products were smaller than T's or D's"
          f("{H} < min({T}, {D})", T=fn("AGM", "DJQ"), D=fn("BHN", "EKR"), H=fn("CIP", "FLS")),
        ]
        p = SubstitutedExpression(
          exprs,
          base=3, # values are (0, 1, 2)
          distinct="ABC DEF GHI JKL MNP QRS AGM BHN CIP DJQ EKR FLS".split(),
          env=dict(pos=pos, num=num),
        )
        # solve the alphametic
        for s in p.solve(verbose=""):
          # output solution
          for (t, ps, ns) in zip(["junior", "colt  ", "senior"], ["ABC", "GHI", "MNP"], ["DEF", "JKL", "QRS"]):
            ((pT, pD, pH), (nT, nD, nH)) = ((pos[s[k]] for k in ps), (num[s[k]] for k in ns))
            printf("{t}: T (#{nT}) = {pT}; D (#{nD}) = {pD}; H (#{nH}) = {pH}")
      
      # consider the values of (a + b + c) * (x + y + z)
      for (p, q) in divisors_pairs(841, every=1):
        if p < 15 or q < 15: continue
        for (abc, xyz) in cproduct([numbers(p), numbers(q)]):
          # all numbers and products are different
          if len(set(abc + xyz)) != 6: continue
          if len(set(i * j for (i, j) in cproduct([abc, xyz]))) != 9: continue
          # solve the puzzle
          solve(abc, xyz)
      

      Solution: In the Colts race Tom (#15) finished 5th; Dick (#11) finished 7th; Harry (#3) finished 17th.

      The full results are:

      Junior: Tom (#11) = 17th; Dick (#3) = 5th; Harry (#15) = 7th
      Colts: Tom (#15) = 5th; Dick (#11) = 7th; Harry (#3) = 17th
      Senior: Tom (#3) = 7th; Dick (#15) = 17th; Harry (#11) = 5th

      In each race the runner numbers are #3, #11, #15 and the positions are 5th, 7th, 17th.

      And the sum of the nine different products of these numbers is:

      (3 + 11 + 15) × (5 + 7 + 17) = 29 × 29 = 841

      In fact the only viable factorisation of 841 is 29 × 29.

      Like

  • Unknown's avatar

    Jim Randell 4:48 pm on 2 June 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 443: Space ship 

    From The Sunday Times, 2nd November 1969 [link]

    Says Bell at the pub:

    I’ve been reading about a space ship. They fly the thing round playing the usual Cops and Robbers stuff, but the way they name the crew is interesting. Using A, B, C and so on right up to I, no gaps, they make, once only, every possible pair of different letters; so there’s no AA, BB and so on, and they take no notice of the order in a pair; s0 BC is the same as CB.

    Four, including AD and BC, are on a list of officers and on that list no letter appears more than once. All the rest are just proles but do they have an A initial they call themselves A proles.

    All proles work in shifts with the same number on each shift — not just two shifts; more than that — and at the end of a shift every prole hands over to another whose initials are each one farther on in the alphabet than his own, so AB would hand over to BC. Of course I is followed by A.

    The shifts are picked so that when all shifts have been worked, every prole has been on duty once only.

    Now you tell me who’s on the first shift. I want the biggest possible list. Easier than it looks. Usual prize. One pint.

    Which A proles, if any, are on the first shift?

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

    [teaser443]

     
    • Jim Randell's avatar

      Jim Randell 4:48 pm on 2 June 2024 Permalink | Reply

      A manual solution follows from a bit of analysis:

      There are C(9, 2) = 36 names, and 4 officers, so there are 32 proles, which we must form into shifts, and we want the shifts to be as large as possible (and more than 2 of them).

      So we are looking at:

      4 shifts, each of 8 proles; or:
      8 shifts, each of 4 proles

      If we start with a name we can consider the successor names until we get back to where we started, which forms the names into 4 groups:

      AB → BC → CD → DE → EF → FG → GH → HI → AI (→ AB) [adjacent letters]
      AC → BD → CE → DF → EG → FH → GI → AH → BI (→ AC) [separated by 1 letter (or 6)]
      AD → BE → CF → DG → EH → FI → AG → BH → CI (→ AD) [separated by 2 letters (or 5)]
      AE → BF → CG → DH → EI → AF → BG → CH → DI (→ AE) [separated by 3 letters (or 4)]

      Any officer (in bold) must be preceded by a member of the final shift and working backwards gives us members of previous shifts, giving a chain that is 8 long, which consists of 1 member for each of 8 shifts, or 2 members for each of 4 shifts.

      Each officer must appear in a different group, and as we have used ABCD, we must choose 2 officers from EFGHI.

      In the final group only EI is available to be an officer, so the remaining officer must be FH in the second group.

      And so we can construct 4 shifts, each of 8 proles:

      shift 4: (AB, EG, CI, DH) + (FG, AC, EH, DI)
      shift 3: (AI, DF, BH, CG) + (EF, BI, DG, CH)
      shift 2: (HI, CE, AG, BF) + (DE, AH, CF, BG)
      shift 1: (GH, BD, FI, AE) + (CD, GI, BE, AF)

      (And the two halves could be split to make 8 shifts, each of 4 proles = (1b, 2b, 3b, 4b, 1a, 2a, 3a, 4a)).

      Solution: AE and AF are on the first shift.


      Here is a program that performs the same steps:

      It runs in 64ms. (Internal runtime is 351µs).

      Run: [ @replit ]

      from enigma import (
        irange, subsets, diff, tuples, repeat, join,
        intersect, disjoint_union, cache, printf
      )
      
      # letters
      letters = "ABCDEFGHI"
      
      # construct possible crew names
      names = list(x + y for (x, y) in subsets(letters, size=2))
      
      # constrict successor names
      adj = dict(tuples(letters, 2, circular=1))
      succ = cache(lambda xs: join((adj[x] for x in xs), sort=1))
      
      # group names into successor chains
      def group(names):
        while names:
          # make the chain for the next name
          g = list(repeat(succ, names[0], 8))
          yield g
          names = diff(names, g)
      
      # collect groups
      gs = list(group(names))
      
      # choose an officer from each group
      def officers(gs, offs=[]):
        # are we done?
        if not gs:
          yield offs
        else:
          g = gs[0]
          # AD and BC are officers
          xs = intersect([{'AD', 'BC'}, g])
          if not xs:
            # choose officers that don't include ABCD
            xs = list(x for x in g if not intersect(['ABCD', x]))
          # process the candidates
          for x in xs:
            offs_ = offs + [x]
            if disjoint_union(offs_):
              yield from officers(gs[1:], offs_)
      
      # choose the officers
      for offs in officers(gs):
        # find the first shift
        shift = set()
        for (g, off) in zip(gs, offs):
          j = g.index(off)
          shift.update(g[(j + i) % 9] for i in [-4, -8])
        shift = sorted(shift)
        # output shifts 1-4
        for k in irange(1, 4):
          printf("shift {k} = {shift}", shift=join(shift, sep=" "))
          if k == 4: break
          # hand on to the next shift
          shift = list(succ(x) for x in shift)
      

      Like

  • Unknown's avatar

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

    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 10:21 am on 2 May 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 553: Grand Vizier 

    From The Sunday Times, 6th February 1972 [link]

    Sever-Nek, the Grand Vizier, was suspicious of the influence which Azi-Muth. the Sultan’s wily tutor, was gaining over his young pupil, and had determined to have him removed, when chance played into his hands.

    The Sultan had a collection of nearly 2,000 small square tiles, all the same size in various colours, and one day to please his tutor had arranged some of them into a square with a pretty central design and a border. Then he announced that he had made two smaller equal squares using the same pieces. “But that is impossible, said Muth, “there must be some left over”.

    There was one over, and Sever-Nek, happening to pass by put his foot over it. “Surely not”, he said. “His Serene Highness has made a most interesting discovery. No doubt the same thing can be done again. Let us add some extra tiles from the box to those already used”. Nothing loath, the Sultan soon produced a larger square from which he almost made two smaller identical squares. This time he was one tile short.

    “Your Serene Highness must have dropped this one on the floor”, said the Grand Vizier, moving his foot. Allow me to complete the design”. The discomfiture of the outwitted tutor was no less complete than his disgrace.

    How many extra tiles were taken from the box to make the larger square?

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

    [teaser553]

     
    • Jim Randell's avatar

      Jim Randell 10:22 am on 2 May 2024 Permalink | Reply

      We can suppose the requirement for a “pretty central design” is to preclude very small squares. I assumed the initial square was at least 5×5.

      This Python program runs in 62ms. (Internal runtime is 399µs).

      Run: [ @replit ]

      from enigma import (irange, inf, sq, is_square, printf)
      
      # record (x, y) where x^2 = 2 y^2 + d for d in [+1, -1]
      ss = { +1: [], -1: [] }
      
      for j in irange(2, inf):
        n = sq(j)
        if n > 1000: break
      
        # look for deltas
        for d in ss.keys():
          i = is_square(2*n + d)
          if i:
            printf("[{d:+d}] {i}^2 = 2x {j}^2 {d:+d}")
            ss[d].append((i, j))
      
      # look for an initial +1, with a size at least 5
      for (x1, y1) in ss[+1]:
        if x1 < 5: continue
        # and a larger -1
        for (x2, y2) in ss[-1]:
          if not (x2 > x1): continue
          # calculate the number of extra tiles (remembering one is hidden)
          d = sq(x2) - sq(x1) + 1
          # output solution
          printf("{d} extra tiles [{x1}^2 = 2 {y1}^2 + 1 -> {x2}^2 = 2 {y2}^2 - 1]")
      

      Solution: 1393 extra tiles were taken to make the larger square.

      The initial square was 17×17 (= 289 tiles), which can be rearranged as two 12×12 squares (= 144 tiles each) with one tile left over (and hidden).

      With an extra 1393 tiles we get a total of 288 + 1393 = 1681 which can be arranged into a 41×41 square.

      The 1681 tiles can also be arranged into two 29×29 squares (= 841 tiles each), except one of them is one tile short, and completed by the hidden tile.


      If a small initial square is allowed then there is a further solution, starting with a 3×3 square (9 tiles), this is rearranged into two 2×2 squares (8 tiles each), with one tile left over. We can then add 41 extra tiles to make a 7×7 square which can split into two 5×5 squares, one of which requires the hidden tile. (Or an extra 1673 tiles could be added to make two 29×29 squares, as above).

      Like

    • John Crabtree's avatar

      John Crabtree 6:13 pm on 2 May 2024 Permalink | Reply

      The sides of the larger and smaller squares are given in the sequences
      https://oeis.org/A001333 Numerators of continued fraction convergents to sqrt(2), and
      https://oeis.org/A001329 Denominators of continued fraction convergents to sqrt(2).
      Then all one needs to do is to find the side of the biggest larger square < sqrt(2000), ie 41, and work from there.

      Like

  • Unknown's avatar

    Jim Randell 10:04 am on 7 April 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 157: Veres and Ficts 

    From The Sunday Times, 12th April 1964 [link]

    Since 1945, when the Veres (who always tell the truth) entered the country of the Ficts (who always lie), there has been a certain amount of intermarriage. So the country now contains four races: The Verifics (children of a Vere father and Fict mother), who first tell the truth and then lie; the Fivers (children of a Fict father and a Vere mother), who first lie and then tell the truth; the Veres (born of two Vere parents) who are always truthful; and the Ficts (born of two Fict parents), who are always untruthful.

    I met four children, one of each race, who told me some interesting things about their sixteen-year-old prince:

    Alan: “The prince is a Fict. My mother is a sister of the Prince’s cook”.
    Bruce: “The Prince’s father is of the same race as Orsino. The Prince’s tutor is Carl’s grandfather”.
    Carl: The Prince is of the same race as I am. I am a Verific”.
    David: “Orsino is the Prince’s tutor. The Prince’s cook is not of the same race as the Prince’s father”.

    What is the race of each child? And what is the race of the Prince?

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

    [teaser157]

     
    • Jim Randell's avatar

      Jim Randell 10:05 am on 7 April 2024 Permalink | Reply

      We are only told about offspring of V and F parents, so I assume there is only one generation that may have mixed parentage. (And this makes the father() and mother() functions idempotent).

      This Python program assigned the four tribes to A, B, C, D and then looks for possible tribes for the Prince, Orsino, the cook and the tutor, and the values of the three free propositions in the children’s statements, and then checks that the truth values of the statements match the assigned tribes.

      (The program assumes VF and FV’s alternate truths and falsehoods, although each statement we are given consists of exactly two clauses, so we don’t need to worry about the extended case).

      It runs in 94ms. (Internal runtime is 29ms).

      Run: [ @replit ]

      from enigma import (subsets, printf)
      
      # V - always tells the truth
      def V(ss): return all(ss)
      
      # F - always tells untruths
      def F(ss): return not any(ss)
      
      alternate= lambda X, Y, ss: X(ss[0::2]) and Y(ss[1::2])
      
      # VF - alternate true then false
      def VF(ss): return alternate(V, F, ss)
      
      # FV - alternate false then true
      def FV(ss): return alternate(F, V, ss)
      
      tribes = [V, F, VF, FV]
      
      # father of tribe X (idempotent)
      def father(X):
        if X is V or X is VF: return V
        if X is F or X is FV: return F
      
      # mother of tribe X (idempotent)
      def mother(X):
        if X is V or X is FV: return V
        if X is F or X is VF: return F
      
      # choose tribes for A, B, C, D
      for (A, B, C, D) in subsets(tribes, size=4, select="P"):
      
        # choose tribe for Orsino (O), Prince (P), Cook (Q), Tutor (T)
        for (O, P, Q, T) in subsets(tribes, size=4, select="M"):
      
          # and choose values for the following propositions:
          # p1 = "A's mother is sister of the cook"
          # p2 = "the tutor is C's grandfather"
          # p3 = "Orsino is the tutor"
          for (p1, p2, p3) in subsets([False, True], size=3, select="M"):
            # check tribes match statements
            if p1 and not (mother(A) is Q): continue
            if p2 and not (father(C) is T): continue
            if p3 and not (O is T): continue
      
            # check the statements:
            # A: "P is F; A's mother is sister of the cook
            if not A([P is F, p1]): continue
            # B: "father(P) = O; T is C's grandfather
            if not B([father(P) is O, p2]): continue
            # C: "P is C; C is VF"
            if not C([P is C, C is VF]): continue
            # D: "O is T; Q != father(P)"
            if not D([p3, father(P) is not Q]): continue
      
            # output solution
            printf(
              "A={A} B={B} C={C} D={D}; O={O} P={P} Q={Q} T={T}; p1={p1} p2={p2} p3={p3}",
              A=A.__name__, B=B.__name__, C=C.__name__, D=D.__name__,
              O=O.__name__, P=P.__name__, Q=Q.__name__, T=T.__name__,
            )
      

      Solution: Alan is a Fiver. Bruce is a Verific. Carl is a Fict. David is a Vere. The Prince is a Fiver.

      We have:

      A is FV
      B is VF
      C is F
      D is V

      Prince is FV
      Orsino is F
      the cook is V
      the tutor is F

      A’s mother is the sister of the cook.
      The tutor is not C’s grandfather.
      Orsino is the tutor.

      And so the statements are:

      A: “Prince (FV) is F” → false; “A’s mother (V) is sister of the cook (V)” → true; (false, true) → FV
      B: “Prince’s father (F) is same as Orsino (F) → true; “tutor is C’s grandfather” → false; (true, false) → VF
      C: “Prince (FV) is same as C (F)” → false; “C (F) is VF” → false; (false, false) → F
      D: “Orsino (F) is tutor” → true; “the cook (V) is not the same as Prince’s father (F)” → true; (true, true) → V

      Like

    • Frits's avatar

      Frits 11:06 am on 8 April 2024 Permalink | Reply

      SubstitutedExpression version of Jim’s program.

      from enigma import SubstitutedExpression
       
      # V - always tells the truth
      def V(ss): return all(ss)
       
      # F - always tells untruths
      def F(ss): return not any(ss)
       
      alternate = lambda X, Y, ss: X(ss[0::2]) and Y(ss[1::2])
       
      # VF - alternate true then false
      def VF(ss): return alternate(V, F, ss)
       
      # FV - alternate false then true
      def FV(ss): return alternate(F, V, ss)
       
      tribes = [V, F, VF, FV]
      race = ["Vere", "Fict", "Verific", "Fiver"]
      d = dict(zip(tribes, race))
       
      # father of tribe X (idempotent)
      def father(X):
        if X is V or X is VF: return V
        if X is F or X is FV: return F
       
      # mother of tribe X (idempotent)
      def mother(X):
        if X is V or X is FV: return V
        if X is F or X is VF: return F
      
      # invalid digit / symbol assignments
      d2i = dict()
      for dgt in range(4):
        vs = set() 
        if dgt > 1: vs.update('XYZ')
        d2i[dgt] = vs   
      
      # return corresponding function for values 0-3
      fn = lambda a: V if not a else F if a == 1 else VF if a == 2 else FV
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          # V, F, VF, FV = 0, 1, 2, 3
          
          # choose tribes for A, B, C, D
          # choose tribe for Orsino (O), Prince (P), Cook (Q), Tutor (T)
          # and choose values for the following propositions:
          # X = "A's mother is sister of the cook"
          # Y = "the tutor is C's grandfather"
          # Z = "Orsino is the tutor"
          
          # check tribes match statements
          "not X or (mother(fn(A)) is fn(Q))",
          "not Y or (father(fn(C)) is fn(T))",
          "not Z or (O is T)",
      
          # check the statements:
          # A: "P is F; A's mother is sister of the cook
          "fn(A)([P == 1, X])",
          # B: "father(P) = O; T is C's grandfather
          "fn(B)([father(fn(P)) is fn(O), Y])",
          # C: "P is C; C is VF"
          "fn(C)([P == C, C == 2])",
          # D: "O is T; Q != father(P)"
          "fn(D)([Z, father(fn(P)) is not fn(Q)])",
        ],
        answer="(fn(A), fn(B), fn(C), fn(D)), fn(P)",
        base=4,
        d2i=d2i,
        distinct=("ABCD"),
        env=dict(mother=mother, father=father, fn=fn),
        verbose=0,    # use 256 to see the generated code
      ) 
      
      # print answers
      for ans in p.answers():
        print(f"Children: {', '.join(d[a] for a in ans[0])}   Prince: {d[ans[1]]}")     
      

      Like

  • Unknown's avatar

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

    Brain-Teaser 351: Birthday party 

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

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

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

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

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

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

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

    [teaser351]

     
    • Jim Randell's avatar

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

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

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

      Run: [ @replit ]

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

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


      There are only three viable candidate lists:

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

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

      So, at the first event we have:

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

      And at the second event:

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

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

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

      Like

    • Frits's avatar

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

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

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

      Like

      • Jim Randell's avatar

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

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

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

        Run: [ @replit ]

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

        Like

      • Frits's avatar

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

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

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

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

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

        Like

        • Jim Randell's avatar

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

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

          Fortunately it doesn’t remove the single candidate solution.

          Like

      • Frits's avatar

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

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

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

        Like

        • Jim Randell's avatar

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

          Starting from the squares is an even better idea.

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

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

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

          Then by summing the equations we get:

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

          And given the squares we can recover the ages:

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

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

          Run: [ @replit ]

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

          Like

  • Unknown's avatar

    Jim Randell 8:57 am on 16 November 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 422: Telephone number 

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

    Fred was an exasperating person; he would never give a straight answer to a straight question. The other day I was seeing him off at a bus stop and he said, just as the bus was coming, “Give me a ring some time”.

    “I will”, I said, and asked him for his telephone number.

    “It has five digits”, he replied, and the first two are the same as my wife’s age while the last two are my age”.

    “But I don’t know how old you are”, I said, “except that you are less than 70”.

    “There’s no need to be rude”, he said. “I am older than my wile by as many years as our son’s age”.

    “What has he got to do with your telephone number?”, I asked.

    “Oh”, he replied, “his age is the middle digit”.

    “That doesn’t help much”, I said.

    “It’s divisible by 259, he shouted”, as the bus whisked him away.

    “That’s a great help”, I shouted back, and it really was.

    What was Fred’s telephone number?

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

    [teaser422]

     
    • Jim Randell's avatar

      Jim Randell 8:57 am on 16 November 2023 Permalink | Reply

      This puzzle is straightforward to solve programatically.

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

      It runs in 84ms. (Internal runtime of the generated program is 11ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --invalid="0,ACD"
      --distinct=""
      
      # Fred is less than 70
      "DE < 70"
      
      # Fred is older than his wife by as many years as his son's age
      "AB + C = DE"
      
      # The phone number is divisible by 259
      "ABCDE % 259 = 0"
      
      --answer="ABCDE"
      

      Solution: Fred’s telephone number is 35742.

      And we have:

      35 + 7 = 42
      35742 = 138 × 259

      Like

    • GeoffR's avatar

      GeoffR 2:51 pm on 16 November 2023 Permalink | Reply

      for H in range(15, 70):
        for W in range(15, H):
          if H - W > 9:
            continue
          for S in range(1, 10):
            # Son's age = Husband's age - Wife's age 
            if H - W == S:
              # Son's age is the single middle digit of the telephone number
              tel_num = int(str(W) + str(S) + str(H))
              if tel_num % 259 == 0:
                print(f"Fred's telephone number was {tel_num}.")
      
      # Fred's telephone number was 35742.
      
      

      If the telephone number was not divisible by 259, I found 450 solutions.

      Like

  • Unknown's avatar

    Jim Randell 5:21 pm on 6 August 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 606: Pints all round 

    From The Sunday Times, 18th February 1973 [link]

    “Little puzzle here”, says Bell at the pub, squeezing himself in. “Move up. Now, we are seven sat round the table. I am No. 1, naturally, and you are 2, 3, up to 7 and back to No. 1 clockwise”.

    “These cards numbered 1 to 7. I shuffle and deal. If you have your own number, say ‘Hit!’. Only one hit? Good! Pass the cards to your left one place. Double hit? Bad”.

    “The trick is to find out how the cards should be dealt for a single hit each time the cards are passed. I’ll make it easy. I always get the first hit and then numbers 2 and 6 get their hits as early as possible after that. Everything’s clockwise, numbering, dealing and passing”.

    “Usual prize one pint”.

    “Difficult? It’s a pushover. If you’re thirsty I’ll give you a hint. Let’s have a look at your cards. Anybody can see number 6 is going to come up last. Swap them round a bit. Hold on! Now you’ve got 5 then 2 then 7, so when 5 gets a hit so does 7. You’ve got to avoid that kind of thing”.

    In what order should the cards be dealt, beginning with No. 1 and proceeding clockwise?

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

    [teaser606]

     
    • Jim Randell's avatar

      Jim Randell 5:22 pm on 6 August 2023 Permalink | Reply

      This Python program considers all possible arrangements of cards, and runs the process looking for a single hit on each turn. And it reports those with 7 turns that each give a single hits, and the first hit is 1. And I look for cases where the index sum of cards 2 and 6 in minimised.

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

      Run: [ @replit ]

      from enigma import (Accumulator, irange, rotate, subsets, singleton, printf)
      
      # find sequences of single hits
      def hits(ss):
        hs = list()
        while True:
          # we want a single hit on each turn
          h = singleton(x for (x, y) in enumerate(ss, start=1) if x == y)
          if h is None: break
          hs.append(h)
          if len(hs) == len(ss): break
          # pass the cards to the left
          ss = rotate(ss, -1)
        # return the order of the hits
        return hs
      
      r = Accumulator(fn=min, collect=1)
      
      # consider possible arrangements of the cards
      for ss in subsets(irange(1, 7), size=len, select='P'):
        hs = hits(ss)
        # single hit each time
        if not (len(hs) == 7): continue
        # the first hit is 1
        if not (hs[0] == 1): continue
        # accumulate values by positions for 2 and 6
        r.accumulate_data(hs.index(2) + hs.index(6), ss)
      
      # output solution
      for ss in r.data:
        printf("{ss}")
      

      Solution: The cards should be dealt in the order: 1, 5, 7, 3, 6, 4, 2.

      Like

  • Unknown's avatar

    Jim Randell 12:31 pm on 16 July 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 207: Prime flats 

    From The Sunday Times, 11th April 1965 [link]

    The number of families living on one floor in a block of flats is prime. Each family consists of a pair of parents and at least one child; the number of people in each family and the total number of people in all families are prime numbers. Every one had recently had a birthday anniversary, and each person’s age is a prime number. No one is older than 40, and every parent was at least 23 before their first child was born. Each of the prime numbers described above occurs exactly twice.

    The sum of the ages of each family is a prime number, and a different one in each case. The sum of the ages of  everyone is a prime number.

    What are the ages of the people in each family?

    [Note: In this puzzle the number 1 is counted as a prime number].

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

    [teaser207]

     
    • Jim Randell's avatar

      Jim Randell 12:32 pm on 16 July 2023 Permalink | Reply

      Parent’s ages are primes in the interval [24, 40], so there are only 3 possibilities: 29, 31, 37.

      And children’s ages can be 1 or a prime, in the interval [1, 14], so there are only 7 possibilities: 1, 2, 3, 5, 7, 11, 13.

      As each prime can only appear twice there cannot be more than 6 parents, i.e. 3 families, and as the number of families is prime it must be 2 or 3. However, if there were 2 families each with an age sum that was prime, then combining their ages would give an even (non-prime) number. So there must be 3 families.

      (For a unique solution it is necessary to exclude the possibility of the number of families being 1. This can be done by noting that the families are referred to in the puzzle text as plural, although I don’t really consider this to be strong enough. So it may have been better to specifically note that there is more than one family, or to specifically allow 1 to be used only as a child’s age).

      This Python program generates possible family groups, where the parents ages are chosen from those given above, and the number of children is chosen such that the total number if the family group is prime. Ages are then chosen for the children to give a prime value for total sum of the ages in the family. And the viable family groups are collected together by total age.

      We then choose a collection of different total ages for the family groups, such that the combined age of all groups is prime, and the populate the groups with actual ages, such that when all the specified primes are collected together each is used exactly twice.

      The program runs in 665ms.

      Run: [ @replit ]

      from enigma import (multiset, subsets, is_prime, group, item, printf)
      
      # available ages for parents and children
      aps = multiset.from_seq([29, 31, 37], count=2)
      aks = multiset.from_seq([1, 2, 3, 5, 7, 11, 13], count=2)
      
      # generate possible family groups
      def generate():
        # choose the ages for the parents
        for ps in subsets(aps, size=2, select='mC'):
          tp = sum(ps)
          # children are at least 23 years younger than the youngest parent
          m = min(ps) - 22
          aks_ = aks.restrict(x for x in aks.keys() if x < m)
          # choose k = the number of children (such that k + 2 is prime)
          for k in (1, 3, 5, 9, 11):
            # choose the ages of the children
            for ks in subsets(aks_, size=k, select='mC'):
              # total of ages in the family
              t = tp + sum(ks)
              if is_prime(t):
                yield (t, ps + ks)
      
      # group families by total age
      fams = group(generate(), by=item(0), f=item(1))
      
      # choose family groups with total ages in <ts>
      # fs = families chosen so far
      # vs = ages used so far
      def choose(ts, fs=[], vs=multiset()):
        if not ts:
          # total number of people is prime
          P = vs.size()
          if not is_prime(P): return
          # check primes in the required set appear twice
          # number of families, total number of people, size of each family
          m = vs.combine((len(fs), P), map(len, fs))
          if not m.all_same(2): return
          # return the families
          yield fs
        else:
          # choose a family with total age <t>
          for f in fams[ts[0]]:
            vs_ = vs.combine(f)
            if not vs_.is_duplicate(2):
              yield from choose(ts[1:], fs + [f], vs_)
      
      # the number of families (must be 3)
      n = 3
      # choose the total ages for each family
      for ts in subsets(fams.keys(), size=n, fn=list):
        # total sum of all ages is prime
        T = sum(ts)
        if not is_prime(T): continue
        # choose the family groups from the ages
        ts.sort(reverse=1)
        for fs in choose(ts):
          # output solution
          printf("{n}: {ts}; T={T} -> {fs}")
      

      Solution: The ages in the family groups are: (37, 37; 13, 7, 7), (31, 31; 2, 2, 1), (29, 29; 1).

      Like

      • Frits's avatar

        Frits 5:45 pm on 16 July 2023 Permalink | Reply

        You can argue that there is no solution.

        “Each of the prime numbers described above occurs exactly twice”.

        The total number of people in all families only occurs once.

        Like

        • Jim Randell's avatar

          Jim Randell 5:52 pm on 16 July 2023 Permalink | Reply

          @Frits: There are 13 people in total, and one of the children is aged 13, so the prime number 13 occurs twice.

          Like

          • Frits's avatar

            Frits 6:13 pm on 16 July 2023 Permalink | Reply

            @Jim, Thanks, I had read it as total number of all ages (227).

            I wonder if I can use SubstitutedExpression() as it is not immediately clear what limit to use for the number of children per family (at least <= 12).

            Like

          • Frits's avatar

            Frits 1:41 pm on 17 July 2023 Permalink | Reply

            A SubstitutedExpression() solution using all letters A-Z takes 1 second with PyPy. As I now tend to use assignment statements with the reorder=0 option the denest option often can’t be used anymore as assignments are not transfered to another nested level.

            A quicker version could be made by further analysis.
            The total number of children must be 7 as 5 fails ((1, 1, 3) results in three 3’s) and 11 fails as well (out of 14 candidates we already lose 4 (number of families and three number of family members). The seven children must occur in a (1, 3, 3) distribution.

            Like

  • Unknown's avatar

    Jim Randell 9:17 am on 2 July 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 438: Electric clock 

    From The Sunday Times, 28th September 1969 [link]

    My watch keeps perfect time and so does my electric wall clock until the battery starts to run down. I check the clock every noon, and when I find that it has lost a minute in 24 hours I know that it will slow down by an additional minute each day until the battery is exhausted.

    At noon on a day towards the end of August, when I was preparing for a fortnight’s holiday, I noticed that the clock was one minute slow but. I forgot to do anything about it. As I hurried off to catch my train, at noon on 1st September, I saw that the clock was exactly 21 minutes slow. I put it right and left it at that.

    Family matters forced me to return earlier than I had expected and on my arrival home I found that the clock was still going. I then noticed that the hands of my watch were exactly overlapping, while the hands of the clock were almost so.

    It took me only a minute to fix a new battery, and I at once put the clock on the correct time. In doing so the hands crossed each other three times.

    On what date did I return from my holiday?

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

    [teaser438]

     
    • Jim Randell's avatar

      Jim Randell 9:18 am on 2 July 2023 Permalink | Reply

      The clock is 21 minutes slow on 1st Sep, and as 21 is the 6th triangular number it must be progressing as follows (minutes slow at noon on the specified date):

      27th Aug: 1 min (+1)
      28th Aug: 3 min (+2)
      29th Aug: 6 min (+3)
      30th Aug: 10 min (+4)
      31st Aug: 15 min (+5)
      1st Sep: 21 min (+6), corrected to 0 min
      2nd Sep: 7 min (+7)
      3rd Sep: 15 min (+8)
      4th Sep: 24 min (+9)
      5th Sep: 34 min (+10)
      6th Sep: 45 min (+11)
      7th Sep: 57 min (+12)
      8th Sep: 70 min (+13)
      9th Sep: 85 min (+14)
      10th Sep: 99 min (+15)
      11th Sep: 115 min (+16)
      12th Sep: 132 min (+17)
      13th Sep: 150 min (+18)
      14th Sep: 169 min (+19), expected return

      A 12 hour clock has overlapping hands at 12:00, and then every 12/11 hours later.

      gap = 12/11 hours = 65m27.27s

      So we can check times that are multiples of this gap (up to 14 days), and look for times when the hands on the wall clock are separated by a small angle, and it would require 3 crossings of the hands to set to clock to the correct time.

      As the clock is set to a time just after the hands have crossed the amount the clock has lost must be greater than 131 minutes. So the earliest possible date of return is 12th Sep. And a return on 14th Sep wouldn’t be an early return, so the answer must be 12th Sep or 13th Sep.

      This Python program starts by constructing a continuous function to give the amount the clock is slow at any particular time, and it does this by using polynomial interpolation of the times given above at noon on the 1st to 14th Sep. (And as we might expect, that function turns out to be a quadratic polynomial).

      It then looks at times when the hands of a correct clock are overlapping, and calculates the angular distance between the hands on the slow clock. If the hand are less than 10° apart, and setting the clock right requires crossing the hands exactly 3 times, then we have found a solution.

      It runs in 66ms. (Internal runtime is 2.4ms).

      Run: [ @replit ]

      from enigma import (Rational, Polynomial, irange, mdivmod, divc, sprintf, printf)
      
      Q = Rational()
      
      # after h hours, loss (in minutes) is t
      h = t = 0
      ps = [(h, t)]
      for i in irange(7, 19):
        h += 24
        t += i
        # remove (<time>, <loss>) (both in hours)
        ps.append((h, Q(t, 60)))
      
      # p: hour -> hours lost
      p = Polynomial.interpolate(ps)
      printf("[p(x) = {p}]")
      
      # calculate hand positions from time in hours (between 0 and 1)
      def pos(t):
        (z, h, m) = mdivmod(t, 12, 1)
        return (Q(h + m, 12), m)
      
      # angular distance between hand positions (between 0 and 1)
      def ang(t):
        (h, m) = pos(t)
        d = abs(h - m)
        return min(d, 1.0 - d)
      
      # format a time (in hours)
      def fmt(t):
        (d, h, m) = mdivmod(t, 24, 1)
        return sprintf("{d:d}+{h:02d}:{m:05.2f}", m=float(m * 60))
      
      # gap between times of successive overlapping hands
      g = Q(12, 11)
      
      # continue forward from noon on 1st Sep in 12/11 hour increments
      for i in irange(1, 11 * 28):
        # actual elapsed time (hours)
        h = i * g
        # time lost (hours)
        x = p(h)
        # calculate the number of crossings in setting the clock to the correct time
        k = divc(x, g)
        if k != 3: continue
        # clock elapsed time (hours)
        t = h - x
        # calculate the angular distance between the hands
        d = ang(t)
        # look for distances less than 10 degrees
        if not (0 < 360 * d < 10): continue
        # output solution
        printf("[i={i}] {h} -> {t}; {d:.2f} deg; {k} crossings", h=fmt(h), t=fmt(t), d=float(360 * d))
      

      Solution: You returned home on 12th September.

      The polynomial that gives the time lost (in hours) from the time of departure (noon on 1st Sep) is:

      p(x) = (1/69120)x^2 + (13/2880)x

      If the setter returned home at noon on 12th Sep, then the clock would be 132 minutes slow, so it would be showing 9:48am. And the hands are 6° apart, and the minute hand is slightly behind the hour hand.

      The battery is replaced and the clock is set to 12:01pm, which involves the hands crossing at: just before 10, just before 11, 12. So the hands cross 3 times.

      And this is the solution given in the book.

      However the program finds a “better” solution.

      If the setter were to return at 10:54am on 12th Sep (i.e. the previous time the hands on the watch coincide), then the clock would be displaying 8:43am, and the hands are just 1.6° apart, with the minute hand slightly behind the hour hand.

      The battery is replaced and the clock set to 10:55am. So the hands cross at: just before 9, just before 10, just before 11. So the hands cross 3 times, and they started out much closer together than the given solution.

      I also think that the wording of the puzzle implies that setter returned home at an overlapping time other than noon.

      However it doesn’t change the date that the setter returned, so the answer is the same in both cases.

      Like

  • Unknown's avatar

    Jim Randell 10:08 am on 25 June 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 346: Antics 

    From The Sunday Times, 24th December 1967 [link]

    All distances and dimensions are exact feet; all times, exact seconds; all the spiders run at 5 feet per second; and drop with a maximum fall of 30 feet.

    Ara and Chne sat hungry in the top north-west corner of the palace corridor (no flies).

    “Could be and ant or two in that bottom south-east corner by the garden door”, said Chne.

    “True”, said Ara. She dropped to the floor and headed straight for the prospective meal, while Chne (she never drops) instantly set off down the shortest route via the north wall and floor.

    Farther along the corridor, Taran and Tula sat hungry together at the top of the south wall.

    “Hey, look!”, cried Taran, as the ant-hunters approached.

    “Must be something in that corner”, said Tula, dropping to the floor and speeding straight toward it.

    Taran at the same moment ran direct for the corner. As she started, Ara, clocking 39 seconds, passed by.

    Tangle and wrangle! Dead heat all! No ants!

    How wide is the corridor?

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

    [teaser346]

     
    • Jim Randell's avatar

      Jim Randell 10:09 am on 25 June 2023 Permalink | Reply

      By folding the walls of the corridor flat we can turn all of the paths into straight lines in the same plane.

      Measuring all distances in feet, and times in “ticks” (= 1/5 second), then each spider travels 1 ft per tick.

      We don’t know how long it takes a spider to drop the height of the corridor, but this is calculated as part of the solution.

      The following Python program runs in 58ms. (Internal runtime is 859µs).

      Run: [ @replit ]

      from enigma import (pythagorean_triples, sum_of_squares, is_square, printf)
      
      # consider the path Taran takes, it is the hypotenuse of a Pythagorean triple
      for (a, b, t2) in pythagorean_triples(999):
        # but must be divisible by 5
        if t2 % 5 > 0: continue
        # and the height of the corridor must be no more than 30
        for (h, t1) in [(a, b), (b, a)]:
          # and Tula's path must also be a multiple of 5
          if h > 30 or t1 % 5 > 0: continue
          # and their times are equal, so we can calculate drop time (in 1/5 s)
          d = t2 - t1
      
          # total time for Ara and Chne is 195 ticks more than for t2
          t = t2 + 195
          # and this is Chne's distance, the hypotenuse of a (w + h, l, t) triangle
          for (x, y) in sum_of_squares(t * t, min_v=1):
            for (wh, l) in [(x, y), (y, x)]:
              w = wh - h
              if w < 0 or not (l > t1): continue
              # Ara's time is the same as Chne's
              z = is_square(w * w + l * l)
              if z is None or z + d != t: continue
      
              # output solution
              printf("h={h} t1={t1} t2={t2}; d={d} t={t}; w={w} l={l} z={z}")
      

      Solution: The corridor is 39 ft wide.

      And 25 ft high, and 252 ft long.

      Chne takes a direct route (across the N wall and floor) so travels hypot(39 + 25, 252) = 260 ft (in 52s).

      Ara drops to the floor (which takes 1s) and travels hypot(39, 252) = 255 ft (in 51s).

      At the 39s mark Taran and Tula set off on their journeys.

      Taran crosses the S wall diagonally for 13s, so travels a distance of 65 ft.

      Tula drops to the floor (1s) and travels for 12s a distance of 60 ft.

      And so they all arrive in the SE bottom corner at exactly the same time.

      Like

  • Unknown's avatar

    Jim Randell 2:38 pm on 18 June 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 433: Hymn numbers 

    From The Sunday Times, 24th August 1969 [link]

    The hymn-board in church was electrically operated and made provision for any four hymn numbers of up to three digits in each (from 0 to 9), but did not provide blanks so that Hymn No. 7 for example would appear on the board as 007, and No. 77 as 077.

    One Sunday a parishioner noticed with surprise that not only was each of the four hymn numbers for that service a perfect square, but moreover each of the three numbers produced in the vertical columns was a 4-digit perfect square.

    When, back home, he told his son Tom of this unique occurrence, the latter exclaimed: “I can’t believe it! What were the numbers then?”, to which the father replied: “You already know enough to work that out for yourself, but it will be of help to you to learn that I also noticed that the totals of the digits of each hymn number were perfect squares too, and that, coincidentally, one of these squares would result from dividing the final hymn number into the number formed by the middle column”.

    But Tom still couldn’t puzzle it out and demanded to be told at any rate one of the hymn numbers, to which the father responded, “All I can remember now is that the third hymn number ended in two noughts”.

    What was the opening hymn number?

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

    [teaser433]

     
    • Jim Randell's avatar

      Jim Randell 2:39 pm on 18 June 2023 Permalink | Reply

      I used the [[ SubstitutedExpression ]] solver from the enigma.py library to solve this puzzle.

      The following run file executes in 80ms. (Internal runtime of the generated code is 8ms).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      #  A B C
      #  D E F
      #  G H I
      #  J K L
      
      --distinct=""
      --invalid="0,ABC" # columns form 4-digit squares
      
      # hymns are different
      "all_different(ABC, DEF, GHI, JKL)"
      
      # rows form squares
      "is_square(ABC)"
      "is_square(DEF)"
      "is_square(GHI)"
      "is_square(JKL)"
      
      # columns form squares
      "is_square(ADGJ)"
      "is_square(BEHK)"
      "is_square(CFIL)"
      
      # row sums form squares
      "is_square(A + B + C)"
      "is_square(D + E + F)"
      "is_square(G + H + I)"
      "is_square(J + K + L)"
      
      # 3rd row is x00
      --assign="H,0"
      --assign="I,0"
      
      # 4th row divides into middle column, to give one of the row sums
      "div(BEHK, JKL) in {A + B + C, D + E + F, G + H + I, J + K + L}"
      
      # [optional] just show the hymn numbers
      --template="ABC DEF GHI JKL"
      --solution=""
      

      Solution: The opening hymn number was: 529.

      The hymns are: 529, 36, 400, 144.

      We have:

      rows
      529 = 23²; 5 + 2 + 9 = 16 = 4²
      036 = 6²; 0 + 3 + 6 = 9 = 3²
      400 = 20²; 4 + 0 + 0 = 4 = 2²
      144 = 12² ; 1 + 4 + 4 = 9 = 3²

      columns
      5041 = 71²
      2304 = 48²
      9604 = 98²

      2304 / 144 = 16 = 4² = 5 + 2 + 9

      And we do indeed get a single solution if we just keep the requirements that the rows and columns form squares (lines 16 – 25).

      Like

  • Unknown's avatar

    Jim Randell 11:09 am on 11 June 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 276: Electing a Chairman 

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

    The ten directors of the Everlasting Mutual Trust Company, Chairman (C), Secretary (S), Treasurer (T), Directors 1, 2, 3, 4, 5, 6, and Vice-Chairman (V), sat in that order to elect a new chairman.

    They all got one vote. No director voted for himself, or for his neighbour, or for the man who voted for him. No officer voted for an officer. C voted for the man who voted for the man who voted for 2, i.e. C vote vote vote 2 for short; and T vote vote vote 4, and V vote vote vote 5, and S vote vote vote 6. Director 4 did not vote for an officer. Neither C nor S voted for 3. The man getting a vote from 6 did not vote for 3.

    For whom did C, S, T, 1, 2, 3, 4, 5, 6, V respectively vote?

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

    [teaser276]

     
    • Jim Randell's avatar

      Jim Randell 11:09 am on 11 June 2023 Permalink | Reply

      Presumably each of them cast one vote, and received one vote.

      We can write “X vote vote vote Y” even more compactly as “X vote^3 Y”.

      This Python 3 program generates possible voting patterns, taking direct voting restrictions into account, and then checks the restrictions on voting chains.

      In Python 3.7+ dict() objects remember their insertion order, so the output should be in the required order.

      It runs in 111ms. (Internal runtime is 52ms).

      Run: [ @replit ]

      from enigma import (tuples, remove, update, map2str, printf)
      
      # multiple lookups on dict <d>
      def nget(d, k, n):
        while n > 0:
          k = d[k]
          n -= 1
        return k
      
      # the voters
      voters = "CST123456V"
      
      # determine invalid votes:
      invalid = dict((k, set()) for k in voters)
      # no-one votes for themselves
      for k in voters:
        invalid[k].add(k)
      # no-one votes for their neighbours
      for (x, y, z) in tuples(voters, 3, circular=1):
        invalid[y].update((x, z))
      # officers (CSTV) don't vote for other officers
      officers = "CSTV"
      for k in officers:
        invalid[k].update(officers)
      
      # generate possible votes
      # return <m>, map of <voter> -> <vote>
      def votes(ks, vs, m=dict()):
        # are we done?
        if not ks:
          yield m
        else:
          k = ks[0]
          xs = invalid[k]
          for v in vs:
            # reject invalid votes and reciprocal votes
            if v not in xs and (v not in m or m[v] != k):
              yield from votes(ks[1:], remove(vs, v), update(m, [(k, v)]))
      
      # 4 did not vote for an officer
      invalid['4'].update(officers)
      # neither C nor S voted for 3
      invalid['C'].add('3')
      invalid['S'].add('3')
      
      # who votes for who?
      for v in votes(voters, set(voters)):
        # check vote chains:
        # the man getting a vote from 6 did not vote for 3
        if nget(v, '6', 2) == '3': continue
        # C vote^3 2
        if nget(v, 'C', 3) != '2': continue
        # T vote^3 4
        if nget(v, 'T', 3) != '4': continue
        # V vote^3 5
        if nget(v, 'V', 3) != '5': continue
        # S vote^3 6
        if nget(v, 'S', 3) != '6': continue
        # output solution
        printf("{v}", v=map2str(v, sort=0))
      

      Solution: The votes are: C→6, S→2, T→3, 1→5, 2→C, 3→V, 4→1, 5→T, 6→S, V→4.

      Or as cycles:

      C → 6 → S → 2 (→ C)
      T → 3 → V → 4 → 1 → 5 (→ T)

      Like

    • Frits's avatar

      Frits 4:35 pm on 11 June 2023 Permalink | Reply

       
      from enigma import SubstitutedExpression
      
      # invalid digit / symbol assignments
      d2i = dict()
      for d in range(0, 10):
        vs = set()
        # no officer voted for an officer
        # director 4 did not vote for an officer 
        
        if d < 1 or d > 6: vs.update('NCSTV')
        else:  # 1 <= d <= 6
          # no director voted for himself or for his neighbour ..
          vs.update("KLMNOP"[max(0, d - 2):d + 1])
        
        if d == 0:
          vs.update("K")
        
        if d == 7:
          vs.update("P")
        
        # neither C nor S voted for 3
        if d == 3:
          vs.update("CS")   
        
        d2i[d] = vs
      
      # return index of person in sequence {seq> who voted <n>
      voted = lambda seq, n: seq.index(n)
      # return the man who voted for the man who voted for <n>
      vote3 = lambda seq, n: voted(seq, voted(seq, n))
      
      # officers CSTV and directors KLMNOP
      # VKLMNOPCST
      # 0123456789
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
         # choose assignment to T as C and S have fewer candidate values
         # T vote^3 4
         "vote3([V, K, L, M, N, O, P, C, S, -1], 4) = T",
      
         # the man getting a vote from 6 did not vote for 3
         "(r := [V, K, L, M, N, O, P, C, S, T])[P] != 3",
         
         # C vote^3 2 (C voted for the man who voted for the man who voted for 2)
         "vote3(r, 2) == C",
         # V vote^3 5
         "vote3(r, 5) == V",
         # S vote^3 6
         "vote3(r, 6) == S",
         
         # no director voted for the man who voted for him
         "all(x != voted(r, i) for i, x in enumerate([K, L, M, N, O, P], 1))",
        ],
        answer="[C, S, T, K, L, M, N, O, P, V]",
        d2i=d2i,
        env=dict(vote3=vote3, voted=voted),
        reorder=0,
        verbose=0,    # use 256 to see the generated code
      )
      
      # print answers
      for (_, ans) in p.solve():
        print(f"answer: {ans}")
      

      Like

  • Unknown's avatar

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

    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:23 am on 21 May 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 386: South Pacific 

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

    Among those innumerable South Pacific islands in which the vicinity of longitude 180° abounds, there are three neighbouring ones between which an ancient ferryboat maintains each week a regular schedule. This schedule, which is strictly adhered to except when ordered otherwise, is:

    ALOHA: dep. Monday 9 a.m.
    BALI-HAI: arr. Tuesday 7.p.m.
    BALI-HAI: dep. Tuesday 10 p.m.
    CRACKATOEA: arr. Friday 9 p.m.
    CRACKATOEA: dep. Saturday 7 a.m.
    ALOHA: arr. Sunday 7 p.m.

    The ferry maintains the same leisurely speed throughout each leg of the triangular circuit.

    To Port Authority at the home port of Aloha, one Thursday at 9 p.m, radioed to the ferry on its course between the other two islands that a hurricane in the area was imminent, and ordered it to proceed at once to the nearest of the three for shelter.

    Without altering speed, the captain immediately complied.

    Which island did he accordingly reach, and what were the day and hour of his arrival there?

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

    [teaser386]

     
    • Jim Randell's avatar

      Jim Randell 9:23 am on 21 May 2023 Permalink | Reply

      The boat travels at a steady speed, so we can equate journey times with distances, and I think we are to assume it travels directly between the islands in straight line distances.

      So the first problem is that the apparent distances between islands (34h, 71h, 36h) do not form a triangle.

      But as we are “in the vicinity of longitude 180°”, it could be the case that the islands straddle the International Date Line, and so some of them could have local times that are 24 hours ahead of the others, and the times mentioned are local to the corresponding islands.

      (I suppose potentially there could be more than 2 timezones involved, and the timezones do not necessarily differ by 24 hours, but I think that would not lead to a unique solution. And I also suppose that the puzzle doesn’t mention that all times are local times so that it doesn’t give away any clues to the solution).

      This Python program considers possible collections of the 3 islands that are 24 hours ahead of the remaining islands, and looks to see if the actual elapsed journey times can form a triangle. If so, we check that a call made at 93 hours after midnight on Monday (local to A), would be received by the boat while it is on the B→C journey, and if so calculates which of the islands it is closest to, and the time it would arrive.

      Run: [ @replit ]

      from enigma import (empty, call, subsets, fdiv, sq, sqrt, divf, sprintf, seq2str, map2str, printf)
      
      # check the side lengths form a triangle
      def is_triangle(a, b, c):
        return all([0 < a < b + c, 0 < b < a + c, 0 < c < a + b])
      
      # adjust a time = what time is it in <x> when in <y> it is <t>?
      def adjust(x, y, t, ahead):
        if x not in ahead and y in ahead: return t - 24
        if x in ahead and y not in ahead: return t + 24
        return t
      
      # local time, Mon 12am + <h> hours
      days = "Mon Tue Wed Thu Fri Sat Sun".split()
      def time(h):
        d = divf(h, 24)
        h -= d * 24
        return sprintf("{d} {h:.4g}h", d=days[d % 7])
      
      # calculate time direct to A from BC leg, given time out from B
      def timeA(elapsed, tB):
        (AB, BC, CA) = (elapsed[k] for k in ('AB', 'BC', 'CA'))
        # cosine rule
        return sqrt(sq(tB) + sq(AB) - tB * fdiv(sq(AB) + sq(BC) - sq(CA), BC))
      
      # solve the puzzle where specified ports are +24 hours ahead of the others
      def solve(times, ahead=empty):
        # calculate the actual elapsed times
        elapsed = dict()
        for (k, t) in times.items():
          (x, y) = k
          elapsed[k] = adjust(x, y, t, ahead)
        # check the elapsed times form a triangle
        if not call(is_triangle, elapsed.values()): return
      
        # the call is made at 93h/A
        # at which time the boat is on the BC leg (46h/B -> 117h/C)
        depB = adjust('A', 'B', 46, ahead)
        arrC = adjust('A', 'C', 117, ahead)
        (tB, tC) = (93 - depB, arrC - 93)
        if not (tB > 0 and tC > 0): return
        # calculate direct route to A
        tA = timeA(elapsed, tC)
        # make the shortest journey
        printf("[ahead = {ahead}, elapsed = {elapsed}]", ahead=seq2str(ahead), elapsed=map2str(elapsed))
        t = min(tA, tB, tC)
        ans = lambda x, t=t: ('= NEAREST' if x == t else '')
        # divert to A
        arr = 93 + tA
        printf("-> A = {tA:.4g}h, arrive {arr} (local) {x}", arr=time(arr), x=ans(tA))
        # return to B
        arr = adjust('B', 'A', 93, ahead) + tB
        printf("-> B = {tB:.4g}h, arrive {arr} (local) {x}", arr=time(arr), x=ans(tB))
        # continue to C as planned
        printf("-> C = {tC:.4g}h, arrive {arr} (local) {x}", arr=time(117), x=ans(tC))
        printf()
      
      # the apparent journey times (from the schedule)
      times = dict(AB=34, BC=71, CA=36)
      
      # consider possible islands with local time 24h ahead
      for ahead in subsets('ABC', min_size=0, max_size=2):
        solve(times, ahead)
      

      Solution: The boat reached Bali-Hai on Thursday, 8pm (local time).


      A and C are 24h ahead of B.

      So the corresponding elapsed journey times are: A→B = 58h; B→C = 47h; C→A = 36h.

      The call is made from A at 9pm on Thursday (A/C time), and received on the boat at at 9pm on Wednesday (B time).

      At this point it is 23 hours into the 47 hour journey, so B is 23 hours away and C is 24 hours away. (And A is about 42 hours away).

      The boat returns to B, and arrives at 8pm on Thursday (B time).

      If it continues to C, it would arrive 1 hour later at 9pm on Friday (C time) (as scheduled).

      Like

    • Hugo's avatar

      Hugo 2:22 pm on 21 May 2023 Permalink | Reply

      Not local times (which differ by 4 minutes for each degree of longitude) but zone times.
      We also have to assume that all the zone times are a whole number of hours fast or slow of GMT;
      Chatham Island (for example) is on GMT + 12:45.

      Like

    • Frits's avatar

      Frits 3:54 pm on 23 May 2023 Permalink | Reply

      Also calculating the distance to Aloha.

         
      from enigma import SubstitutedExpression
      
      # check the side lengths form a triangle
      def is_triangle(a, b, c):
        return all([0 < a < b + c, 0 < b < a + c, 0 < c < a + b])
        
      # distance to A to point (p, 0) on line BC 
      def dista(ab, bc, ac, p):  
        x = (ab**2 - ac**2 - bc**2) / (-2 * bc)
        y = (ac**2 - x**2)**.5
        
        # return distance between (x, y) and (p, 0)
        return ((x - p)**2 + (y - 0)**2)**.5
      
      # return local time, Mon 0am + <h> hours
      def time(h):
        weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
                    "Saturday", "Sunday"]
      
        (d, h) = divmod(h, 24)
        return weekdays[d % 7] + " " + str(h % 12) + ('am' if h < 12 else 'pm')
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
         # (K, L, M) = ahead bits for (A, B, C)
         "K + L + M < 3",
         
         # check that the adjusted times/distances form a triangle
         "is_triangle(((depb := 46 + 24 * (K - L)) - 3) - 9, \
                       (arrc := 117 + 24 * (K - M)) - depb, \
                       163 - (arrc + 10))",
                       
         # at time of the radio call the boat is still at sea (between B and C)
         "(93 - depb) and (arrc - 93)",
        ],
        answer="(da := dista((depb - 3) - 9, \
                             arrc - depb, \
                             163 - (arrc + 10), \
                             93 - depb), \
                93 - depb, arrc - 93), \
                (93 + da, 186 - depb + 24 * (L - K), arrc)",
        d2i=dict([(k, "KLM") for k in range(2, 10)]),
        distinct="",
        env=dict(is_triangle=is_triangle, dista=dista),
        reorder=0,
        verbose=0,    # use 256 to see the generated code
      )
      
      islands = ("Aloha", "Bali-Hai", "Crackatoea")
      # print answers
      for (_, ans) in p.solve():
         # determine shortest route
        for ind in [i for i, x in enumerate(ans[0]) if x == min(ans[0])]:
          print(f"reached island: {islands[ind]}, arrival: {time(ans[1][ind])}")
      

      Like

      • Jim Randell's avatar

        Jim Randell 9:39 pm on 23 May 2023 Permalink | Reply

        Yes, it does say the boat should proceed to the “nearest of the three islands”, so I’ve added in code to calculate the distance to A (using the cosine rule), and also to give a bit more information with the solution.

        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