Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 11:47 am on 10 December 2019 Permalink | Reply
    Tags: by: G M L Tuchmann   

    Brain-Teaser 512: [Five nieces] 

    From The Sunday Times, 4th April 1971 [link]

    When I first met the Problem Setter, and let on that I was interested in his line of entertainment, he immediately told me that he had five nieces all going to the same primary school where each girl was entered on her fourth birthday and on each subsequent birthday morning was moved up into the next form.

    Forms I-III were the Infants section to which they all still belonged. Tomorrow (Tuesday) one of the five would have a birthday, on Wednesday another. Which?

    To get me going, he gave me their names in order of seniority and told me which niece would be in which form on Wednesday.

    I said all I could usefully deduce from this was that Alice wasn’t one of the celebrants; I couldn’t be at all sure of any of the other four; I was aware, needless to say, that Alice, though younger than Emily, was older than Isabel, and that Olive was older than Ursula, but would I be right in guessing that Wednesday’s celebrant was older than Tuesday’s? (This necessitating that Ursula and Olive were currently in the same form).

    When he said yes, I knew the answer, so …

    Whose birthday [was on] on Tuesday, and whose [was] on Wednesday?

    This puzzle was originally published with no title.

    [teaser512]

     
    • Jim Randell's avatar

      Jim Randell 11:47 am on 10 December 2019 Permalink | Reply

      I did this one manually before I wrote a program.

      This Python program looks at the girls in ascending age order, and the possible assignment of forms, such that the setter would be unable to deduce any celebrants, and only deduce one non-celebrant. From the information given we can then assign to the girls and verify the remaining conditions. It runs in 80ms.

      Run: [ @repl.it ]

      from collections import defaultdict
      from itertools import product
      from enigma import subsets, irange, tuples, intersect, diff, join, printf
      
      # update a sequence with (<index>, <delta>) pairs
      def update(s, *ps):
        s = list(s)
        for (i, v) in ps:
          s[i] += v
        return tuple(s)
      
      # is a sequence ordered?
      def is_ordered(s):
        return not any(x > y for (x, y) in tuples(s, 2))
      
      # indices for the girls
      girls = list(irange(0, 4))
      
      # record the celebrants by their forms (on Wednesday)
      r = defaultdict(list)
      
      # choose the forms that the girls are in on Wednesday
      for fs in subsets([1, 2, 3, 4], size=5, select="R"):
        # at most 2 girls can be promoted to form 4
        if fs.count(4) > 2: continue
      
        # choose the girls with birthdays on Tuesday and Wednesday
        for (Tu, We) in subsets(girls, size=2, select="P"):
          # the situation on Tuesday
          fsT = update(fs, (We, -1))
          if 0 in fsT or not is_ordered(fsT): continue
          # the situation on Monday
          fsM = update(fsT, (Tu, -1))
          if 0 in fsM or 4 in fsM or not is_ordered(fsM): continue
          r[fs].append((Tu, We))
      
      # for each key determine girls that definately are or aren't celebrants
      for (fs, vs) in r.items():
        # we can't positively identify any celebrant
        pos = intersect(vs)
        if pos: continue
        # we can only negatively identify one non-celebrant
        neg = set(girls).difference(*vs)
        if len(neg) != 1: continue
        # that non-celebrant is A
        A = neg.pop()
        # I and E are either side of A
        for (I, E) in product(girls[:A], girls[A + 1:]):
          # and U and O are the other two (in that order)
          (U, O) = diff(girls, (A, E, I))
      
          # we are then told Wednesday's celebrant is older than Tuesday's
          for (Tu, We) in vs:
            if not(We > Tu): continue
            # and U and O are in the same form on Monday
            fsM = update(fs, (We, -1), (Tu, -1))
            if not(fsM[U] == fsM[O]): continue
            # output solution
            d = dict(zip((A, E, I, O, U), "AEIOU"))
            s = join((join((d[i], '=', x)) for (i, x) in enumerate(fsM)), sep=", ")
            printf("Tu={Tu} We={We}; Mon=[{s}]", Tu=d[Tu], We=d[We])
      

      Solution: Isabel’s birthday is on Tuesday. Olive’s birthday is on Wednesday.

      The order of the girls (youngest to oldest) is: I, U, A, O, E.

      Mon: I=1, U=2, A=2, O=2, E=3
      Tue: I=2, U=2, A=2, O=2, E=3 [I moves up to Form 2]
      Wed: I=2, U=2, A=2, O=3, E=3 [O moves up to Form 3]

      Like

  • Unknown's avatar

    Jim Randell 10:27 am on 8 December 2019 Permalink | Reply
    Tags:   

    Brainteaser 1770: Magic spell 

    From The Sunday Times, 18th August 1996 [link]

    Marvo uses a prearranged pack of cards to perform the following trick. Holding the pack face downwards, one by one he would take a card from the top and place it on the bottom, calling out a letter each time so as to spell:

    A, C, E, [the next card is an ace, which is placed on the table]
    T, W, O, [next card is a 2]
    T, H, R, E, E, [next card is a 3]

    J, A, C, K, [next card is a Jack]
    Q, U, E, E, N, [next card is a Queen]
    K, I, N, G, [next card is a King]
    A, C, E, [next card is an ace]
    T, W, O, [next card is a 2]

    Once he had spelt out the name of the card he would remove the next card from the pack, turn it over and place it face up on the table. Of course it was always the card which he had just spelt out.

    In this way he worked through the clubs, then the hearts, then the diamonds and finally the spades, finishing with just the King of spades in his hand.

    One day his disgruntled assistant sabotaged his act by secretly cutting the pack. However the first card which Marvo turned over was still an ace, and the the second card was still a two.

    What was the next card Marvo turned over?

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

    [teaser1770]

     
    • Jim Randell's avatar

      Jim Randell 10:28 am on 8 December 2019 Permalink | Reply

      We can construct the initial configuration of the pack by starting with a pack of blank cards numbered in order from 1 to 52, and then performing the trick, but when we turn over one of the blank cards we write the appropriate value and suit on the card and continue until the end of the trick. We can then use the numbers to put the pack back into the desired order.

      We can then look through the pack for an ace followed 4 cards later by a two, and then we are interested what card occurs 6 cards after that.

      This Python program runs in 70ms.

      Run: [ @repl.it ]

      from enigma import irange, printf
      
      # words for each value
      word = {
        'A': 'ACE', '2': 'TWO', '3': 'THREE', '4': 'FOUR', '5': 'FIVE',
        '6': 'SIX', '7': 'SEVEN', '8': 'EIGHT', '9': 'NINE', 'X': 'TEN',
        'J': 'JACK', 'Q': 'QUEEN', 'K': 'KING',
      }
      
      # the positions in the pack
      pack = list(irange(0, 51))
      
      # map position -> value + suit
      m = dict()
      
      # go through the suits
      for s in "CHDS":
        # and then the values in each suit
        for v in "A23456789XJQK":
          # apply the operation the appropriate number of times
          for x in word[v]:
            pack.append(pack.pop(0))
          # reveal and discard the top card
          n = pack.pop(0)
          assert n not in m
          # record that card at this position
          m[n] = v + s
      
      # advance by counting value v
      advance = lambda k, v: (k + len(word[v]) + 1) % 52
      
      # look for an ace...
      for (k1, v1) in m.items():
        if v1[0] != 'A': continue
        # but not not the ace we are expecting
        if v1 == 'AC': continue
        # ... followed by a 2 ...
        k2 = advance(k1, '2')
        v2 = m[k2]
        if v2[0] != '2': continue
        # ... and what is the next card?
        k3 = advance(k2, '3')
        v3 = m[k3]
        printf("@{k1}: {v1} -> @{k2}: {v2} -> @{k3}: {v3}")
      

      Solution: The third card turned over is the three of diamonds.

      After that the trick would go awry. The next card turned over is not a 4, it is 8♦.

      The original layout of the pack is:

      After the pack is cut 4♣ is at the top (as highlighted in the diagram), after counting out A, C, E we get A♠, and then T, W, O gets 2♥, and then T, H, R, E, E gets 3♦.

      Like

  • Unknown's avatar

    Jim Randell 9:04 pm on 6 December 2019 Permalink | Reply
    Tags:   

    Teaser 2985: What’s my (land) line? 

    From The Sunday Times, 8th December 2019 [link] [link]

    My telephone has the usual keypad:

    My [11-digit] telephone number starts with 01 and ends in 0. All digits from 2 to 9 are used exactly once in between, and each pair of adjacent digits in the phone number appear in a different row and column of the keypad array.

    The 4th and 5th digits are consecutive as are the 9th and 10th and the 8th digit is higher than the 9th.

    What is my number?

    [teaser2985]

     
    • Jim Randell's avatar

      Jim Randell 9:25 pm on 6 December 2019 Permalink | Reply

      Suppose the phone number is: 01-ABCDEFGH-0.

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

      The following run file executes in 146ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      --digits="2-9"
      
      # 4th and 5th digits are consecutive numbers
      "abs(B - C) = 1"
      
      # as are 9th and 10th digits
      "abs(G - H) = 1"
      
      # 8th digit is greater than the 9th
      "F > G"
      
      # assign row/col values to the individual digits
      --code="row = [ 4, 1, 1, 1, 2, 2, 2, 3, 3, 3 ]"
      --code="col = [ 2, 1, 2, 3, 1, 2, 3, 1, 2, 3 ]"
      --code="check = lambda s: all(row[x] != row[y] and col[x] != col[y] for (x, y) in tuples(s, 2))"
      # adjacent digits appear in different rows/columns
      "check([1, A, B, C, D, E, F, G, H, 0])"
      
      # solution
      --code="number = lambda x: sprintf('01{x:08d}0')"
      --answer="number(ABCDEFGH)"
      

      Solution: The phone number is 01867295340.

      Like

    • Frits's avatar

      Frits 12:46 pm on 24 September 2020 Permalink | Reply

       
      # make sure loop variable value is not equal to previous ones
      def domain(v):
        # find already used loop values  ...
        vals = set()
        # ... by accessing previously set loop variable names
        for s in lvd[v]:
          vals.add(globals()[s])
        
        return [x for x in range(2,10) if x not in vals]
        
      def check(s):
        # row coord: (1 + (i - 1) // 3)
        # col coord: (1 + (i - 1) % 3)
        
        # Check adjacent fields
        for (x, y) in list(zip(s, s[1:])):
          # check if on same row 
          if (x - 1) // 3 == (y - 1) // 3:
            return False
          # check if on same col 
          if (x - y) % 3 == 0:
            return False  
        return True      
      
      # set up dictionary of for-loop variables
      lv = ['A', 'B', 'C', 'G', 'H', 'F', 'D', 'E']
      lvd = {v: lv[:i] for i, v in enumerate(lv)}
      
      sol = set()
      
      # Solve puzzle by testing all posssible values
      for A in {5, 6, 8, 9}:
        for B in domain('B'):
          if not check([A, B]): continue
          for C in domain('C'):
            if abs(B - C) != 1: continue
            if not check([B, C]): continue
            for G in domain('G'):
              for H in domain('H'):
                if abs(G - H) != 1: continue
                if not check([G, H]): continue
                for F in domain('F'):
                  if F < G: continue
                  if not check([F, G]): continue
                  for D in domain('D'):
                    for E in domain('E'):
                      if not check([C, D, E, F]): continue
                      sol.add((A, B, C, D, E, F, G, H))
      
      print("  ABCDEFGH ")             
      for s in sol:
        print(f"01{''.join(str(x) for x in s)}0")  
      
      # Output:
      #
      #   ABCDEFGH
      # 01867295340
      

      Like

  • Unknown's avatar

    Jim Randell 4:48 pm on 29 November 2019 Permalink | Reply
    Tags:   

    Teaser 2984: Shuffle the cards 

    From The Sunday Times, 1st December 2019 [link] [link]

    In the classroom I have a box containing ten cards each with a different digit on it. I asked a child to choose three cards and to pin them up to make a multiplication sum of a one-figure number times a two-figure number. Then I asked the class to do the calculation.

    During the exercise the cards fell on the floor and a child pinned them up again, but in a different order. Luckily the new multiplication sum gave the same answer as the first and I was able to display the answer using three of the remaining cards from my box.

    What was the displayed answer?

    [teaser2984]

     
    • Jim Randell's avatar

      Jim Randell 5:05 pm on 29 November 2019 Permalink | Reply

      If the original sum is A × BC, then none of the digits in the second sum can be in their original positions, so the second sum is one of: B × CA or C × AB.

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

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      # initial sum
      "A * BC = DEF"
      
      # but it also works in a different order
      "DEF in (B * CA, C * AB)"
      
      # required answer
      --answer="DEF"
      

      Solution: The result of the two multiplications is 130.

      The two sums are: 2×65 and 5×26, but we don’t know which was the original one.

      Like

      • Jim Randell's avatar

        Jim Randell 9:42 pm on 2 December 2019 Permalink | Reply

        If we consider the two possible pairs of sums:

        A × BC = DEF ∧ B × CA = DEF
        A × BC = DEF ∧ C × AB = DEF

        Rewriting the first with A = X, B = Y, C = Z and the second with A = Y, B = Z, C = X, we get:

        X × YZ = DEF ∧ Y × ZX = DEF
        Y × ZX = DEF ∧ X × YZ = DEF

        Which are the same, so we can solve the puzzle with an even simpler one liner:

        % python -m enigma SubstitutedExpression "X * YZ = DEF" "Y * ZX = DEF" --answer="DEF"
        (X * YZ = DEF) (Y * ZX = DEF) (DEF)
        (5 * 26 = 130) (2 * 65 = 130) (130) / D=1 E=3 F=0 X=5 Y=2 Z=6
        DEF = 130 [1 solution]
        

        Like

    • GeoffR's avatar

      GeoffR 7:41 pm on 4 December 2019 Permalink | Reply

      
      from time import perf_counter_ns
      start = perf_counter_ns()
      
      from itertools import permutations
      
      for p in permutations((1,2,3,4,5,6,7,8,9),3):
          a, b, c = p    # choose 3 positive digits
      
          # Original displayed answer
          s = a * (10 * b + c)
          
          # 1st alternative position for digits to give same product
          if s == b * (10 * c + a):
              if len (set((a, b, c, s//100, s//10%10, s%10))) == 6:
                print(f"1.Original displayed answer = {s} ({a}*{10*b+c})")
                print(f"2nd displayed answer = {s} ({b}*{10*c+a})")
                print(f"1st digit={a}, 2nd digit={b}, 3rd digit={c}")
                print(f"{(perf_counter_ns() - start) / 1e6:.3f} milliseconds")
      
          # 2nd alternative positions for digits to give same product
          if s == c * (10 * a + b):
              if len (set((a, b, c, s//100, s//10%10, s%10))) == 6:
                print(f"2.Original displayed answer = {s} ({a}*{10*b+c})")
                print(f"2nd displayed answer = {s} ({c}*{10*a+b})")
                print(f"1st digit={a}, 2nd digit={b}, 3rd digit={c}")
                print(f"{(perf_counter_ns() - start) / 1e6:.3f} milliseconds")
      
      # 2.Original displayed answer = 130 (2*65)
      # 2nd displayed answer = 130 (5*26)
      # 1st digit=2, 2nd digit=6, 3rd digit=5
      # 49.703 milliseconds
      #
      # 1.Original displayed answer = 130 (5*26)
      # 2nd displayed answer = 130 (2*65)
      # 1st digit=5, 2nd digit=2, 3rd digit=6
      # 88.180 milliseconds
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 4:54 pm on 5 December 2019 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9: A; var 1..9: B; var 1..9: C; var 1..9: D;
      var 0..9: E; var 0..9: F;
      
      constraint all_different([A, B, C, D, E, F]);
      
      var 10..99: BC = 10*B + C;
      var 10..99: CA = 10*C + A;
      var 10..99: AB = 10*A + B;
      var 100..999: DEF = 100*D + 10*E + F;
      
      % Original sum
      constraint A * BC == DEF;
      
      % Sums with digits in a different order
      constraint B * CA == DEF \/ C * AB == DEF;
      
      solve satisfy;
      
      output ["Displayed Sum is " ++ show(A) ++ " X " ++ show(BC)
      ++ " = " ++ show(DEF) ];
      
      % Displayed Sum is 2 X 65 = 130
      % Displayed Sum is 5 X 26 = 130
      % ==========
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 2:50 pm on 28 November 2019 Permalink | Reply
    Tags:   

    Teaser 2890: Squares on cubes 

    From The Sunday Times, 11th February 2018 [link] [link]

    Liam has a set of ten wooden cubes; each has a different number (from 1 to 10) painted on one face (the other five faces are blank). He has arranged them in a rectangular block with all numbers upright and facing outwards. Each vertical side of the block shows some numbers which can be read as a number that is a perfect square. No two square numbers have the same number of digits.

    Which square number must be present?

    [teaser2890]

     
    • Jim Randell's avatar

      Jim Randell 2:51 pm on 28 November 2019 Permalink | Reply

      The cubes are placed on the table, so that the numbers are showing on vertical faces. They are then slid around on the table to arrange them into a contiguous rectangular block with all the numbers showing on the outside vertical edges of the block.

      The cubes must be arranged into a 2×5 block (a 1×10 block is not possible), and each of the 4 vertical sides of the block reads as a square number. And each of the 4 square numbers has a different number of digits.

      The sides composed of 2 blocks cannot involve 10 (as none of 10, 10_, _10 are square numbers), so they must display square numbers made of 1 number (1 digit) and 2
      numbers (2 digits) and the 5 block sides must show squares made of 3 numbers (3 digits) and 4 numbers (5 digits, including 10).

      This Python program runs in 103ms.

      Run: [ @repl.it ]

      from enigma import (irange, subsets, concat, is_square, Accumulator, diff, join, printf)
      
      # single digits (10 must appear in the 4-number square)
      digits = irange(1, 9)
      
      # make squares from k numbers
      def squares(numbers, k):
        for ns in subsets(numbers, size=k, select="P"):
          s = int(concat(ns))
          if is_square(s):
            yield ns
      
      # find values common to all solutions
      r = Accumulator(fn=set.intersection)
      
      # choose a 1 number square [1, 4, 9]
      for s1 in squares(digits, 1):
      
        # choose a 2 number square [not 10]
        for s2 in squares(diff(digits, s1), 2):
      
          # choose a 3 number square [not 10]
          for s3 in squares(diff(digits, s1 + s2), 3):
      
            # choose a 4 number square [must have 10]
            for s4 in squares(diff(digits, s1 + s2 + s3) + (10,), 4):
      
              # turn the squares into strings
              ss = tuple(map(concat, (s1, s2, s3, s4)))
      
              printf("{s1} {s2} {s3} {s4}")
              r.accumulate(set(ss))
      
      printf("common squares = {r}", r=join(sorted(r.value), sep=", "))
      

      Solution: The square number 9 must be present.

      The three possible arrangements for the square numbers are:

      (9, 36, 784, 11025)
      (9, 81, 324, 51076)
      (9, 81, 576, 23104)

      Here is a diagram of the first arrangement.

      Imagine the cubes are laid out like a 2×5 block of chocolate viewed from above. The numbers are etched on the the sides of the cubes. But the block has started to melt and so the bottom of it has splayed out, allowing us to see all four of the square numbers when viewed from directly above:

      Like

    • GeoffR's avatar

      GeoffR 8:23 am on 30 November 2019 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Looking for 1-digit, 2-digit, 3-digit and 5-digit squares
      % on cubes to use up all 10 numbers (1-10) in a 5 by 2 block
      % of cubes in same arrangement as Jim
      
      var 0..9: A; var 0..9: B; var 0..9: C; var 0..9: D;
      var 0..9: E; var 0..9: F; var 0..9: G; var 0..9: H; 
      var 0..9: I; var 0..9: J; var 0..9: K;
      
      % 2,3 and 5 digit squares
      var 10..99: BC = 10*B + C;
      var 100..999: DEF = 100*D + 10*E + F;
      var 10000..99999: GHIJK; 
      
      set of int: sq2 = {n*n | n in 4..9 };
      set of int: sq3 = {n*n | n in 10..31};  
      set of int: sq5 = {n*n | n in 100..316};
      
      % Four squares
      constraint A == 4 \/ A == 9;
      constraint BC in sq2;
      constraint DEF in sq3;
      constraint GHIJK in sq5;
      
      constraint G == GHIJK div 10000 
      /\ H == GHIJK div 1000 mod 10
      /\ I = GHIJK div 100 mod 10 
      /\ J == GHIJK div 10 mod 10
      /\ K = GHIJK mod 10;
      
      % Number 10 must be in the five digit square GHIJK
      constraint (G == 1 /\ H = 0) \/  (H == 1 /\ I = 0)
      \/ (I == 1 /\ J = 0) \/ (J == 1 /\ K = 0);
      
      % check digit '1' occurs twice only i.e as 1 and 10
      constraint count_eq([A, BC div 10, BC mod 10,
      DEF div 100, DEF div 10 mod 10, DEF mod 10,
      G, H, I, J, K ], 1, 2);
      
      % the set of digits used
      var set of int: my_set  = {A, BC div 10, BC mod 10,
      DEF div 100, DEF div 10 mod 10, DEF mod 10,
      G, H, I, J, K };
      
      % maximum set of digits used
      set of int: all_dig == {1,2,3,4,5,6,7,8,9,0};
      
      constraint my_set == all_dig;
      
      solve satisfy;
      
      % Four squares
      output [ "Squares: 1-digit = " ++ show(A)
      ++ ", 2-digit = " ++ show(BC)
      ++ ", 3-digit = " ++ show(DEF)
      ++ ", 5-digit = " ++ show(GHIJK)];
      
      % Squares: 1-digit = 9, 2-digit = 36, 3-digit = 784, 5-digit = 11025
      % ----------
      % Squares: 1-digit = 9, 2-digit = 81, 3-digit = 576, 5-digit = 23104
      % ----------
      % Squares: 1-digit = 9, 2-digit = 81, 3-digit = 324, 5-digit = 51076
      % ----------
      % ==========
      % Finished in 684msec
      
      

      Without the constraint that the digit one is the duplicated digit, I found three more possible sets of squares:

      (4,36,289, 51076)
      (9,36,784,21025)
      (9,25,784, 36100)

      @Jim: Please confirm your run-time for this code.Thanks,

      Like

      • Jim Randell's avatar

        Jim Randell 8:43 am on 1 December 2019 Permalink | Reply

        @Geoff: On my machine I get a comparative runtime of 377ms for the code you posted (using the gecode solver – the chuffed solver fails on this model).

        Like

  • Unknown's avatar

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

    Brain-Teaser 511: What’s my age? 

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

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

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

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

    Please reply on enclosed stamped postcard.”

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

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

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

    How old am I?

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

    [teaser511]

     
    • Jim Randell's avatar

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

      This Python program runs in 73ms.

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

      Solution: The setter’s age is 71.


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

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

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

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

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

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

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

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

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

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

      Like

  • Unknown's avatar

    Jim Randell 1:15 pm on 25 November 2019 Permalink | Reply
    Tags:   

    Teaser 2886: Metal arithmetic 

    From The Sunday Times, 14th January 2018 [link] [link]

    The area of one face of a hot, steel cuboid block was a single-figure whole number of square feet; and it was within one per cent of this after cooling and contraction. When cool, it was cut, parallel to this face, into blocks of the same width and height, but unequal length. For the first cut block, its width, height and length, in inches, were different two-figure whole numbers with only four factors (including 1 and the number), and they had only the factor 1 in common. The same applied to the other cut blocks. Curiously, the six digits of the width, height and length of the first cut block were also all different.

    In ascending order, what were the dimensions, in inches, of the shortest block?

    [teaser2886]

     
    • Jim Randell's avatar

      Jim Randell 1:16 pm on 25 November 2019 Permalink | Reply

      This Python program runs in 81ms.

      Run: [ @replit ]

      from enigma import (irange, divisors, subsets, divc, is_duplicate, printf)
      
      # find 2-digit numbers with exactly 4 divisors
      ns = dict()
      for n in irange(10, 99):
        fs = divisors(n)
        if len(fs) == 4:
          ns[n] = set(fs)
      
      # check x and y share exactly k divisors
      def check(x, y, k=1):
        return len(ns[x].intersection(ns[y])) == k
      
      # find x, y dimensions
      for (x, y) in subsets(ns.keys(), size=2):
      
        # cross section is within 1% of a single digit number of square feet
        A = x * y
        k = divc(A, 144)
        if k > 9: continue
        if not (100 * A > 99 * 144 * k): continue
      
        # x and y share only one divisor
        if not check(x, y): continue
      
        # find possible values for z dimension
        zs = list(z for z in ns.keys() if z != x and z != y and check(x, z) and check(y, z))
        # it is implied there are at least 3 blocks
        if not (len(zs) > 2): continue
      
        # and there must be a z such that x, y, z consist of 6 different digits
        if all(is_duplicate(x, y, z) for z in zs): continue
      
        # output dimensions of the smallest block
        printf("x={x} y={y} z={z} [A={A} k={k} zs={zs}]", z=min(zs))
      

      Solution: The shortest block measured 21 × 34 × 55 inches.

      The 21 in. × 34 in. face has an area of 714 sq. in., when heated up the face expands to exactly 5 square feet = 720 sq. in.

      The area of the face when cold is 99.17% of this area.

      The 4 divisors of 21 are: 1, 3, 7, 21.

      The 4 divisors of 34 are: 1, 2, 17, 34.

      The cold block was cut into lengths of 55 in., 65 in., 95 in. (it is implied the block was cut into at least 3 pieces).

      The 4 divisors of 55 are: 1, 5, 11, 55.

      The 4 divisors of 65 are: 1, 5, 13, 65.

      The 4 divisors of 95 are: 1, 5, 19, 95.

      So the first block cut could be: 21 × 34 × 65, using the digits: 1, 2, 3, 4, 5, 6, or: 21 × 34 × 95, using the digits: 1, 2, 3, 4, 5, 9.

      And the shortest block is 21 × 34 × 55, which repeats the digit 5.

      Like

  • Unknown's avatar

    Jim Randell 8:11 am on 24 November 2019 Permalink | Reply
    Tags:   

    Brainteaser 1766: House squares 

    From The Sunday Times, 21st July 1996 [link]

    There is an even number of houses in my road, numbered consecutively from 1 upwards, with the odd numbers on one side and the even numbers on the other.

    My neighbour’s son was practising with his new calculator. He found the sum of the squares of all the house numbers on one side of the road and subtracted it from the sum of the squares of all the house numbers on the other side of the road. The digits in his answer were all the same.

    Noticing that two of the houses displayed “For Sale” signs he decided to carry out a second calculation omitting those two houses. This gave an answer 50% higher than the first.

    Which two houses are for sale?

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

    [teaser1766]

     
    • Jim Randell's avatar

      Jim Randell 8:12 am on 24 November 2019 Permalink | Reply

      This Python program runs in 70ms.

      Run: [ @repl.it ]

      from enigma import seq_all_same, nsplit, irange, div, subsets, printf
      
      # generate the difference between the sum of the even and odd squares up to n
      def generate():
        # sums of squares
        n = d = 0
        while True:
          n += 1
          d -= n * n
          n += 1
          d += n * n
          yield (n, d)
      
      # find solutions to the puzzle
      def solve():
        # look at differences for increasing even n
        for (n, d) in generate():
          # the difference should be a repdigit
          if not seq_all_same(nsplit(d)): continue
          # the second answer is 50% higher
          diff = div(d, 2)
          if diff is None: continue
      
          # look for the house numbers for sale p, q
          for (p, q) in subsets(irange(1, n), size=2):
            delta = sum([-1, 1][x % 2] * x * x for x in (p, q))
            if abs(delta) == diff:
              yield (p, q, n, d, d + diff)
      
      # we only need the first solution
      for (p, q, n, d, d2) in solve():
        printf("for sale = {s} [{n} houses; difference = {d} -> {d2}]", s=(p, q))
        break
      

      Solution: The two houses for sale are 14 and 23.

      There are 36 houses in the street.

      The result of the first calculation is 666.

      The result of the second calculation is 999.

      The difference between the sums of the squares of the first n even numbers and the first n odd numbers can be expressed:

      D(n) = n (2n + 1)

      Which allows a slightly shorter program than generating the differences constructively.

      Like

  • Unknown's avatar

    Jim Randell 5:08 pm on 22 November 2019 Permalink | Reply
    Tags:   

    Teaser 2983: Maths for dessert 

    From The Sunday Times, 24th November 2019 [link] [link]

    George and Martha have their five daughters round the dinner table. After the meal, they had ten cards numbered 0 to 9 inclusive and randomly handed two to each daughter. Each was invited to form a two-digit number. The daughter drawing 0 obviously had no choice and had to announce a multiple of ten.

    However, the others each had the choice of two options. For example if 3 and 7 were present, either 37 or 73 would be permissible. George added up the five two-digit numbers (exactly one being divisible by 9) and Martha noticed that three of the individual numbers divided exactly into that total.

    What was the total of the remaining two numbers?

    [teaser2983]

     
    • Jim Randell's avatar

      Jim Randell 5:16 pm on 22 November 2019 Permalink | Reply

      This Python program runs in 76ms.

      Run: [ @repl.it ]

      from enigma import (partitions, irange, cproduct, filter2, ordered, printf)
      
      # form numbers from digits a and b
      def numbers(a, b):
        if a > 0: yield 10 * a + b
        if b > 0: yield 10 * b + a
      
      # deal out the 10 cards in pairs
      for ps in partitions(irange(0, 9), 2):
        # exactly one of the numbers is divisible by 9
        if sum((a + b) % 9 == 0 for (a, b) in ps) != 1: continue
        # form the numbers from the pairs
        for ns in cproduct(numbers(*p) for p in ps):
          # exactly three of the numbers divide into the total
          t = sum(ns)
          (ds, rs) = filter2((lambda n: t % n == 0), ns)
          if len(ds) != 3: continue
          # output solution
          printf("sum{rs} = {s} [numbers = {ns}, total = {t}]", rs=ordered(*rs), s=sum(rs), ns=ordered(*ns))
      

      Solution: The sum of the numbers that do not divide into the total is 147.

      The numbers are: 14, 28, 50, 63, 97.

      Of these only 63 is divisible by 9.

      The sum of the numbers is 252, which is divisible by 14, 28, and 63.

      And the remaining numbers (50 and 97) sum to 147.

      Like

    • GeoffR's avatar

      GeoffR 9:47 am on 25 November 2019 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C;
      var 1..9:D; var 1..9:E; var 1..9:F;
      var 1..9:G; var 1..9:H; var 1..9:I;
      
      % Make J the zero digit
      int: J == 0;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I,J]);
        
      % Five 2-digit numbers 
      var 10..99: AB = 10*A + B;
      var 10..99: CD = 10*C + D;
      var 10..99: EF = 10*E + F;
      var 10..99: GH = 10*G + H;
      var 10..99: IJ = 10*I + J;
      
      var 10..400: all_dig; 
      var 10..200: dig2;   % sum of the remaining two numbers
      
      constraint all_dig = AB + CD + EF + GH + IJ;
      
      % One of the five 2-digit numbers is divisible by 9
      constraint sum ( [ AB mod 9 == 0, CD mod 9 == 0, 
      EF mod 9 == 0, GH mod 9 == 0, IJ mod 9 == 0 ] ) = 1;
      
      % Three of the 2-digit numbers divide the five digit total
      constraint all_dig mod AB == 0 /\ all_dig mod CD == 0 
      /\ all_dig mod EF == 0 /\ all_dig mod GH != 0 /\ all_dig mod IJ != 0;
      
      % Sum of two digits which do not divide the five digit total
      constraint  dig2 == GH + IJ ;
      
      solve satisfy;
      
      output [ "Total of the remaining two numbers = " ++ show(dig2)
      ++ "\nTotal of five 2-digit numbers = " ++ show(all_dig)++ "\n"
      ++"The five numbers are " ++ show(AB) ++ ", " ++ show(CD) ++ ", " 
      ++ show(EF) ++ ", " ++ show(GH) ++ " and " ++ show(IJ) ];
      
      % Total of the remaining two numbers = 147
      % Total of five 2-digit numbers = 252
      % The five numbers are 28, 14, 63, 97 and 50
      
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 7:56 am on 28 November 2019 Permalink | Reply

      
      from itertools import permutations
      
      for p in permutations('1234567890'):
        a, b, c, d, e, f, g, h, i, j = p
        
        ab, cd, ef = int(a + b), int(c + d), int(e + f)
        gh, ij = int(g + h), int(i + j)
      
        # check all five numbers are 2-digit numbers
        n5 = (ab, cd, ef, gh, ij)
        if all( x < 100 and x >= 10 for x in n5):
          tot = ab + cd + ef + gh + ij
      
          # two conditions to check
          if (sum(1 for y in n5 if y % 9 == 0) == 1 
            and sum(1 for z in n5 if tot % z == 0) == 3) :
               
            if (tot % ab == 0 and tot % cd == 0 and tot % ef == 0
              and tot % gh != 0 and tot % ij != 0) :
              print(f"Three factors are {ab}, {cd} and {ef}" )
              print(f"Two remaining numbers are {gh} and {ij} (total {gh+ij})")
              break    # to stop repeated answers
      
      # Three factors are 14, 28 and 63
      # Two remaining numbers are 50 and 97 (total 147)
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:48 pm on 18 November 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 510: [Angling competition] 

    From The Sunday Times, 21st March 1971 [link]

    Announcing that the Baron’s team had won the angling competition, the Mayor told those assembled that the winning catch weighed 19st 1lb 7oz and consisted of plaice, halibut and turbot. He also disclosed the remarkable fact that each fish of the same kind had been precisely the same weight — each plaice 2lb 8oz, each halibut 4lb 1oz and each turbot 6lb 6oz.

    Although His Worship mentioned that half the total number of fish in the winning catch were of one and the same kind and that there were three times as many of one kind as of one other kind, he lamentably failed to give the further details so many were waiting to hear.

    One enthusiast, for example, wished to know the total weight of all the plaice landed by the Baron’s team.

    Can you say what it was? (Answers in lbs).

    This puzzle was originally published with no title.

    [teaser510]

     
    • Jim Randell's avatar

      Jim Randell 9:40 am on 21 November 2019 Permalink | Reply

      Using oz for for measuring weight, we have:

      1 lb = 16 oz
      1 st = 14 lb = 224 oz

      So the total weight of fish is:

      total = 19 st, 1 lb, 7 oz = 4279 oz

      And the individual types of fish weigh:

      plaice = 2 lb, 8 oz = 40 oz
      halibut = 4 lb, 1 oz = 65 oz
      turbot = 6 lb, 6 oz = 102 oz

      This Python program uses the [[ express() ]] function from the enigma.py library. It runs in 110ms.

      from enigma import (express, div, printf)
      
      # find numbers of plaice, halibut, turbot
      for s in express(4279, (40, 65, 102), min_q=1):
        t = sum(s)
        # half the total was of one kind
        h = div(t, 2)
        if h is None or h not in s: continue
        # and there were 3 times as many of one kind than of an other
        if not any(3 * x in s for x in s): continue
        # output solution
        (p, h, t) = s
        printf("plaice = {p}, halibut = {h}, turbot = {t}")
      

      Solution: The total weight of plaice was 82 lb, 8 oz (= 82.5 lb).

      There were 33 plaice (= 1320 oz = 82 lb, 8 oz = 5 st, 12 lb, 8 oz).

      There were 11 halibut (= 715 oz = 44 lb, 11 oz = 3 st, 2 lb, 11 oz).

      There were 22 turbot (= 2244 oz = 140 lb, 4 oz = 10 st, 4 oz).

      Like

  • Unknown's avatar

    Jim Randell 9:57 am on 17 November 2019 Permalink | Reply
    Tags: by: Conor Henderson   

    Brainteaser 1757: Enigma variations 

    From The Sunday Times, 19th May 1996 [link]

    I recently joined Enigma, the international club for puzzle-solvers. When I received my membership card I noticed that my membership number was a six-figure number and that its digits could be rearranged to make three consecutive two-figure numbers.

    I also noticed that its six digits could be rearranged to form two consecutive three-figure numbers.

    Furthermore, no one with a lower six-figure membership number could make the same claims.

    What is my membership number?

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

    [teaser1757]

     
    • Jim Randell's avatar

      Jim Randell 8:51 am on 19 November 2019 Permalink | Reply

      We can examine sequences of 3 consecutive 2-digit numbers (there are only 88 of them), and look for ones whose digits can be rearranged to form 2 consecutive 3-digit numbers. Each of these produces a collection of 6-digit membership numbers, and we can choose the smallest of these.

      This Python program runs in 135ms.

      Run: [ @repl.it ]

      from enigma import irange, tuples, multiset, nsplit, subsets, nconcat, Accumulator, printf
      
      # collect the digits from a sequence of numbers
      digits = lambda ns: multiset.from_seq(*(nsplit(n) for n in ns))
      
      # turn digits ds into n consecutive k-digit numbers
      def consecutive(ds, n, k):
        # extract the first k-digit number
        for s in subsets(ds, size=k, select="mP"):
          if s[0] == 0: continue
          # make the sequence of consecutive numbers
          s = nconcat(s)
          ss = tuple(irange(s, s + n - 1))
          # does it use the same digits?
          if digits(ss) == ds:
            yield ss
      
      # find the smallest number
      r = Accumulator(fn=min)
      
      # consider 3 consecutive 2-digit numbers
      for n2s in tuples(irange(10, 99), 3):
        ds = digits(n2s)
      
        # can the digits be rearranged to form 2 consecutive 3-digit numbers?
        n3s = list(consecutive(ds, 2, 3))
        if not n3s: continue
      
        # construct the smallest possible 6 digit number from the digits
        n = sorted(ds)
        # but leading zeros aren't allowed
        if n[0] == 0:
          # bring the first non-zero digit forward
          n.insert(0, n.pop(next(i for (i, x) in enumerate(n) if x > 0)))
        n = nconcat(n)
      
        printf("[{n:06d} -> {n2s} -> {n3s}]")
        r.accumulate(n)
      
      # output solution
      printf("membership number = {r.value:06d}")
      

      Solution: Your membership number is: 102222.

      The six digits of 102222 can be rearranged as (20, 21, 22) and also (220, 221).

      The largest such number is 999987:

      999987 → (97, 98, 99), (997, 998)

      There are 19 viable sequences of 3 consecutive 2-digit numbers, which give a total of 1195 candidate membership numbers.

      If leading zeros were allowed (which would not be uncommon for membership numbers) the lowest viable membership number would be 012222 (and there would be 1350 candidate numbers).

      Like

  • Unknown's avatar

    Jim Randell 9:06 am on 17 November 2019 Permalink | Reply
    Tags:   

    Brainteaser 1747: Lottery charges 

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

    Chargealot are having a new set of lottery balls made. They are going to be painted with numbers from 1 (not 01) to 49. The painter decides that this is one chance to make a fortune from the lottery and so he charges accordingly. He divides the digits into two types and charges a certain whole number of pounds for painting each digit of one type (each time it occurs) and a different whole number of pounds for all the other digits (each time they occur). Both charges are more than £50 but less than £100.

    To make the cost sound reasonable, instead of quoting a price for the whole set he quoted the average price per ball. Of course some balls cost less than average, and some balls cost more, but just one ball’s price was equal to the average.

    What were his two charges per digit?

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

    [teaser1747]

     
    • Jim Randell's avatar

      Jim Randell 9:07 am on 17 November 2019 Permalink | Reply

      We can divide the digits from 0-9 into two sets, “type A” and “type B” with associated costs a and b.

      There are 9 single digit numbers and 40 2-digit numbers, so the 49 numbers use 89 digits in total.

      If, in total, there are nA type A digits and nB type B digits then we have:

      nA + nB = 89
      T = a × nA + b × nB = 49 × m

      where T is the total cost and m is the average cost per ball, and only one of the balls can cost the average amount.

      This Python program runs in 158ms.

      Run: [ @repl.it ]

      from collections import defaultdict
      from enigma import subsets, diff, irange, nsplit, div, printf
      
      # place 0 in set A and choose some other digits to go in set B
      for B in subsets(irange(1, 9), min_size=1):
        A = diff(irange(0, 9), B)
      
        # group the numbers by (A, B) usage
        d = defaultdict(list)
        nA = 0
        for n in irange(1, 49):
          # count the A and B digits
          k = [0, 0]
          for x in nsplit(n):
            k[x in B] += 1
          d[tuple(k)].append(n)
          nA += k[0]
        nB = 89 - nA
      
        # find a number in a class by itself
        for ((i, j), vs) in d.items():
          if len(vs) != 1: continue
      
          # find costs for the digits in A and B
          for a in irange(51, 99):
            b = div(a * (49 * i - nA), nB - 49 * j)
            if b is None or b < 51 or b > 99: continue
      
            # check none of the other groups cost the same as the mean
            m = a * i + b * j
            if any(a * x + b * y == m for (x, y) in d.keys() if not(x == i and y == j)): continue
      
            # output solution
            printf("cost = ({a}, {b}), digits = ({A}, {B}); mean = {m}, number = {vs[0]}")
      

      Solution: Each digit costs £ 74 or £ 83.

      The ball that costs the mean amount is one of the repeated digit balls, i.e. 11, 22, 33, 44. It costs £ 148 (i.e. 2× £ 74).

      The digit used twice on the mean ball is the only digit that costs £ 74, all the other digits cost £ 83.

      The total cost for all the balls is therefore £ 7252.

      Like

  • Unknown's avatar

    Jim Randell 5:15 pm on 15 November 2019 Permalink | Reply
    Tags:   

    Teaser 2982: Antique tea pot 

    From The Sunday Times, 17th November 2019 [link] [link]

    My local antiques dealer marks each item with a coded price tag in which different digits represent different letters. This enables him to tell the whole number of pounds he paid for the item. I bought a tea pot from him tagged MOE.

    Inside the tea pot was a scrap of paper which I used to work out his code. The letters AMOUNT I SPENT had been rearranged to make the multiplication sum above.

    How much did he pay for the tea pot?

    [teaser2982]

     
    • Jim Randell's avatar

      Jim Randell 5:18 pm on 15 November 2019 Permalink | Reply

      The multiplication is straightforward to solve manually.

      Programatically, we can solve this puzzle using the [[ SubstitutedExpression() ]] solver from the enigma.py library.

      The following run file executes in 145ms.

      Run: [ @repl.it ]

      #! python -m enigma -rr
      
      SubstitutedExpression
      
      "MAN * PIN = TOOTIN"
      
      "MAN * N = TUON"
      "MAN * I = AASA"
      "MAN * P = TMEP"
      
      --answer="MOE"
      

      Solution: MOE = 350, so the teapot cost £ 350.

      The corresponding digits for AMOUNT I SPENT are 235961 7 84061.

      Like

    • GeoffR's avatar

      GeoffR 3:03 pm on 16 November 2019 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";    
      
      %       M A N
      %    X  P I N 
      %    ---------
      %     T U O N
      %   A A S A
      % T M E P
      %------------
      % T O O T I N
      %-------------
      
      var 0..9: A; var 0..9: M; var 0..9: O; var 0..9: U;
      var 0..9: N; var 0..9: T; var 0..9: I;
      var 0..9: S; var 0..9: P; var 0..9: E;
      
      constraint M > 0 /\ P > 0 /\ T > 0 /\ A > 0;
      
      var 100..999: MAN = 100*M + 10*A + N;
      var 100..999: PIN = 100*P + 10*I + N;
      var 100..999: MOE = 100*M + 10*O + E;
      
      var 1000..9999: TUON = 1000*T + 100*U + 10*O + N;
      
      var 10000..99999: AASA = 11010*A + 100*S;
      
      var 100000..999999: TMEP = 100000*T + 10000*M
      + 1000*E + 100*P;
      
      var 100000..999999: TOOTIN = 100100*T + 11000*O
      + 10*I + N;
      
      constraint MAN * PIN == TOOTIN;
      constraint N * MAN == TUON;
      constraint I * MAN * 10 == AASA;
      constraint P * MAN * 100 == TMEP;
      
      solve satisfy;
      
      output [ "MOE = £" ++ show(MOE) ++ "\n" ++
      "Main sum is " ++ show(MAN) ++ " X " ++
      show(PIN) ++ " = " ++ show(TOOTIN) ];
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:50 am on 12 November 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 509: [Truth and lies] 

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

    Alpha, Beta, and Gamma are brothers. One of them always tells the truth, one always lies, and the other sometimes tells the truth and sometimes lies.

    I asked Gamma who was the elder of Alpha and Beta. Gamma (who was a little shy) whispered the answer to Alpha who the told me that Gamma had said that Alpha was older than Beta. Beta agreed that he was younger than Alpha, but he warned me that Alpha did not always tell the truth.

    The eldest of the three said that Gamma was more honest than Beta, and the youngest agreed with him. Furthermore, the younger of Beta and Gamma said that Alpha always told the truth.

    Who is the eldest? And who is the most honest?

    This puzzle was originally published with no title.

    [teaser509]

     
    • Jim Randell's avatar

      Jim Randell 8:51 am on 12 November 2019 Permalink | Reply

      This Python program runs in 70ms.

      Run: [ @replit ]

      from enigma import (subsets, printf)
      
      # the possible behaviours:
      
      # T tells the truth
      def T(s):
        return bool(s)
      
      # F lies
      def F(s):
        return not s
      
      # U is unreliable
      def U(s):
        return True
      
      # honesty ratings
      honest = { F: 0, U: 1, T: 2 }
      
      # assign behaviours to A, B, G
      for (fA, fB, fG) in subsets((T, F, U), size=3, select="P"):
      
        # "B said A does not always tell the truth"
        if not fB(fA != T): continue
      
        # choose an age ordering for A, B, G
        for (aA, aB, aG) in subsets((0, 1, 2), size=3, select="P"):
      
          # "B said B was younger than A"
          if not fB(aB < aA): continue
      
          # "the younger of B and G said that A always told the truth"
          if not (fB if aB < aG else fG)(fA == T): continue
      
          # "A said that G said that A was older than B"
          if not fA(fG(aA > aB)): continue
      
          # map ages to behaviours
          m = dict(zip((aA, aB, aG), (fA, fB, fG)))
      
          # "the eldest said G was more honest than B"
          # "the youngest agreed G was more honest than B"
          s = (honest[fG] > honest[fB])
          if not (m[2](s) and m[0](s)): continue
      
          # output solution
          (A, B, G) = (x.__name__ for x in (fA, fB, fG))
          (a, b, g) = (["youngest", "middle", "eldest"][x] for x in (aA, aB, aG))
          printf("A={A}, {a}; B={B}, {b}; G={G}, {g}")
      

      Solution: Alpha is the eldest. Beta is the most honest.

      Alpha is the eldest, and sometimes tells the truth and sometimes lies.

      Beta is the middle, and always tells the truth.

      Gamma is the youngest, and always lies.

      Like

  • Unknown's avatar

    Jim Randell 1:45 pm on 10 November 2019 Permalink | Reply
    Tags: ,   

    Brainteaser 1742: Not-so-safe deposit 

    From The Sunday Times, 4th February 1996 [link]

    The safe deposit boxes at our bank are arranged in a huge pyramid, the top of which is illustrated:

    When the were first opened the top box had a quantity of gold coins placed in it and the rest were empty. One night a burglar broke into that box, emptied it, and (cleverly, he thought) instead of running off with the coins, he found the two empty boxes in the row below and put some of his haul in one and the rest in the other.

    Word spread through the underworld about the bank’s lax security, and a sequence of burglaries took place. Each burglar broke into one box containing some coins, emptied it, found two empty boxes in the row below, placed some of his haul in one of these boxes and the rest in the other.

    Ages later the manager of the bank opened up the top box and was horrified to find it empty. So he tried the boxes in the next row and found them empty too. He carried on like this until he found some coins in one particular row. In fact, no matter how many burglaries had taken place, there could not have been more empty rows at the top of the pyramid. And indeed if any fewer burglaries had taken place all those rows could not have been empty.

    (a) How many empty rows at the top were there?
    (b) How many burglaries were there?

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

    [teaser1742]

     
    • Jim Randell's avatar

      Jim Randell 1:45 pm on 10 November 2019 Permalink | Reply

      We can try to empty the uppermost rows as quickly as possible.

      Initially, in the top row there is 1 box, initially containing the complete amount:

      [0] 1/1 = 1

      The first burglar breaks in, and splits the amount between the two boxes in the second row. So each of the 2 boxes contains 1/2 of the complete amount:

      [1] 0/1 + 2/2 = 1

      The second burglar breaks in and splits the amount from one of the boxes in the second row between 2 (of the 3) boxes in the third row:

      [2] 0/1 + 1/2 + 2/4 = 1

      The third burglar can’t burgle the remaining box in the second row, as there aren’t enough empty boxes in the third row to hide the spoils. So he splits the amount from one of the boxes in the third row into 2 (of the 4) boxes in the fourth row:

      [3] 0/1 + 1/2 + 1/4 + 2/8 = 1

      The fourth burglar can burgle the box in the second row, as there are now 2 empty boxes in the third row:

      [4] 0/1 + 0/2 + 3/4 + 2/8 = 1

      And so the process continues:

      [5] 0/1 + 0/2 + 2/4 + 4/8 = 1
      [6] 0/1 + 0/2 + 2/4 + 3/8 + 2/16 = 1
      [7] 0/1 + 0/2 + 2/4 + 2/8 + 4/16 = 1
      [8] 0/1 + 0/2 + 1/4 + 4/8 + 4/16 = 1
      [9] 0/1 + 0/2 + 1/4 + 4/8 + 3/16 + 2/32 = 1
      [10] 0/1 + 0/2 + 1/4 + 3/8 + 5/16 + 2/32 = 1
      [11] 0/1 + 0/2 + 1/4 + 3/8 + 4/16 + 4/32 = 1
      [12] 0/1 + 0/2 + 1/4 + 3/8 + 3/16 + 6/32 = 1
      [13] 0/1 + 0/2 + 1/4 + 2/8 + 5/16 + 6/32 = 1
      [14] 0/1 + 0/2 + 0/4 + 4/8 + 5/16 + 6/32 = 1

      So we’ve managed to empty the first 3 rows (in 14 burglaries).

      Can we empty the fourth row?

      What if every row below it was full? We would have:

      S = 5/16 + 6/32 + 7/64 + 8/128 + …
      S = (1/16 + 4/16) + (1/32 + 5/32) + (1/64 + 6/64) + (1/128 + 7/128) + …
      S = (1/16 + 1/32 + 1/64 + 1/128 + …) + (4/16) + (5/32 + 6/64 + 7/128 + …)
      S = (1/8)(1/2 + 1/4 + 1/8 + 1/16 + …) + (1/4) + S/2
      S = (1/8) + (1/4) + S/2
      S/2 = 3/8
      S = 3/4

      We can check this by calculating the sum the first few terms:

      >>> sum(fdiv(n + 1, 2**n) for n in irange(4, 100))
      0.75
      

      So there is not enough room to hide the entire amount using just rows 5 onwards, hence we can never empty row 4.

      Solution: (a) The first 3 rows were empty.

      The number of burglaries required to achieve any situation is one less than the number of occupied boxes.

      And as the fractions of the original collection decrease as we move down the rows we want to occupy the minimal number of boxes possible. If the first 3 rows are empty this minimal number is 4 on the 4th row, 5 on the 5th row, 6 on the 6th row (4/8 + 5/16 + 6/32 = 1), which required 4 + 5 + 6 − 1 = 14 burglaries.

      Solution: (b) There were 14 burglaries.

      Like

      • John Crabtree's avatar

        John Crabtree 9:50 pm on 13 November 2019 Permalink | Reply

        While experimenting with this, I found that one box in row 2 could be replaced by:
        i) a pyramid with one box full in row 4, two in row 5, three in row 6 etc
        or ii) one box full in row 4, and three in all lower rows
        Add the two together, and the grid would be full with two boxes full in row 4, and all all boxes in lower rows. It does not matter if this pattern can actually be reached. Row 4 cannot be empty. it is quite easy to check if the first three rows can be emptied.

        Like

  • Unknown's avatar

    Jim Randell 4:53 pm on 8 November 2019 Permalink | Reply
    Tags:   

    Teaser 2981: Faulty pedometer 

    From The Sunday Times, 10th November 2019 [link] [link]

    Judith is a keen walker who uses a five-digit pedometer to record her number of steps. Her pedometer is inaccurate as some of the counters consistently move on to 0 early by missing out one or more digits. For instance, one of them might roll over from 7 to 0 every time instead of from 7 to 8, missing out digits 8 and 9. She is, however, well aware of this and can work out the correct number of steps.

    After walking her usual distance, the pedometer shows 37225 steps but she knows that the true number is 32% less than this. A second distance she walks requires a 30% reduction in the number displayed to give the true number of steps.

    How many steps is the second distance?

    [teaser2981]

     
    • Jim Randell's avatar

      Jim Randell 7:38 am on 9 November 2019 Permalink | Reply

      Originally I thought the pedometer would be accumulating steps (so the second reading would add some steps to the first reading), but I could not find a solution where the second reading should be reduced by 30% (although I did get an answer where it was reduced by 28%).

      So I considered the problem where the pedometer is reset to zero before the start of each walk, and doesn’t wrap around. This gives a unique solution.

      This Python program finds possible bases for the 32% reduction, and then uses those bases to look for values with a 30% reduction. It runs in 161ms.

      Run: [ @repl.it ]

      from enigma import (div, irange, nconcat, printf)
      
      # find k bases that give a reading of R for actual value N
      def bases(R, N, k, bs=[]):
        if k == 0:
          if R == N:
            yield bs
        else:
          (r, d) = divmod(R, 10)
          for b in irange(d + 1, 10):
            (n, x) = divmod(N, b)
            if x == d:
              yield from bases(r, n, k - 1, bs + [b])
      
      # turn actual value n into a reading according to bases bs
      def reading(n, bs):
        ds = list()
        for b in bs:
          (n, d) = divmod(n, b)
          ds.insert(0, d)
        if n > 9: return
        ds.insert(0, n)
        return nconcat(ds)
      
      # solve the puzzle
      # R = initial reading
      # p = percentage of R for actual reading
      # q = percentage for next reading
      def solve(R, p, q):
        N = div(R * p, 100)
        printf("[reading = {R} -> actual = {N}]")
      
        # compute bases for the first 4 digits
        for bs in bases(R, N, 4):
          printf("[bases = {bs}]")
      
          # find an actual value for the next reading
          for n in irange(1, 99999):
            # calculate the required reading
            r = div(n * 100, q)
            if r is None: continue
            # calculate the reading from the bases
            r2 = reading(n, bs)
            if r2 is None: break
            if r == r2:
              yield (n, r, bs)
      
      # solve for the required parameters
      for (n, r, bs) in solve(37225, 68, 70):
        printf("steps = {n} [reading={r}, bases={bs}]")
      

      Solution: The second distance was 10080 steps.

      The units digit skips 9.

      The tens digit operates normally.

      The hundreds digit skips 9.

      The thousands digit skips 8 and 9.

      The ten thousands digit must be able to read as far as 3 correctly (in order for the initial reading to be 37225).

      So, initially, 25313 steps gives a reading of 37225.

      25313 = (((3 × 8) + 7) × 9 + 2) × 10 + 2) × 9 + 5

      and:

      25313 = 0.68 × 37225

      And then a value of 10080 steps gives a reading of 14400.

      10080 = (((1 × 8) + 4) × 9 + 4) × 10 + 0) × 9 + 0

      and:

      10080 = 0.70 × 14400

      Like

  • Unknown's avatar

    Jim Randell 12:50 pm on 5 November 2019 Permalink | Reply
    Tags: , ,   

    Brain-Teaser 508: [Family birthdays] 

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

    Grandma said: “Grandpa and I share between 100 and 120 years of age, being both over 50 years old. The total ages of our family (our son and his children — all of different age years, and over 12 months) exactly equal [my own age. On the same day next year they will exactly equal] Grandpa’s age.”

    “At present, Grandpa’s age is divisible by the age of only one child, but in one year’s time it will be divisible by the separate ages of three children, and also by the number in our family, while my own age will be divisible by the age of one child only (unity always excluded).”

    “During the year ahead on just one day in May, Grandpa’s age will be divisible by that of our son, whose birthday is earlier.”

    “From Grandpa downwards, what are our ages?”

    The following apology was published with Brain-Teaser 511:

    It is regretted that by a printing error the following words were omitted after “exactly equal”: “my own age. On the same day next year they will exactly equal”.

    I have inserted the missing text in the puzzle above.

    This puzzle was originally published with no title.

    [teaser508]

     
    • Jim Randell's avatar

      Jim Randell 2:22 pm on 5 November 2019 Permalink | Reply

      I wrote a program to solve the puzzle as originally stated (without the additional text) and found 15 solutions.

      So I created a MiniZinc model that makes it easy to play around with variations on the puzzle.

      When the constraints arising from the missing text are incorporated we get a single solution.

      %#! minizinc -a
      
      % grandpa and grandma are aged between 51 and 69
      var 51..69: gp;
      var 51..69: gm;
      
      % but the total is not more than 120
      constraint not(gp + gm > 120);
      
      % son's age
      var 18..51: son;
      
      % kids ages (0 for no kid)
      array[1..10] of var 0..16: kids;
      
      % kids are descending order, and all different
      constraint forall (i in 2..10) (kids[i] > 0 -> kids[i - 1] > kids[i]);
      
      % son + total of kids ages = gm [originally was = gp]
      constraint son + sum(kids) = gm;
      
      % son + total of kids ages in 1 year = gp, so: gp - gm = number of children
      % [originally this condition was omitted]
      constraint sum (i in 1..10) (kids[i] > 0) = gp - gm;
      
      % exactly 1 kid divides gp
      constraint sum (i in 1..10) (kids[i] > 1 /\ gp mod kids[i] = 0) = 1;
      
      % but exactly 3 kids divide gp in 1 year
      constraint sum (i in 1..10) (kids[i] > 0 /\ (gp + 1) mod (kids[i] + 1) = 0) = 3;
      
      % and exactly 1 kid divides gm in 1 year
      constraint sum (i in 1..10) (kids[i] > 0 /\ (gm + 1) mod (kids[i] + 1) = 0) = 1;
      
      % also the number of kids + 1 divides into gp in 1 year
      constraint (gp + 1) mod (1 + sum (i in 1..10) (kids[i] > 0)) = 0;
      
      % (son + 1) divides gp
      constraint gp mod (son + 1) = 0;
      
      % at least 16 years between generations
      constraint son + 16 < min([gp, gm]);
      constraint max(kids) + 16 < son;
      
      solve satisfy;
      

      And we can format the output with Python 3 by using the minizinc.py wrapper:

      from minizinc import MiniZinc
      
      for (gp, gm, son, kids) in MiniZinc("teaser508.mzn").solve(result="gp gm son kids"):
        kids = list(x for x in kids.values() if x > 0)
        print(f"Grandpa = {gp}; Grandma = {gm}; Son = {son}; Children = {kids}")
      

      So the answer to the corrected puzzle is:

      Solution: Grandpa = 62; Grandma = 56; Son = 30; Children = 8, 6, 5, 4, 2, 1.

      Without the constraints to enforce a 16 year gap between generations we can have a further solution (with 7 children):

      Grandpa = 63; Grandma = 56; Son = 20; Children = 15, 6, 5, 4, 3, 2, 1

      Like

  • Unknown's avatar

    Jim Randell 2:46 pm on 4 November 2019 Permalink | Reply
    Tags:   

    Teaser 2790: Factoidels 

    From The Sunday Times, 13th March 2016 [link] [link]

    In order to introduce the class to “lowest common denominators”,  Amelia’s teacher asked them to find the lowest number with the property that each of 1, 2, 3, 4 and 5 divided exactly into it. They correctly calculated the answer as 60, and the teacher called this the “factoidel” of 5. He went on to demonstrate that the factoidel of 6 was also 60. Being keen, Amelia investigated factoidels at home and then told the teacher that she had found the highest set of four consecutive numbers whose factoidels are all different (at least she had cleverly checked well into the billions).

    What were those four consecutive numbers?

    [teaser2790]

     
    • Jim Randell's avatar

      Jim Randell 2:47 pm on 4 November 2019 Permalink | Reply

      It’s easy enough to write a program that looks for sequences of 4 consecutive distinct “factoidels”. And this finds the supposed “largest” solution very quickly, but how do we know when to stop looking?

      The sequence of “factoidels” is:

      A[n] = lcm(1, 2, …, n)

      If we consider the ratio of consecutive terms:

      B[n] = A[n] / A[n − 1]
      where n > 1

      then:

      B[n] = p, if n = p^k for some prime p and integer k
      B[n] = 1, otherwise

      (See OEIS A014963) [ @oeis.org ]

      To find 4 consecutive terms in A[] that are all different, we need to find 3 consecutive terms in B[] that are non-unity. i.e. three consecutive integers that are powers of primes.

      So suppose the numbers we are looking for are: (n, n + 1, n + 2).

      If n is even, i.e. n = 2^k, then (n + 2) is also even, and must be a power of two:

      n + 2 = 2^k + 2 = 2^(k + 1)

      The only solution is k = 1, (n, n + 1, n + 2) = (2, 3, 4), which give the following terms from the sequences:

      B[2] = 2, B[3] = 3, B[4] = 2
      A[1] = 1, A[2] = 2, A[3] = 6, A[4] = 12

      If n is odd, then n + 1 = 2^k.

      k = 1 gives no sequence in B[].

      If k = 2 we have (n, n + 1, n + 2) = (3, 4, 5), which gives the following terms:

      B[3] = 3, B[4] = 2, B[5] = 5
      A[2] = 2, A[3] = 6, A[4] = 12, A[5] = 60

      If k =3 we have (n, n + 1, n + 2) = (7, 8, 9), which gives the following terms:

      B[7] = 7, B[8] = 2, B[9] = 3
      A[6] = 60, A[7] = 420, A[8] = 840, A[9] = 2520

      In general for k ≥ 4 we have (n, n + 1, n + 2) = (2^k – 1, 2^k, 2^k + 1).

      Catalan’s Conjecture (now a proved theorem) [ @wikipedia.org ] tells us that the only two consecutive numbers that are non-trivial powers are 8 and 9, so there is only a solution if (2^k − 1) and (2^k + 1) are both primes (otherwise we have another consecutive pair of non-trivial powers).

      But all twin primes apart from (3, 5) are of the form (6n − 1, 6n + 1), so we won’t find a larger solution no matter how far we go.

      Hence the three solutions found above are the only solutions, and it is sufficient to check up to n = 9

      Run: [ @replit ]

      from enigma import (irange, lcm, printf)
      
      (f, s) = (1, [])
      for n in irange(1, 9):
        f = lcm(f, n)
        s.append(f)
        if len(s) > 4: s.pop(0)
        if len(set(s)) == 4:
          printf("{ns} -> {s}", ns=tuple(irange(n - 3, n)))
      

      Solution: The four consecutive numbers are: 6, 7, 8, 9.

      Even without all the maths, computationally, with a simple prime test, we can very quickly (less than a second) verify that there are no twin primes of the form (2^k ± 1) for 2 < k ≤ 50. And this is certainly enough to replicate Amelia’s checks.

      Like

  • Unknown's avatar

    Jim Randell 4:32 pm on 1 November 2019 Permalink | Reply
    Tags: ,   

    Teaser 2980: Egyptian weights and measures 

    From The Sunday Times, 3rd November 2019 [link] [link]

    We were wondering why ancient Egyptians chose to represent arbitrary fractions as sums of distinct unit fractions of the form 1/n (thus 5/7 = 1/2 + 1/5 + 1/70). One of us recalled long ago watching our greengrocer use four brass weights of 1/2, 1/4, 1/8, 1/16 lb to weigh any number of ounces up to 15 by stacking some of them on one side of her balancing scales. We wanted to make a metric equivalent, a set of distinct weights of unit fractions of a kilo, each weighing a whole number of grams, to weigh in 10g steps up to 990g.

    Naturally, we wanted to use as little brass as possible, but we found that there were several possible such sets. Of these, we chose the set containing the fewest weights.

    List, in increasing order, the weights in our set.

    [teaser2980]

     
    • Jim Randell's avatar

      Jim Randell 5:03 pm on 1 November 2019 Permalink | Reply

      Possible weights are the divisors of 1000.

      This Python program looks for subsets that permit all the required weights to be made, and then selects those sets with the minimal possible weight. It runs in 501ms.

      Run: [ @repl.it ]

      from enigma import (divisors, inf, subsets, printf)
      
      # find weights that are unit fractions of 1000
      weights = divisors(1000)
      weights.remove(1000)
      
      # find minimal weight sets of weights that can be used to weigh all
      # multiples of 10 from 10 to 990
      (w, rs) = (inf, list())
      
      # choose a set of weights
      for ws in subsets(weights, min_size=7):
        k = sum(ws)
        if k < 990 or k > w: continue
      
        # collect amounts
        amounts = set()
        # choose a subset of the weights to use
        for s in subsets(ws, min_size=1):
          # calculate the total weight
          t = sum(s)
          # is it a multiple of 10 between 10 and 990?
          if 9 < t < 991 and t % 10 == 0:
            amounts.add(t)
      
        # can we make 99 different amounts?
        if len(amounts) == 99:
          if k < w: (w, rs) = (k, list())
          rs.append(ws)
          printf("[{k} = {ws} ({n})]", n=len(ws))
      
      # and find the minimum number weights in the set
      n = min(len(ws) for ws in rs)
      
      # output solutions
      for ws in rs:
        if len(ws) == n:
          printf("min = {w}; set of {n} weights = {ws}")
      

      Solution: There are 10 weights in the set: 2g, 5g, 8g, 10g, 25g, 40g, 50g, 100g, 250g, 500g.

      The total weight of the set is 990g (which is obviously the minimum possible total to be able to weigh up to 990g).

      There are 4 viable sets of weights that have a total weight of 990g:

      set of 10 weights = (2, 5, 8, 10, 25, 40, 50, 100, 250, 500)
      set of 11 weights = (1, 2, 4, 8, 10, 25, 40, 50, 100, 250, 500)
      set of 12 weights = (1, 2, 4, 5, 8, 10, 20, 40, 50, 100, 250, 500)
      set of 13 weights = (1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 125, 200, 500)


      When I initially read the puzzle I solved it allowing weights to be placed on both sides of the scales, and I found two sets of 7 weights which can be used to achieve the required values, where both sets weigh 990g in total:

      set of 7 weights = (5, 20, 40, 50, 125, 250, 500)
      set of 7 weights = (5, 20, 40, 100, 125, 200, 500)

      Like

  • Unknown's avatar

    Jim Randell 10:08 am on 31 October 2019 Permalink | Reply
    Tags:   

    Brain-Teaser 507: [Colourful buses] 

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

    The Colourful Bus Company serves the five towns of Antwich, Bearton, Catville, Dogchester and Eagleham. The towns are joined by seven roads as shown on the map.

    Each bus of the company is either Red or Blue or Green or Yellow. Each of the seven roads is covered in one direction only and by one colour of bus only.

    I spent three days recently riding round the district on the buses, each day staring at Antwich and finishing at Eagleham. My journeys were on the following buses in order:

    Day 1: Red, Blue, Yellow, Blue, Green, Yellow, ???
    Day 2: Green, Red, Blue, Green, Red, Blue, Red, ???
    Day 3: Green, Yellow, Blue, Yellow, Blue, Red, ???

    I forgot to note the colour of the last bus each day.

    If a bus runs from Antwich to Bearton, what is the direction and colour of the bus between Catville and Dogchester?

    This puzzle was originally published with no title.

    [teaser507]

     
    • Jim Randell's avatar

      Jim Randell 10:10 am on 31 October 2019 Permalink | Reply

      This Python 3 program runs in 77ms.

      Run: [ @repl.it ]

      from enigma import update, join, printf
      
      # the towns, the buses, the roads
      towns = (A, B, C, D, E) = list("ABCDE")
      buses = "rbgy" 
      roads = {
        A: (B, C, D),
        B: (A, C, E),
        C: (A, B, D),
        D: (A, C, E),
        E: (B, D),
      }
      
      # follow a journey
      # bs = buses remaining to take
      # p = current location
      # t = target location
      # m = current map
      # return (map, towns)
      def journey(bs, p, t, m, s=None):
        if s is None: s = [p]
        # are we done?
        if not bs:
          # can we reach the target in one hop?
          if t in roads[p]:
            # is the road already on the map
            if (p, t) in m:
              yield (m, s + [t])
            # can we add it to the map?
            elif (t, p) not in m:
              yield (update(m, [(p, t)], ['?']), s + [t])
        else:
          # choose an onward road
          for x in roads[p]:
            # is it already in the map?
            if (p, x) in m:
              # and is it the right colour?
              if m[(p, x)] == bs[0]:
                # solve for the remaining buses
                yield from journey(bs[1:], x, t, m, s + [x])
              elif m[(p, x)] == '?':
                yield from journey(bs[1:], x, t, update(m, [(p, x)], [bs[0]]), s + [x])
            # can we add it to the map?
            elif (x, p) not in m:
              # solve for the remaining buses
              yield from journey(bs[1:], x, t, update(m, [(p, x)], [bs[0]]), s + [x])
      
      # solve for a collection of journeys between s and t
      def solve(s, t, js, m={}, ss=[]):
        # are we done?
        if not js:
          yield (m, ss)
        else:
          # solve for the first journey
          for (m1, s1) in journey(js[0], s, t, m):
            # and then for the remaining journeys
            yield from solve(s, t, js[1:], m1, ss + [s1])
      
      # solve the journeys
      for (m, ss) in solve(A, E, ["rbybgy", "grbgrbr", "gybybr"], { (A, B): '?' }):
        # output solution
        printf("map: {m}", m=join((join((k[0], '->', k[1], ' = ', v)) for (k, v) in m.items()), sep="; "))
        for (i, s) in enumerate(ss, start=1):
          printf("day {i}: {s}", s=join(s, sep=" -> "))
        printf()
      

      Solution: The bus that runs from Catville to Dogchester is red.

      The bus routes are as shown in the following diagram:

      The journeys are:

      Day 1: A → B → E → D → A → C → B → E
      Day 2: A → C → D → A → C → D → A → B → E
      Day 3: A → C → B → E → D → A → B → E

      This is the only solution with a bus running from A to B. However if the bus ran from B to A the solution would be:

      A → C = green
      A → D = red
      B → A = blue
      C → B = red
      C → D = yellow
      D → E = blue
      E → B = yellow

      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