Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:35 am on 31 October 2021 Permalink | Reply
    Tags: by: R. Crosbie-Hill   

    A Brain-Teaser: [Multiples of 11] 

    From The Sunday Times, 9th June 1957 [link]

    In how many different ways can the following groups of digits be arranged so that the resultant number is a multiple of 11?

    (a) 1, 2, 3, 4, 5.
    (b) 1, 2, 3, 4, 5, 6.
    (c) 1, 2, 3, 4, 5, 6, 7.

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of £5 was offered.

    This puzzle was originally published with no title.

    [teaser-1957-06-09] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:36 am on 31 October 2021 Permalink | Reply

      Again, the advent of computers means that this problem can be tackled with a very simple program, that constructs all possible rearrangements of the digits, and counts those that are divisible by 11.

      The following Python program runs in 56ms.

      Run: [ @replit ]

      from enigma import (subsets, nconcat, icount, printf)
      
      # find numbers that are multiples of 11
      def m11s(ds):
        for ss in subsets(ds, size=len, select="P"):
          n = nconcat(ss)
          if n % 11 == 0:
            yield n
      
      for ds in [(1, 2, 3, 4, 5), (1, 2, 3, 4, 5, 6), (1, 2, 3, 4, 5, 6, 7)]:
        t = icount(m11s(ds))
        printf("{ds} -> {t}")
      

      Solution: (a) 0; (b) 0; (c) 576.

      We can be a bit cleverer though.

      One test for divisibility by 11 is to calculate the alternating sum of the digits (i.e. 1st digit − 2nd digit + 3rd digit − 4th digit + …), and if this is divisible by 11, then the original number is divisible by 11. And it also means that any number formed by rearrangement of the digits, providing the digits originally in odd positions are still in odd positions (and the digits in even positions are still in even positions), will also be divisible by 11.

      The following Python program is a longer, but more efficient method of counting the numbers. (And indeed the internal runtime decreases from 9.03ms to 151µs).

      from enigma import (subsets, factorial, printf)
      
      # count the number of multiples of 11 that can be made from digits ds
      def count_m11s(ds):
        r = 0
        t = sum(ds)
        # consider the 2nd, 4th, ... digits
        n = len(ds)
        k = n // 2
        for ss in subsets(ds, size=k):
          if (t - 2 * sum(ss)) % 11 == 0:
            r += factorial(k) * factorial(n - k)
        return r
      
      for ds in [(1, 2, 3, 4, 5), (1, 2, 3, 4, 5, 6), (1, 2, 3, 4, 5, 6, 7)]:
        t = count_m11s(ds)
        printf("{ds} -> {t}")
      

      In the case of the digits (1, 2, 3, 4, 5, 6, 7), we find that the following numbers in (odd) and (even) positions will form multiples of 11:

      (2, 3, 4, 5) + (1, 6, 7)
      (1, 3, 4, 6) + (2, 5, 7)
      (1, 2, 5, 6) + (3, 4, 7)
      (1, 2, 4, 7) + (3, 5, 6)

      Each collection of digits contributes factorial(4) × factorial(3) = 144 numbers to the total.

      So the answer is: 4 × 144 = 576.

      Like

    • GeoffR's avatar

      GeoffR 12:37 pm on 31 October 2021 Permalink | Reply

      from itertools import permutations
      
      cnt5, cnt6, cnt7 = 0, 0, 0
      
      # 5 consecutive digits - divisibility by 11
      for p1 in permutations((1,2,3,4,5)):
          a,b,c,d,e = p1
          num1 = 10000*a + 1000*b + 100*c + 10*d + e
          if num1 % 11 == 0: cnt5 += 1
      
      print('5 digits divisibility count = ', cnt5)
      
      # 6 consecutive digits - divisibility by 11
      for p2 in permutations((1,2,3,4,5,6)):
          a,b,c,d,e,f = p2
          num2 = 100000*a + 10000*b + 1000*c + 100*d + 10*e + f
          if num2 % 11 == 0: cnt6 += 1
      
      print('6 digits divisibility count = ', cnt6)
      
      # 7 consecutive digits - divisibility by 11
      for p3 in permutations((1,2,3,4,5,6,7)):
          a,b,c,d,e,f,g = p3
          num3 = 1000000*a + 100000*b + 10000*c + 1000*d + 100*e + 10*f + g
          if num3 % 11 == 0: cnt7 += 1
      
      print('7 digits divisibility count = ', cnt7)
      
      # 5 digits divisibility count =  0
      # 6 digits divisibility count =  0
      # 7 digits divisibility count =  576
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:05 pm on 29 October 2021 Permalink | Reply
    Tags:   

    Teaser 3084: Face value 

    From The Sunday Times, 31st October 2021 [link] [link]

    Plato: I have written a different whole number (chosen from 1 to 9 inclusive) on each of the faces of one of my regular solids and have labelled each vertex with the product of the numbers on its adjacent faces. If I tell you the sum of those eight vertex labels, you can’t deduce my numbers, but if I rearrange the numbers on the faces and tell you the new sum, then you can deduce the numbers.

    Eudoxus: Tell me the new sum then.

    Plato: No, but I’ll tell you it’s a 10th power.

    Eudoxus: Aha! I know your numbers now.

    Plato: Yes, that’s right. But if I now told you the original sum, you couldn’t work out which numbers were originally opposite each other.

    What was the original sum?

    [teaser3084]

     
    • Jim Randell's avatar

      Jim Randell 5:01 pm on 29 October 2021 Permalink | Reply

      The cube is the only Platonic Solid [@wikipedia] with 8 vertices.

      This Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (irange, inf, subsets, partitions, group, unpack, peek, powers, printf)
      
      # generate face pairs and vertex sum for a set of numbers
      def arrange(ns):
        for fs in partitions(ns, 2, distinct=1):
          ((U, D), (L, R), (F, B)) = fs
          # calculate the values on the vertices
          vs = (
            U * L * F, U * R * F, U * L * B, U * R * B,
            D * L * F, D * R * F, D * L * B, D * R * B,
          )
          yield (ns, fs, sum(vs))
      
      # generate all possible face pairs and vertex sums
      def generate():
        for ns in subsets(irange(1, 9), size=6):
          yield from arrange(ns)
      
      # map vertex sum to sets of numbers
      d = group(generate(), by=unpack(lambda ns, fs, t: t), f=unpack(lambda ns, fs, t: ns), fn=set)
      
      # look for vertex sums that are 10th powers
      m = max(d.keys())
      for k in powers(1, inf, 10):
        if k > m: break
        vs = d.get(k, None)
        if vs is None: continue
        printf("{k} -> {vs}")
        assert len(vs) == 1
      
        # map vertex sums (for this set of numbers) to face pairs
        r = group(arrange(peek(vs)), by=unpack(lambda ns, fs, t: t), f=unpack(lambda ns, fs, t: fs))
      
        # look for sums with multiple face pairs (and multiple number sets)
        for (t, fs) in r.items():
          if not (len(fs) > 1 and len(d[t]) > 1): continue
          printf("  {t} -> {fs}")
      

      Solution: Plato’s original labelling gave a vertex sum of 1188.

      There are many ways to achieve this, so we couldn’t tell which set of numbers he used:

      (1+8)(2+9)(5+7)
      (1+8)(3+9)(4+7)
      (1+8)(3+9)(5+6)
      (2+9)(3+6)(4+8)
      (2+7)(3+9)(5+6)
      (2+9)(3+6)(5+7)
      (2+7)(4+8)(5+6)

      But he then rearranged his numbers to give a vertex sum of 1024, which can only be done in one way:

      (2+6)(3+5)(7+9)

      So we know he used the numbers (2, 3, 5, 6, 7, 9).

      He then tells us that even though we know the numbers, we still can’t tell his original arrangement.

      This is because there are two ways to achieve 1188 using this set of numbers:

      (2+7)(3+9)(5+6)
      (2+9)(3+6)(5+7)

      And this is the only set of numbers for 1188 that has more than one arrangement.


      Manually, noting:

      (U + D)(L + R)(F + B) = ULF + ULB + URF + URB + DLF + DLB + DRF + DRB

      gives us a quick way to calculate the vertex sum. It is the same as the product of the three sums of opposite face pairs.

      And this lets us simplify the arrange() function above:

      from enigma import multiply
      def arrange(ns):
        for fs in partitions(ns, 2, distinct=1):
          yield (ns, fs, multiply(x + y for (x, y) in fs))
      

      And we see that the only possible value for the 10th power is 2^10 = 1024.

      So the values on opposite faces must sum to powers of 2:

      (1, 3) = 2^2
      (1, 7) = 2^3
      (2, 6) = 2^3
      (3, 5) = 2^3
      (7, 9) = 2^4

      The only arrangement that gives 1024 is:

      (2, 6) (3, 5) (7, 9)

      We can then look for 2 different arrangements of these numbers into pairs that give the same vertex sum. And 1188 is the only possibility.

      Like

      • Frits's avatar

        Frits 11:54 am on 30 October 2021 Permalink | Reply

        @Jim, len(vs) is likely to be 1 but I am not sure if it can be used for checking if the numbers can be deduced.

        Like

        • Jim Randell's avatar

          Jim Randell 12:03 pm on 30 October 2021 Permalink | Reply

          Yes, we can check the values in vs all use the same numbers:

          vs = set(tuple(sorted(flatten(v))) for v in vs)
          assert len(vs) == 1
          

          And then ns is just this single value.

          I’ll update my code.

          (In fact, I’ve rewritten it to be a bit faster and more compact).

          Like

    • Frits's avatar

      Frits 11:19 am on 30 October 2021 Permalink | Reply

        
      from enigma import SubstitutedExpression, tri
      
      ndigits = 9
      nfaces = 6
      npairs = nfaces // 2
      # highest possible sum
      h = ((tri(ndigits) - tri(ndigits - nfaces)) / npairs) ** npairs
      powers = [i for i in range(2, int(h ** 0.1) + 1)]
      
      # based on the entries in <powers> we can easily deduct the digits used
      # in the new sum (in this case the highest pair sum must be a 4th power)
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # find 2 different arrangements with same sum
          "A < C < E",                    # order the pairs
          "A < B and C < D and E < F",    # order within the pairs
          "G < I < K",                    # order the pairs
          "G < H and I < J and K < L",    # order within the pairs
          
          # different arrangements
          "(A, B, C, D, E, F) < (G, H, I, J, K, L)",
          
          # same sum
          "(A + B) * (C + D) * (E + F) == (G + H) * (I + J) * (K + L)",
        ],
        answer="((A, B), (C, D), (E, F)), ((G, H), (I, J), (K, L)), \
                (A + B) * (C + D) * (E + F)",
        # from manual analysis        
        digits=(2, 3, 5, 6, 7, 9),
        distinct=("ABCDEF","GHIJKL"),
        verbose=0,
      )
      
      # print answers
      for (_, ans) in p.solve():
        print(f"the original sum was {ans[2]}")
      

      Like

      • Jim Randell's avatar

        Jim Randell 1:11 pm on 30 October 2021 Permalink | Reply

        Or you could do:

        from enigma import (partitions, multiply, filter_unique, printf)
        
        # possible face pairs
        pairs = partitions((2, 3, 5, 6, 7, 9), 2, distinct=1)
        
        # calculate vertex sum
        fn = lambda fs: multiply(x + y for (x, y) in fs)
        
        # find vertex sums with multiple face pairs
        for fs in filter_unique(pairs, fn).non_unique:
          printf("{t} -> {fs}", t=fn(fs))
        

        Like

    • GeoffR's avatar

      GeoffR 9:31 am on 5 November 2021 Permalink | Reply

      from enigma import all_different
      from itertools import combinations, permutations
      from collections import defaultdict
      
      D3084 = defaultdict(list)
      
      # If the cube sides are (L1, L2, R1, R2, U, D), the sum
      # of eight vertices = (L1 + L2) * (R1 + R2) * (U + D)
      
      # Since 2 ^ 10 = 1024 and 3 ^ 10 = 59049 and the maximum
      # vertices sum for face digits (1..9) = 2805 (17*15*11)
      # ... the 10th power must be 2 ^ 10 = 1024
      
      # find cube face values for 10th power = 1024
      for p1 in permutations(range(1, 10), 6):
        L1, L2, R1, R2, U, D = p1
        if (L1 + L2) * (R1 + R2) * (U + D) == 1024:
          set_faces = set((L1, L2, R1, R2, U, D))
      
      face_pairs = list(combinations(set_faces, 2))
      
      # map six cube face values to sum of eight vertices
      for A, B, C in combinations(face_pairs, 3):
        if all_different(A[0], A[1], B[0], B[1], C[0], C[1]):
          # digits must be same as 10th power of 1024
          if {A[0], A[1], B[0], B[1], C[0], C[1]} == set_faces:
            VSum = (A[0] + A[1]) * (B[0] + B[1]) * (C[0] + C[1])
            D3084[VSum].append(((A[0], A[1]), (B[0], B[1]), (C[0], C[1])))
      
      for k, v in D3084.items():
        # original number must have non-unique face values
        if len(v) > 1:
          print(f"The original number was {k}")
          print(f"Face Values are {v}")
      
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 9:53 am on 8 November 2021 Permalink | Reply

      This programme ran in 300ms with the Chuffed Solver, but took much longer with the Geocode Solver. I had a constraint to limit the set length of 15 values to 14 (i.e. 1 No. duplicated), but it proved not necessary in practice, and increased the run time substantially to 9.5s. This constraint is commented out in the programme.

      The tenth power of two is shown in the output after the code, as is the original sum of 1188 (i.e. the answer), for two instances.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % min vertex sum = (1+2) * (3+4) * (5+6) = 231
      % max vertex sum = (9+8) * (7+6) * (5+4) = 1989
      % ..as 2^10 = 1024 and 3^10 = 59049, therefore 1024 is the only
      % possible 10th power within min and max sum limits
      
      % the cube has six faces with different digits 1..9
      var 1..9: a; var 1..9: b; var 1..9: c;
      var 1..9: d; var 1..9: e; var 1..9: f;
      
      constraint all_different([a, b, c, d, e, f]);
      
      % N1..N15 are all the cube possible vertex totals
      var 231..1989: N1; var 231..1989:N2; var 231..1989:N3;
      var 231..1989: N4; var 231..1989:N5; var 231..1989:N6;
      var 231..1989: N7; var 231..1989:N8; var 231..1989:N9;
      var 231..1989: N10; var 231..1989:N11; var 231..1989:N12;
      var 231..1989: N13; var 231..1989:N14; var 231..1989:N15;
      
      % the fifteen possible vertex totals,
      % from six of the possible nine digits 1..9
      constraint N1 == (a+b) * (c+d) * (e+f);
      constraint N2 == (a+b) * (c+e) * (d+f);
      constraint N3 == (a+b) * (c+f) * (d+e);
      
      constraint N4 == (a+c) * (b+d) * (e+f);
      constraint N5 == (a+c) * (b+e) * (d+f);
      constraint N6 == (a+c) * (b+f) * (d+e);
      
      constraint N7 == (a+d) * (b+c) * (e+f);
      constraint N8 == (a+d) * (b+e) * (c+f);
      constraint N9 == (a+d) * (b+f) * (c+e);
      
      constraint N10 == (a+e) * (b+c) * (d+f);
      constraint N11 == (a+e) * (b+d) * (c+f);
      constraint N12 == (a+e) * (b+f) * (c+d);
      
      constraint N13 == (a+f) * (b+c) * (d+e);
      constraint N14 == (a+f) * (b+d) * (c+e);
      constraint N15 == (a+f) * (b+e) * (c+d);
      
      % One of the vertex totals is a 10th power i.e.2 ^ 10 = 1024
      % ...this constraint fixes the six digits for the solution
      constraint sum([
      N1 == 1024, N2 == 1024, N3 == 1024, N4 == 1024, N5 == 1024,
      N6 == 1024, N7 == 1024, N8 == 1024, N9 == 1024, N10 == 1024,
      N11 == 1024, N12 == 1024, N13 == 1024, N14 == 1024, N15 == 1024
      ]) == 1;
                      
      % constraint card ({N1, N2, N3, N4, N5, N6, N7, N8,
      % N9, N10, N11, N12, N13, N14,  N15}) == 14;
                      
      solve satisfy;
      
      output["N1 = " ++ show(N1) ++ ", sides: " ++ show([a,b]) 
      ++ ", " ++ show([c,d]) ++ ", " ++ show([e,f]) 
      ++ "\nN2 = " ++ show(N2) ++ ", sides: " ++ show([a,b]) 
      ++ ", " ++ show([c,e]) ++ ", " ++ show([d,f])
      ++ "\nN3 = " ++ show(N3) ++ ", sides: " ++ show([a,b]) 
      ++ ", " ++ show([c,f]) ++ ", " ++ show([d,e])
      
      ++ "\nN4 = " ++ show(N4) ++ ", sides: " ++ show([a,c]) 
      ++ ", " ++ show([b,d]) ++ ", " ++ show([e,f])
      ++ "\nN5 = " ++ show(N5) ++ ", sides: " ++ show([a,c]) 
      ++ ", " ++ show([b,e]) ++ ", " ++ show([d,f])
      ++ "\nN6 = " ++ show(N6) ++ ", sides: " ++ show([a,c])
       ++ ", " ++ show([b,f]) ++ ", " ++ show([d,e])
      
      ++ "\nN7 = " ++ show(N7) ++ ", sides: " ++ show([a,d]) 
      ++ ", " ++ show([b,c]) ++ ", " ++ show([e,f])
      ++ "\nN8 = " ++ show(N8) ++ ", sides: " ++ show([a,d]) 
      ++ ", " ++ show([b,e]) ++ ", " ++ show([c,f])
      ++ "\nN9 = " ++ show(N9) ++ ", sides: " ++ show([a,d]) 
      ++ ", " ++ show([b,f]) ++ ", " ++ show([c,e])
      
      ++ "\nN10 = " ++ show(N10) ++ ", sides: " ++ show([a,e])
      ++ ", " ++ show([b,c]) ++ ", " ++ show([d,f])
      ++ "\nN11 = " ++ show(N11) ++ ", sides: " ++ show([a,e])
      ++ ", " ++ show([b,d]) ++ ", " ++ show([c,f])
      ++ "\nN12 = " ++ show(N12) ++ ", sides: " ++ show([a,e])
      ++ ", " ++ show([b,f]) ++ ", " ++ show([c,d])
      
      ++ "\nN13 = " ++ show(N13) ++ ", sides: " ++ show([a,f]) 
      ++ ", " ++ show([b,c]) ++ ", " ++ show([d,e])
      ++ "\nN14 = " ++ show(N14) ++ ", sides: " ++ show([a,f]) 
      ++ ", " ++ show([b,d]) ++ ", " ++ show([c,e])
      ++ "\nN15 = " ++ show(N15) ++ ", sides: " ++ show([a,f]) 
      ++ ", " ++ show([b,e]) ++ ", " ++ show([c,d])
      ];
      
      % 15 Possible vertex totals for 6 sides of a cube
      % N1 = 1210, sides: [3, 7], [9, 2], [5, 6]
      % N2 = 1120, sides: [3, 7], [9, 5], [2, 6]
      % N3 = 1050, sides: [3, 7], [9, 6], [2, 5]
      % N4 = 1188, sides: [3, 9], [7, 2], [5, 6]   <<< original sum = 1188 (answer)
      % N5 = 1152, sides: [3, 9], [7, 5], [2, 6]
      % N6 = 1092, sides: [3, 9], [7, 6], [2, 5]
      % N7 = 880, sides: [3, 2], [7, 9], [5, 6]
      % N8 = 900, sides: [3, 2], [7, 5], [9, 6]
      % N9 = 910, sides: [3, 2], [7, 6], [9, 5]
      % N10 = 1024, sides: [3, 5], [7, 9], [2, 6]   <<< 10th power = 1024 (2^10)
      % N11 = 1080, sides: [3, 5], [7, 2], [9, 6]
      % N12 = 1144, sides: [3, 5], [7, 6], [9, 2]
      % N13 = 1008, sides: [3, 6], [7, 9], [2, 5]
      % N14 = 1134, sides: [3, 6], [7, 2], [9, 5]
      % N15 = 1188, sides: [3, 6], [7, 5], [9, 2]   <<< original sum = 1188 (answer)
      % ----------
      
      
      
      

      Like

      • Frits's avatar

        Frits 12:47 pm on 9 November 2021 Permalink | Reply

        The all_different clause has been replaced by a < b < c < d < e < f.
        Also the card statement has been changed to less equal to 14.

        Now the Geocode solver takes less than half a second (printing all solutions) .
        Chuffed still takes 11 seconds.

         
        % A Solution in MiniZinc
        include "globals.mzn";
         
        % min vertex sum = (1+2) * (3+4) * (5+6) = 231
        % max vertex sum = (9+8) * (7+6) * (5+4) = 1989
        % ..as 2^10 = 1024 and 3^10 = 59049, therefore 1024 is the only
        % possible 10th power within min and max sum limits
         
        % the cube has six faces with different digits 1..9
        var 1..4: a; var 2..5: b; var 3..6: c;
        var 4..7: d; var 5..8: e; var 6..9: f;
         
        constraint a < b /\ b < c /\ c < d /\ d < e /\ e < f;
         
        % N1..N15 are all the cube possible vertex totals
        var 231..1989: N1; var 231..1989:N2; var 231..1989:N3;
        var 231..1989: N4; var 231..1989:N5; var 231..1989:N6;
        var 231..1989: N7; var 231..1989:N8; var 231..1989:N9;
        var 231..1989: N10; var 231..1989:N11; var 231..1989:N12;
        var 231..1989: N13; var 231..1989:N14; var 231..1989:N15;
         
        % the fifteen possible vertex totals,
        % from six of the possible nine digits 1..9
        constraint N1 == (a+b) * (c+d) * (e+f);
        constraint N2 == (a+b) * (c+e) * (d+f);
        constraint N3 == (a+b) * (c+f) * (d+e);
         
        constraint N4 == (a+c) * (b+d) * (e+f);
        constraint N5 == (a+c) * (b+e) * (d+f);
        constraint N6 == (a+c) * (b+f) * (d+e);
         
        constraint N7 == (a+d) * (b+c) * (e+f);
        constraint N8 == (a+d) * (b+e) * (c+f);
        constraint N9 == (a+d) * (b+f) * (c+e);
         
        constraint N10 == (a+e) * (b+c) * (d+f);
        constraint N11 == (a+e) * (b+d) * (c+f);
        constraint N12 == (a+e) * (b+f) * (c+d);
         
        constraint N13 == (a+f) * (b+c) * (d+e);
        constraint N14 == (a+f) * (b+d) * (c+e);
        constraint N15 == (a+f) * (b+e) * (c+d);
         
        % One of the vertex totals is a 10th power i.e.2 ^ 10 = 1024
        % ...this constraint fixes the six digits for the solution
        constraint sum([
        N1 == 1024, N2 == 1024, N3 == 1024, N4 == 1024, N5 == 1024,
        N6 == 1024, N7 == 1024, N8 == 1024, N9 == 1024, N10 == 1024,
        N11 == 1024, N12 == 1024, N13 == 1024, N14 == 1024, N15 == 1024
        ]) == 1;
                         
        constraint card ({N1, N2, N3, N4, N5, N6, N7, N8,
         N9, N10, N11, N12, N13, N14,  N15}) <= 14;
                         
        solve satisfy;
         
        output["N1 = " ++ show(N1) ++ ", sides: " ++ show([a,b]) 
        ++ ", " ++ show([c,d]) ++ ", " ++ show([e,f]) 
        ++ "\nN2 = " ++ show(N2) ++ ", sides: " ++ show([a,b]) 
        ++ ", " ++ show([c,e]) ++ ", " ++ show([d,f])
        ++ "\nN3 = " ++ show(N3) ++ ", sides: " ++ show([a,b]) 
        ++ ", " ++ show([c,f]) ++ ", " ++ show([d,e])
         
        ++ "\nN4 = " ++ show(N4) ++ ", sides: " ++ show([a,c]) 
        ++ ", " ++ show([b,d]) ++ ", " ++ show([e,f])
        ++ "\nN5 = " ++ show(N5) ++ ", sides: " ++ show([a,c]) 
        ++ ", " ++ show([b,e]) ++ ", " ++ show([d,f])
        ++ "\nN6 = " ++ show(N6) ++ ", sides: " ++ show([a,c])
         ++ ", " ++ show([b,f]) ++ ", " ++ show([d,e])
         
        ++ "\nN7 = " ++ show(N7) ++ ", sides: " ++ show([a,d]) 
        ++ ", " ++ show([b,c]) ++ ", " ++ show([e,f])
        ++ "\nN8 = " ++ show(N8) ++ ", sides: " ++ show([a,d]) 
        ++ ", " ++ show([b,e]) ++ ", " ++ show([c,f])
        ++ "\nN9 = " ++ show(N9) ++ ", sides: " ++ show([a,d]) 
        ++ ", " ++ show([b,f]) ++ ", " ++ show([c,e])
         
        ++ "\nN10 = " ++ show(N10) ++ ", sides: " ++ show([a,e])
        ++ ", " ++ show([b,c]) ++ ", " ++ show([d,f])
        ++ "\nN11 = " ++ show(N11) ++ ", sides: " ++ show([a,e])
        ++ ", " ++ show([b,d]) ++ ", " ++ show([c,f])
        ++ "\nN12 = " ++ show(N12) ++ ", sides: " ++ show([a,e])
        ++ ", " ++ show([b,f]) ++ ", " ++ show([c,d])
         
        ++ "\nN13 = " ++ show(N13) ++ ", sides: " ++ show([a,f]) 
        ++ ", " ++ show([b,c]) ++ ", " ++ show([d,e])
        ++ "\nN14 = " ++ show(N14) ++ ", sides: " ++ show([a,f]) 
        ++ ", " ++ show([b,d]) ++ ", " ++ show([c,e])
        ++ "\nN15 = " ++ show(N15) ++ ", sides: " ++ show([a,f]) 
        ++ ", " ++ show([b,e]) ++ ", " ++ show([c,d])
        ];
        

        Like

      • Frits's avatar

        Frits 12:50 pm on 9 November 2021 Permalink | Reply

        @Jim/GeoffR,

        Please let me know how you post minizinc programs.

        Like

        • Jim Randell's avatar

          Jim Randell 2:08 pm on 9 November 2021 Permalink | Reply

          @Frits: Details on using [code] ... [/code] tags are here [link].

          For languages that don’t have a supported syntax highlighter you can use [[ language="text" ]].

          Like

  • Unknown's avatar

    Jim Randell 10:16 am on 28 October 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 905: Prime square 

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

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

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

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

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

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

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

    Can you complete the magic square?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

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

    [teaser905]

     
    • Jim Randell's avatar

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

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

      The following run file executes in 64ms.

      Run: [ @replit ]

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

      Solution: Here is a diagram of the completed square:

      Like

      • Mark Valentine's avatar

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

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

        Like

        • Mark Valentine's avatar

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

          Apologies. Misread it. Yours is correct.

          Like

      • Frits's avatar

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

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

        Like

      • Jim Randell's avatar

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

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

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

        Like

    • Hugh Casement's avatar

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

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

      Like

    • GeoffR's avatar

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

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

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

      Like

      • Frits's avatar

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

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

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

        Like

    • GeoffR's avatar

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

      @Frits:

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

      Like

  • Unknown's avatar

    Jim Randell 9:31 am on 26 October 2021 Permalink | Reply
    Tags:   

    Teaser 2833: Celebrity dogs 

    From The Sunday Times, 8th January 2017 [link] [link]

    Six celebrities appeared on television with their dogs. Each celebrity brought two dogs and between them they had twelve different breeds.

    The celebrities were Clooney, Hathaway, Jackman, Palermo, Rossum and Seyfried. The breeds of dog were Akita, Basenji, Basset, Bull Terrier, Chihuahua, Dalmation, Foxhound, Keeshond, Plott, Poodle, Rottweiler and Setter.

    For the name and breeds in each trio of celebrity plus their two dogs, if you look at any two out of the three then there are just two letters of the alphabet that occur (once or more) in both.

    In alphabetical order of the breeds, please list the initials of the owners (e.g. C, S, A, C, …)

    [teaser2833]

     
    • Jim Randell's avatar

      Jim Randell 9:32 am on 26 October 2021 Permalink | Reply

      I used the standard spelling of “Dalmatian”, but it doesn’t change the answer if you use the version given in the puzzle text.

      This is another puzzle that can be solved using the [[ grouping ]] functionality from the enigma.py library.

      The following Python program runs in 45ms.

      Run: [ @replit ]

      from enigma import (grouping, join, printf)
      
      # categories for this puzzle (using the normal spelling of "Dalmatian")
      names = ( 'Clooney', 'Hathaway', 'Jackman', 'Palermo', 'Rossum', 'Seyfried' )
      dogs = (
        'Akita', 'Basenji', 'Basset', 'Bull Terrier', 'Chihuahua', 'Dalmatian',
        'Foxhound', 'Keeshond', 'Plott', 'Poodle', 'Rottweiler', 'Setter'
      )
      
      # form the 2-gangs
      for gs in grouping.gangs(2, names, dogs, grouping.share_letters(2)):
        # output the gangs
        grouping.output_gangs(names, gs)
      
        # map breeds to their owners
        m = dict()
        for (n, ds) in zip(names, gs):
          m.update((d, n) for d in ds)
        # output owner initials by breed
        printf("-> {s}", s=join((m[d][0] for d in dogs), sep=" "))
      

      Solution: The initials of the owners are: J P H C J H S R C S R P.

      Ownership is as follows:

      Clooney: Bull Terrier, Plott
      Hathaway: Basset, Dalmatian
      Jackman: Akita, Chihuahua
      Palermo: Basenji, Setter
      Rossum: Keeshond, Rottweiler
      Seyfried: Foxhound, Poodle

      Like

  • Unknown's avatar

    Jim Randell 4:18 pm on 22 October 2021 Permalink | Reply
    Tags:   

    Teaser 3083: Village signposts 

    From The Sunday Times, 24th October 2021 [link] [link]

    Inside a shed I saw where the council was preparing signposts for seven neighbouring villages. Between any two villages there is at most one direct road, and the roads don’t meet except at village centres, where the signposts are to stand. The arms were all affixed, and labelled except for one name to be completed on each. The names in clockwise order on each were as follows:

    Barton, Aston, ?
    Barton, Grafton, ?
    Carlton, Forton, Eaton, ?
    Grafton, Aston, ?
    Dalton, Grafton, Eaton, ?
    Barton, Forton, Carlton, ?
    Dalton, Aston, ?

    Starting at Dalton, I walked along roads, taking the road furthest rightwards at each village and returning to Dalton. I chose the first road so that I visited as many other villages as possible with this method.

    In order, what were the other villages I visited?

    [teaser3083]

     
    • Jim Randell's avatar

      Jim Randell 10:18 pm on 22 October 2021 Permalink | Reply

      I’m not quite sure what “furthest rightwards” means in this context. At the moment I’m assuming it means when I arrive in a village I take the “first right” (i.e. the road that is anti-clockwise from that which I entered the village on).

      And using this interpretation I think there is a unique layout and solution.

      I determined the layout manually (but with programmatic assistance, a fully programmatic determination is given below). The following Python program finds the longest “first right” journey on this layout.

      Run: [ @replit ]

      from enigma import (Accumulator, join, printf)
      
      # the layout is determined by teaser3083c.py
      graph = dict(D='BAC', E='BGF', G='CFEB', F='GAE', B='DGEA', A='BFCD', C='DAG')
      
      # make a journey on the graph, taking the "first right" at each village
      def journey(vs):
        while True:
          (v0, v1) = vs[-2:]
          # are we done?
          if v1 == vs[0]:
            return vs
          # choose the next destination
          ds = graph[v1]
          i = ds.index(v0)
          vs.append(ds[i - 1])
      
      # format a journey
      fmt = lambda vs: join(vs, sep=" -> ")
      
      # find maximal length journeys
      r = Accumulator(fn=max, collect=1)
      
      # start at D, and choose initial destination
      for v in graph['D']:
        vs = journey(['D', v])
        printf("{vs}", vs=fmt(vs))
        r.accumulate_data(len(vs), vs)
      
      for vs in r.data:
        printf("longest: {vs}", vs=fmt(vs))
      

      Solution: The other villages visited were: Carlton, Grafton, Barton.

      Here is a diagram that shows the layout of the roads:

      The possible “first right” journeys from D are:

      D → A → C → D
      D → B → A → D
      D → C → G → B → D

      Like

      • Jim Randell's avatar

        Jim Randell 10:34 pm on 23 October 2021 Permalink | Reply

        And here is a fully programmed solution that determines the layout of the graph (as used in the program above).

        It considers possible ways to complete the signs that give 12 roads, with a signpost at each end, and then assembles them into an acceptable layout with no crossing roads. It does this by turning each town into a “molecule” with the specified roads leading out of it, and then allows one molecule to gradually absorb each of the others so that the roads form a viable layout.

        It runs in 603ms.

        Run: [ @replit ]

        from enigma import (union, multiset, ordered, unpack, irange, find, seq_all_different, Accumulator, map2str, printf)
        
        # the given signposts (with known destinations)
        signs = [ 'BA', 'BG', 'CFE', 'GA', 'DGE', 'BFC', 'DA' ]
        
        # determine the labels used
        labels = union(signs)
        assert len(labels) == len(signs)
        
        # choose a distinct element from each set
        def choose(ss, s=[]):
          # are we done?
          if not ss:
            yield s
          else:
            for x in ss[0]:
              if x not in s:
                yield from choose(ss[1:], s + [x])
        
        # look for the longest match between s1 and s2
        def align(s1, s2):
          r = Accumulator(fn=max)
          n = len(s2)
          for j in irange(1, n):
            # does the first element of s2 match s1?
            i = find(s1, s2[0])
            if i != -1:
              # bring it to the front of s1
              if i > 0: s1 = s1[i:] + s1[:i]
              k = 1
              # extend the match if possible
              while k < n and s1[-1] == s2[k]:
                s1.insert(0, s1.pop(-1))
                k += 1
              # all matches must be used
              if seq_all_different(s1[k:] + s2[k:]):
                # save the length of the match and the aligned sequences      
                r.accumulate_data(k, (list(s1), list(s2)))
              if k == n: break
            if j < n:
              # rotate s2
              s2.append(s2.pop(0))
          if r.value: return (r.value,) + (r.data)
          return (None, None, None)
        
        # can we merge the blobs into a single blob?
        def merge(blobs, blob=None):
          rev = lambda s: s[::-1]
          # initial blob?
          if blob is None:
            (i, blob) = max(enumerate(blobs), key=unpack(lambda i, x: len(x)))
            blob = list(rev(x) for x in blob)
            blobs.pop(i)
          # process the remaining blobs
          while blobs:
            # find the most matching of the remaining blobs
            r = Accumulator(fn=max)
            for (j, b) in enumerate(blobs):
              (k, blob_, b_) = align(blob, b)
              if k:
                r.accumulate_data((k, k - len(b)), (j, b_, blob_))
            if not r.value: return False
            ((k, _), (j, b, blob)) = (r.value, r.data)
            # merge blobs
            blob = blob[k:] + list(rev(x) for x in b[k:])
            blobs.pop(j)
          # did everything match up?
          return (not blob)
        
        # check the graph can be laid out (could check graph planarity)
        def check(g):
          # each blob is a list roads
          blobs = list(list(k + v for v in vs) for (k, vs) in g.items())
          # can the blobs be merged?
          return merge(blobs)
        
        # choose a location for each signpost
        for locs in choose(list(labels.difference(s) for s in signs)):
          # choose the missing destinations
          for dests in choose(list(labels.difference(s + v) for (v, s) in zip(locs, signs))):
            # make the graph
            g = dict((v, s + d) for (v, s, d) in zip(locs, signs, dests))
            # count the roads
            rs = multiset()
            for (s, ds) in g.items():
              rs.update_from_seq(ordered(s, d) for d in ds)
            # each road should have 2 ends
            if not all(v == 2 for v in rs.values()): continue
            # can the graph be laid out?
            if not check(g): continue
            # output the graph
            printf("graph = {g}", g=map2str(g))
        

        Without the [[ check() ]] function at line 90, we find there are 8 candidate layouts, which originally I drew out manually, and the “correct” one was immediately obvious. But it was quite fun to come up with an algorithm to determine which of the layouts is viable.

        Like

  • Unknown's avatar

    Jim Randell 9:07 am on 21 October 2021 Permalink | Reply
    Tags: by: Alfred Moritz   

    Brain-Teaser 882: Another magic class 

    From The Sunday Times, 2nd July 1978 [link]

    This puzzle concerns a class of twenty-five pupils whose first names happen to have different initials (and none begins with X). The teacher makes them sit in alphabetical order in five neat rows of five with Arthur sitting to the left of Barry and in front of Freda.

    When their homework was returned (marked, as usual, out of a maximum of 25 without using fractional marks) they found that each pupil had a different mark and that, surprisingly enough, the total of marks in each row of five, each column of five and each diagonal of five was identical.

    Yvonne came top, followed by Harry and Jane in that order. Ursula scored more marks than Zena, and Richard was beaten by Charles. Victor had twice as many marks as George, and Ivor had four times as many as Freda. Susan beat Michael by the same margin by which Michael beat George (who, incidentally, scored an odd number of marks). This was also the margin by which Walter beat his left-hand neighbour and by which his right-hand neighbour beat him.

    Kenneth beat Olga. By how many marks?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser882]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 21 October 2021 Permalink | Reply

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

      The following run file executes in 73ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # values are 0 to 25, so let's use base 26
      --base=26
      
      # X is the unused value, so: k = (tri(25) - X) / 5, tri(25) = 325
      # so X is a multiple of 5
      "X % 5 == 0"
      
      # each row/column/diagonal sums to the magic constant
      "5 * (A + B + C + D + E) + X == 325"
      "5 * (F + G + H + I + J) + X == 325"
      "5 * (K + L + M + N + O) + X == 325"
      "5 * (P + Q + R + S + T) + X == 325"
      "5 * (U + V + W + Y + Z) + X == 325"
      
      "5 * (A + F + K + P + U) + X == 325"
      "5 * (B + G + L + Q + V) + X == 325"
      "5 * (C + H + M + R + W) + X == 325"
      "5 * (D + I + N + S + Y) + X == 325"
      "5 * (E + J + O + T + Z) + X == 325"
      
      "5 * (A + G + M + S + Z) + X == 325"
      "5 * (E + I + M + Q + U) + X == 325"
      
      # Y is the highest mark
      "(25 if X != 25 else 24) = Y"
      
      # H is the second highest mark
      "(Y - 1 if X != Y - 1 else Y - 2) = H"
      
      # J is the third highest mark
      "(H - 1 if X != H - 1 else H - 2) = J"
      
      # Y beat W by the same margin that W beat V
      "Y - W == W - V"
      
      # and this is the same margin that S beat M and M beat G
      "S - M == Y - W"
      "M - G == Y - W"
      
      # V = 2G, G's score was odd
      "2 * G = V"
      "G % 2 == 1"
      
      # I = 4F
      "4 * F = I"
      
      # U beat Z, C beat R, K beat O
      "U > Z"
      "C > R"
      "K > O"
      
      # [optional]
      --template=""
      --denest
      

      Solution: Kenneth beat Olga by 16 marks.

      The complete layout is:

      [A 21] [B 14] [C  7] [D  0] [E 18]
      [F  2] [G  5] [H 23] [I  8] [J 22]
      [K 20] [L 15] [M 12] [N  9] [O  4]
      [P 11] [Q 16] [R  1] [S 19] [T 13]
      [U  6] [V 10] [W 17] [Y 24] [Z  3]
      [X 25]

      So the marks awarded were 0-24, no-one scored 25.

      Like

    • Frits's avatar

      Frits 12:54 pm on 22 October 2021 Permalink | Reply

       
      # Y + V = 2 * W  \
      # Y = 24 or 25    > V is even so Y is also even
      # V = 2G         /
      #
      # (X, Y, H, J) = (25, 24, 23, 22)
      # W = 17
      #
      # F + G + I = 15 \   G + 5 * F = 15
      # G is odd        >  so G = 5, F = 2, I = 8
      # 4 * F = I      /
      #
      # V = 10 (2 * G)
      # center M = 60 / 12
      # S = 19 (S - M == Y - W)
      #
      # C + H + M + R + W = 60 \  C + R = 8
      #                         >
      # C > R                  /  so only option R = 1, C = 7
      #
      # A + G + M + S + Z = 60 \  A + Z = 24  so Z > 2
      # U > Z                   >
      # U + V + W + Y + Z = 60 /  U + Z = 9 only option Z = 3, U = 6, A = 21
      #
      # D + I + N + S + Y = 60 --> D + N = 9, only options (D, N): (0, 9) or (9, 0)
      #
      # only candidates for 4 are O and T -- > O + T = y + 4  (y is O or T)
      # E + J + O + T + Z = 60 --> E + O + T = 35 --> E + y = 31 --> E > 10
      #
      # B + G + L + Q + V = 60 \  B + L + Q = 45 so B > 6 and also B > 10
      #                         >
      # A + B + C + D + E = 60 /  B + D + E = 32
      #
      # minimal(B + E) = 24 --> D <= 8 --> D = 0 and N = 9
      #
      # E + O + T = 35 \
      #                 > Q = O + T - 1 = y + 3
      # E + Q = 34     /
      #
      # B + E = 32, only options (B, E, Q): (14, 18, 16) or (18, 14, 20)
      # last option implies y = 17 which is not possible
      # so B = 14, E = 18, Q = 16, L = 15
      #
      # A + F + K + P + U = 60 --> K + P = 31
      # only options (K, P): (20, 11) or (11, 20)
      #
      # K + L + M + N + O = 60 --> K + O = 24 with K > O
      # only option (K, O): (20, 4)
      # K = 20, O = 4, T = 13, P = 11 
      

      Like

  • Unknown's avatar

    Jim Randell 9:38 am on 19 October 2021 Permalink | Reply
    Tags:   

    Teaser 2831: Nine ladies dancing 

    From The Sunday Times, 25th December 2016 [link]

    On the ninth day of Christmas the ladies Anne, Bella, Cary, Debbie, Eileen, Fran, Gill, Honor and Iris entered a dancing competition. In some order they had labels ‘One’, ‘Two’, ‘Three’, ‘Four’, ‘Five’, ‘Six’, ‘Seven’, ‘Eight’ and ‘Nine’ on their backs. They each danced a different dance chosen from cha-cha, charleston, foxtrot, jive, polka, quickstep, samba, twist and waltz.

    For any lady’s name and label there was just one letter of the alphabet that occurred (once or more) in both. The same was true for any lady’s name and their dance, and also for any lady’s label and dance.

    In alphabetical order of their names, what are the numbers of the nine ladies?

    This puzzle completes the archive of Teaser puzzles from 2016. There are now 568 Teaser puzzles available on the site, originally published between 1957 and 2021.

    I have 32 puzzles remaining to post (all from 2017) that I solved at the time, and after that all following puzzles posted will be new to me.

    [teaser2831]

     
    • Jim Randell's avatar

      Jim Randell 9:38 am on 19 October 2021 Permalink | Reply

      This is another puzzle that can be solved using the [[ grouping ]] functionality in the enigma.py library.

      Normally this type of puzzle is by Graham Smithers, but this one is by Victor Bryant.

      The following Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import grouping
      
      # categories for this puzzle
      names = ( 'Anne', 'Bella', 'Cary', 'Debbie', 'Eileen', 'Fran', 'Gill', 'Honor', 'Iris' )
      labels = ( 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine' )
      dances = ( 'cha-cha', 'charleston', 'foxtrot', 'jive', 'polka', 'quickstep', 'samba', 'twist', 'waltz' )
      
      # match the categories into groups such that each pair in each group shares exactly 1 letter
      grouping.solve([names, labels, dances], grouping.share_letters(1))
      

      Solution: The numbers are: eight, one, four, six, three, seven, nine, two, five.

      Like

    • Frits's avatar

      Frits 1:47 pm on 19 October 2021 Permalink | Reply

      With a different version of grouping.groups().

        
      from enigma import grouping
      from itertools import product
      
      # categories for this puzzle
      names = ( 'Anne', 'Bella', 'Cary', 'Debbie', 'Eileen', 'Fran', 'Gill', 
                'Honor', 'Iris' )
      labels = ( 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 
                 'Nine' )
      dances = ( 'cha-cha', 'charleston', 'foxtrot', 'jive', 'polka', 'quickstep',
                 'samba', 'twist', 'waltz' )
       
      def groups(vs, fn, s=[]):
        """
        group the lists of elements in <vs> into groups (one element from each list)
        such that the values in the groups satisfy the selection function <fn>
      
        returns a sequence of groups.
        """
        # are we done?
        if not vs[0]:
          yield tuple(s)
        else:
          # make list of items which together with vs[0][0] satisfy 
          # the selection function <fn> 
          cands = [li for v in vs[1:] 
                   if (li := [x for x in v if fn(*(vs[0][0], x))])]
          if len(cands) > 1:
            for v in product(*(x for x in cands)):
              # the full group is
              group = (vs[0][0],) + v
              # check the group
              if fn(*group):
                # solve for the remaining elements 
                yield from groups([vs[0][1:]] + [[y for y in x if y != v[i]]
                           for i, x in enumerate(vs[1:])], fn, s + [group])
              
      # match the categories into groups such that each pair in each group 
      # shares exactly 1 letter
      for g in groups([names, labels, dances], grouping.share_letters(1)):
        for items in g:
          print(", ".join(items))
      

      Like

    • Frits's avatar

      Frits 2:36 pm on 19 October 2021 Permalink | Reply

      @Jim, do you have grouping puzzles with more than 3 groups?

      Like

      • Jim Randell's avatar

        Jim Randell 2:55 pm on 19 October 2021 Permalink | Reply

        They seem to be a speciality of Sunday Times Teasers (and particularly Graham Smithers). I don’t think I’ve come across any Teasers that use more that three lists of values. (i.e. The first argument to [[ grouping.solve() ]] is a collection of 3 sequences).

        Like

        • Frits's avatar

          Frits 3:05 pm on 19 October 2021 Permalink | Reply

          Using lnames = ( 'Red', 'Lurr', 'Fuss', 'Bun', 'Fix', 'Gav', 'Bax', 'Fox', 'Heg' ) as fourth group:

           
          Anne, Eight, cha-cha, Gav
          Bella, One, jive, Heg
          Cary, Four, quickstep, Red
          Debbie, Six, charleston, Bax
          Eileen, Three, waltz, Lurr
          Fran, Seven, samba, Bun
          Gill, Nine, twist, Fix
          Honor, Two, polka, Fox
          Iris, Five, foxtrot, Fuss
          

          Like

  • Unknown's avatar

    Jim Randell 11:25 am on 17 October 2021 Permalink | Reply
    Tags: by: A. D. Denton   

    A Holiday Brain Teaser: [Diophantine quadruples] 

    From The Sunday Times, 4th August 1957 [link]

    The series below is peculiar in that if any two of the numbers are multiplied together and added to unity, the result is a perfect square:

    1, 3, 8

    Let A[4], be a fourth number in this series. There is a further series with the same characteristic:

    8, B[2], 190, B[4]

    in which not only is B[1], the same as A[3], but B[2] is also the same as A[4].

    What is B[4]?

    (Note: zero is excluded)

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of £5 was offered for a solution to the puzzle, and a further prize of £25 for finding A[5] or B[5].

    This puzzle was originally published with no title.

    [teaser-1957-08-04] [teaser-unnumbered]

     
    • Jim Randell's avatar

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

      I am assuming we are looking for a sequence of increasing integers.

      With computers we can easily calculate the next terms.

      Even a simple program runs in less than a second:

      from enigma import (irange, inf, is_square, printf)
      
      # find the smallest number which when multiplied by any of ns, and 1
      # is added to the result, give a square number
      def extend(ns):
        for x in irange(1, inf):
          if all(is_square(x * n + 1) for n in ns):
            return x
      
      # extend the series 1, 3, 8, ...
      A4 = extend([1, 3, 8])
      printf("A4 = {A4}")
      
      # extend the series 8, A4, 190, ...
      B4 = extend([8, A4, 190])
      printf("B4 = {B4}")
      

      But we can be a little bit cleverer, and this gives us a program that runs in just a few milliseconds:

      Run: [ @replit ]

      from enigma import (irange, inf, div, is_square, printf)
      
      # find the smallest number which when multiplied by any of ns, and 1
      # is added to the result, give a square number
      def extend(ns):
        n0 = ns[0]
        # look for squares, z^2, such that: z^2 = n0 * x + 1
        for z in irange(1, inf):
          x = div(z * z - 1, n0)
          if x and all(is_square(n * x + 1) for n in ns[1:]):
            return x
      
      # extend the series 1, 3, 8, ...
      A4 = extend([1, 3, 8])
      printf("A4 = {A4}")
      
      # extend the series 8, A4, 190, ...
      B4 = extend([8, A4, 190])
      printf("B4 = {B4}")
      

      Solution: A[4] = 120. B[4] = 730236.

      See OEIS A030063 [@oeis], which says that the sequence cannot be extended further (although there is a further term in rationals).


      The following was published in the 18th August 1957 edition of The Sunday Times [link]:

      Competitors were asked, in effect, to find fourth terms to two series of numbers such that if any two are multiplied together and added to unity the result is a perfect square.

      The two series were: 1, 3, 8, 120; and: 8, 120, 190, 730236.

      The number of solutions received was 131, of which most were correct: the first correct one opened was from Mr. D. W. Mardle of Bishop’s Cleeve, who will receive £5.

      The fourth number was not easy to find, but Diophantine Equations could be used. A prize of £25 would have been awarded to any competitor who found a fifth term, which the propounder of the problem had been unable to find though he could not prove its non-existence. Many suggested that the fifth number must be very large and one competitor used the electronic brain Deuce, but only up to 10 digits: human brains were better and said that it had more than 10, 20 or 100 digits. No competitor bettered G. Hopkins of Kettering who, prior to publication, had found no number with less than 1,000 digits.

      Some doubted whether it existed and others asserted that it did not. Two competitors said they were working on the solution and would send a fifth number within a few days! Another — more realistically said he would spend next winter looking for it. Some competitors quoted A[5] and B[5] as 777480/8288641 and 194612421699066228/17740920385231762867201 but these were not integral and were not the fifth numbers.

      One competitor quoted the great mathematician Euler (circa 1760), who, it is understood, showed that there was a fifth fractional number but left the existence of a fifth integral number as a matter of inquiry. The competition examined numbers far greater than any mentioned by the well-known mathematicians and in that sense came nearer to a proof than they.

      The following competitors will receive consolation prizes of £2 each as they sent in correct solutions and also made valiant attempts to find the fifth number or a general solutlon: H. McANDREW, J. WILMERS, H. G. APSIMON, A. K. BOLLAND, MISS R. GILBERT, D. C. LESLIE, A. W. JOSEPH, P. A. SAMET, N. H. FANSHAWE, E. J. WATSON.

      Like

      • Jim Randell's avatar

        Jim Randell 12:10 pm on 17 October 2021 Permalink | Reply

        Euler found an infinite family of such 4-tuples:

        {a, b, a + b + 2r, 4r(a + r)(b + r)}; where r² = a⋅b + 1

        (Which means that any pair of values from one quadruple can be used to seed another).

        The A sequence of the puzzle is given by: a = 1; b = 3; r = 2.

        And the B sequence is given by: a = 8; b = 120; r = 31.

        from enigma import (div, printf)
        
        def extend(ns):
          (a, b, c) = ns
          r = div(c - a - b, 2)
          if r is not None and r * r == a * b + 1:
            return 4 * r * (r + a) * (r + b)
        
        # extend the series 1, 3, 8, ...
        A4 = extend([1, 3, 8])
        printf("A4 = {A4}")
        
        # extend the series 8, A4, 190, ...
        B4 = extend([8, A4, 190])
        printf("B4 = {B4}")
        

        In 1969 it was shown that the {1, 3, 8, 120} quadruple cannot be extended with another value, and in 2016 (a mere 59 years after the puzzle was set) it was proved that no Diophantine quintuple exists (see: [ @wikipedia ]). So the £25 prize was safe.

        Like

      • Jim Randell's avatar

        Jim Randell 5:48 pm on 20 October 2021 Permalink | Reply

        And here is a solution using Pell’s equations [revised to use the pells.py module from the py-enigma-plus repository] (see: Teaser 2994), which means fewer candidate numbers are considered when looking for solutions.

        The following Python program runs in 63ms. (Internal runtime is 334µs).

        from enigma import (div, is_square, peek, subsets, diff, printf)
        import pells
        
        # in general to extend a pair (a, b) we need x, such that:
        #
        #  a.x + 1 = A^2
        #  b.x + 1 = B^2
        #
        # so, eliminating x:
        #
        #  B^2 = (b/a)(A^2 - 1) + 1
        #  B^2 = (b/a)A^2 - (b/a) + 1
        #  B^2 - (b/a)A^2 = 1 - b/a
        #
        # which, if b/a is an integer, is a form of Pell's equation
        
        # extend diophantine tuple where b/a is an integer
        def extend(a, b, *rest):
          D = div(b, a)
          for (B, A) in pells.diop_quad(1, -D, 1 - D):
            x = div(A * A - 1, a)
            if x and all(is_square(x * n + 1) for n in rest):
              yield x
        
        # extend 1, 3, 8 ...
        A4 = peek(extend(1, 3, 8))
        printf("A4 = {A4}")
        
        # choose a viable (a, b) pair
        ns = (8, A4, 190)
        (a, b) = peek((a, b) for (a, b) in subsets(sorted(ns), size=2) if b % a == 0)
        B4 = peek(extend(a, b, *diff(ns, [a, b])))
        printf("B4 = {B4}")
        

        Like

    • GeoffR's avatar

      GeoffR 3:09 pm on 17 October 2021 Permalink | Reply

      The best run-time I could get was 12.81 sec on an I9 processor. The Geocode solver took much longer. I had to use the answer to fix variable upper bounds.

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      predicate is_sq(var int: y) =
      exists(z in 1..ceil(sqrt(int2float(ub(y))))) (
              z*z = y);
      
      % 1st series A1, A2, A3, A4
      int: A1 == 1; 
      int: A2 == 3; 
      int: A3 == 8;
      var 1..200: A4;
      
      constraint is_sq(A1 * A2 + 1) /\ is_sq(A1 * A3 + 1) /\ is_sq(A1 * A4 + 1)
      /\ is_sq(A2 * A3 + 1) /\ is_sq(A2 * A4 + 1) /\ is_sq(A3 * A4 + 1);
      
      % 2nd series B1, B2, B3, B4
      int: B1 == 8; 
      var 9..200: B2; 
      int: B3 == 190;
      var 191..800000: B4;
      
      constraint B2 == A4;
      
      constraint is_sq(B1 * B2 + 1) /\ is_sq(B1 * B3 + 1) /\ is_sq(B1 * B4 + 1)
      /\ is_sq(B2 * B3 + 1) /\ is_sq(B2 * B4 + 1) /\ is_sq(B3 * B4 + 1);
      
      solve satisfy;
      
      output [ "A1, A2, A3, A4 = " ++ show([A1, A2, A3, A4]) ++
      "\n" ++ "B1, B2, B3, B4 = " ++ show([B1, B2, B3, B4])];
      
      % A1, A2, A3, A4 = [1, 3, 8, 120]
      % B1, B2, B3, B4 = [8, 120, 190, 730236]
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 10:38 pm on 17 October 2021 Permalink | Reply

      A Python solution ran in 258 msec.

      
      from enigma import is_square as is_sq
      
      # 1st sequence is 1, 3, 8, A4
      for A4 in range (9, 200):
          if not is_sq(A4 * 1 + 1): continue
          if not is_sq(A4 * 3 + 1): continue
          if not is_sq(A4 * 8 + 1): continue
          print('A4 = ', A4)
          # 2nd sequence is 8, A4, 190, B4
          for B4 in range(191, 800000):
              if not is_sq(B4 * 8 + 1): continue
              if not is_sq(B4 * A4 + 1):continue
              if not is_sq(B4 * 190 + 1): continue
              print('B4 = ', B4)
              
      # A4 =  120
      # B4 =  730236
      
      

      Like

    • Frits's avatar

      Frits 12:33 am on 18 October 2021 Permalink | Reply

        
      from enigma import div, first, inf, irange, is_square as is_sq
      
      # sqrt(8 * 190 + 1) = 39
      A4s = [A4 for i in range(3, 40) if is_sq((A4 := i * i - 1) * 8 + 1) and 
                                         is_sq(A4 * 3 + 1)]   
      # 1st sequence is 1, 3, 8, A4
      for A4 in A4s:
        # 2nd sequence is 8, A4, 190, B4
        # sqrt(190 * 190 + 1) = 190.xx
        B4 = first(B4 for i in irange(191, inf) if (B4 := div(i * i - 1, 190)) and 
                   is_sq(B4 * A4 + 1) and is_sq(B4 * 8 + 1) )
        print(f"A4 = {A4}, B4 = {B4[0]}")
      

      Like

    • GeoffR's avatar

      GeoffR 9:17 am on 18 October 2021 Permalink | Reply

      @Frits:

      Neat solution.

      The combined use of first() in an iterator and inf from the enigma.py library solves the problem I had in fixing the upper bound in a search for value B4.

      Like

      • Frits's avatar

        Frits 9:57 am on 18 October 2021 Permalink | Reply

        @GeoffR: the other way of achieving it is to break out of a while True loop for the first correct B4.

        Also possible is using a wheel [2, 40, 152, 190] so that (i * i – 1) always is a multiple of 190.

          
        B4 = first(B4 for k in irange(0, inf) 
                   for i in [189 + (190 * k) + x for x in [2, 40, 152,  190]] 
                   if is_sq((B4 := (i * i - 1) // 190) * A4 + 1) and 
                      is_sq(B4 * 8 + 1))
        

        Like

  • Unknown's avatar

    Jim Randell 5:04 pm on 15 October 2021 Permalink | Reply
    Tags:   

    Teaser 3082: In the swim 

    From The Sunday Times, 17th October 2021 [link] [link]

    Expert mathematicians Al Gritham, Ian Tadger, Carrie Over and Tessa Mole-Poynte have a swimming race for a whole number of lengths. At some point during each length (so not at the ends) they calculate that they have completed a fraction of the distance, and at the finish they compare their fractions (all in the lowest terms). Al says, “My fractions have prime numerators, the set of numerators and denominators has no repeats, their odd common denominator has two digits, and the sum of my fractions is a whole number”. Tessa says, “Everybody’s fractions have all the properties of Al’s, but one of mine is closer to an end of the pool than anybody else’s”.

    What were Tessa’s fractions?

    [teaser3082]

     
    • Jim Randell's avatar

      Jim Randell 10:00 pm on 15 October 2021 Permalink | Reply

      The puzzle doesn’t say anything about the sets of fractions being different, but I think we need to assume that to find a unique solution. (Otherwise 3 of the 4 sets could be the same, with the largest measure, and Tessa’s could be one of the three possible sets that have a smaller measure).

      The program finds all possible sets, collected by the number of lengths in the race, and finds which has the smallest measure.

      I turns out there is only one possible number of lengths, and there are four possible sets of fractions, so if each is assigned to one of the participants Tessa’s must be the one with the minimum measure.

      This Python program runs in 67ms.

      Run: [ @replit ]

      from enigma import (Primes, irange, fraction, mlcm, group, fdiv, Accumulator, printf)
      
      # primes up to 100
      primes = Primes(100)
      
      # select fractions with different numerators and denominators, that sum to a whole number
      def select(ss, rs=[], ns=set(), t=(0, 1)):
        # are we done?
        if not ss:
          if t[1] == 1:
            yield rs
        else:
          # choose the next fraction
          for (a, b) in ss[0]:
            if not (a in ns or b in ns):
              yield from select(ss[1:], rs + [(a, b)], ns.union((a, b)), fraction(a, b, *t))
      
      # generate sets of fractions that satisfy the conditions
      def generate():
      
        # consider the 2-digit, odd common denominator
        for d in irange(11, 99, step=2):
          # must be more than one possible denominator
          if d in primes: continue
      
          # calculate the possible fractions
          fs = list()
          for n in irange(1, d - 1):
            (a, b) = fraction(n, d)
            if a in primes:
              fs.append((a, b))
      
          # consider number of lengths (each denominator must be different)
          for k in irange(2, len(set(b for (a, b) in fs))):
      
            # put the fractions into the corresponding lengths
            lengths = list(list() for _ in irange(k))
            for (a, b) in fs:
              (i, r) = divmod(a * k, b)
              if r > 0:
                lengths[i].append((a, b))
      
            # return possible sets of fractions
            if all(lengths):
              for ss in select(lengths):
                # check the common denominator
                d = mlcm(*(b for (a, b) in ss))
                if 10 < d < 100 and d % 2 == 1:
                  yield ss
      
      # group possible sets of fractions by number of lengths
      d = group(generate(), by=len)
      
      # consider number of lengths
      for k in sorted(d.keys()):
        rs = d[k]
        if len(rs) > 1:
          printf("{k} lengths -> {n} sets", n=len(rs))
          # output sets, and measure
          Q = fdiv
          r = Accumulator(fn=min, collect=1)
          for ss in rs:
            m = min(min(Q(a, b) - Q(i, k), Q(i + 1, k) - Q(a, b)) for (i, (a, b)) in enumerate(ss))
            r.accumulate_data(m, ss)
            printf("-> {ss} [{m:.2%}]")
          # output minimum measure sets
          for ss in r.data:
            printf("min = {ss}")
          printf()
      

      Solution: Tessa’s fractions were: 13/75, 7/15, 3/5, 19/25.

      So the race was over 4 lengths.

      Tessa’s fourth fraction 19/25 = 0.76, so is 1% of the total distance after the start of the final length.

      The other participants must have the following sets of fractions:

      13/63, 3/7, 5/9, 17/21
      7/75, 11/25, 3/5, 13/15
      23/99, 3/11, 5/9, 31/33

      Each set of fractions (including Tessa’s) sum to 2.


      We can show that the number of lengths must be even:

      If we consider a k length race. Then the fractions are:

      0/k < a < 1/k
      1/k < b < 2/k
      2/k < c < 3/k

      (k − 1)/k < z < k/k

      From which we see:

      T(k − 1)/k < a + b + c + … + z < T(k)/k
      (1/2)(k − 1) < sum < (1/2)(k + 1)

      If k is even, k = 2x:

      (1/2)(2x − 1) < sum < (1/2)(2x + 1)
      x − 1/2 < sum < x + 1/2

      and as the sum is an integer we have: sum = x.

      If k is odd, k = 2x + 1:

      (1/2)(2x) < sum < (1/2)(2x + 2)
      x < sum < x + 1

      And there are no integer solutions.

      So k must be even (and the sum of the fractions must be k/2).

      And k cannot be 2, as then the fractions would need to be (a / b) and 1 − (a / b) = (b − a) / b which have the same denominator.

      We could use this observation to reduce the number of cases considered by the program, line 34 becomes:

      for k in irange(4, len(set(b for (a, b) in fs)), step=2):
      

      Then only denominators of 45, 63, 75, 81, 99 are considered, and only races consisting of 4 lengths.

      Like

    • Frits's avatar

      Frits 2:04 pm on 16 October 2021 Permalink | Reply

      Using Jim’s approach as base but trying to do it a little bit different.

      Quite a complex puzzle this week.

        
      from enigma import fraction
      from collections import defaultdict
      from itertools import compress
      
      def primesbelow(n):
        # rwh_primes1v2(n):
        """ Returns a list of primes < n for n > 2 """
        sieve = bytearray([True]) * (n // 2 + 1)
        for i in range(1, int(n ** 0.5) // 2 + 1):
          if sieve[i]:
            sieve[2 * i * (i + 1)::2 * i + 1] = \
            bytearray((n // 2 - 2 * i * (i + 1)) // (2 * i + 1) + 1)
        return [2, *compress(range(3, n, 2), sieve[1:])]
      
      P = primesbelow(100)
                                
      # select fractions with different numerators and denominators, 
      # that sum to a whole number
      def select(ss, k, rs=[], ns=set(), t=(0, 1)):
        nr_processed = len(rs)
        # are we done?
        if nr_processed == k:
          if t[1] == 1:
            yield rs
        else:
          low = nr_processed / k
          hgh = (nr_processed + 1) / k
          for i, (a, b) in enumerate(ss):
            # only select fractions in the same length 
            f = a / b
            if f <= low: continue
            if f >= hgh: break
            
            # choose the next fraction
            if not(a in ns or b in ns):
              yield from select(ss[i + 1:], k, rs + [(a, b)], ns.union((a, b)), 
                            fraction(a, b, *t))   
      
      d = defaultdict(list)
      
      # odd 2-digit common denominators
      for cd in range(11, 100, 2):
        # must be more than one possible denominator
        if cd in P: continue
        
        # generate sets of fractions with prime numerators
        fs = [f for n in range(1, cd) if (f := fraction(n, cd))[0] in P]
        
        k = 2
        while True:
          # break if there is a gap in the set of length numbers
          if len(set((k * a) // b for a, b in fs)) != k:
            break
         
          # select different fractions that sum to a whole number
          for s in select(fs, k):
            # check on 2-digit common denominator, no odd check needed
            if max(b for a, b in s) < 10: continue
            
            d[k].append(s)
          k += 1  
      
      for k, vs in d.items():
        # we need enough data for 4 mathematicians
        if len(vs) < 4: continue
        
        # calculate difference from whole number      
        diffs = [(min((diff := ((k * a) / b) % 1), 1 - diff), v) 
                  for v in vs for a, b in v]
        
        # one of Tessa's fractions is the closest to an end of the pool         
        tessa = min(diffs)
        
        print(f"Tessa's fractions are: {tessa[1]}")      
      

      Like

      • Frits's avatar

        Frits 2:18 pm on 16 October 2021 Permalink | Reply

        Probably no such 1-digit common denominator can ever happen (line 58).

        Like

    • Hugh Casement's avatar

      Hugh Casement 1:44 pm on 24 October 2021 Permalink | Reply

      I don’t understand this puzzle. In fact, I don’t believe it can be understood.
      “Their odd common denominator has two digits” implies that the denominators are all the same, which does not tally with “the set of numerators and denominators has no repeats”.

      “… have completed a fraction of the distance” means to me a fraction of the current length.
      Tessa said that one of her fractions was the closest to an end of the pool; the largest of all Jim’s fractions is 31/33, not 19/25 ! Alternatively, 7/75 is the smallest.

      Altogether the setters of puzzles should say what they mean (and mean what they say, even if that is not the same thing, as the Mad Hatter protested).

      Like

  • Unknown's avatar

    Jim Randell 7:15 am on 14 October 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 879: Seven criminals 

    From The Sunday Times, 11th June 1978 [link]

    The instructor at the police training college spoke to the six constables in his class in these words:

    “You have been studying full-face photographs of seven criminals whom we are calling P, Q, R, S, T, U and V. Now l am going to show you one photograph, taken in profile, of each criminal, and you have to write down their names in the
    order in which I present them.”

    This was done and the constables handed in the following
    six answers:

    1. P Q R S T U V
    2. P Q R U T S V
    3. P S U V R T Q
    4. P S Q U R T V
    5. P U R V T S Q
    6. R P U Q T S V

    “I am pleased to see that each criminal has been correctly identified by at least one of you”, said the instructor. “And I note that you all have a different number of correct answers and so I can give out the prizes”.

    In what order were the photographs presented?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser879]

     
    • Jim Randell's avatar

      Jim Randell 7:16 am on 14 October 2021 Permalink | Reply

      It is straightforward to check all possible arrangements.

      This Python program runs in 87ms.

      Run: [ @replit ]

      from enigma import subsets, seq_all_different, join, printf
      
      # the six answers
      ans = [
        'PQRSTUV',
        'PQRUTSV',
        'PSUVRTQ',
        'PSQURTV',
        'PURVTSQ',
        'RPUQTSV',
      ]
      
      # count number of items in correct position
      correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys))
      
      # consider possible orderings
      for ss in subsets('PQRSTUV', size=len, select="P"):
        # how many are in the correct position?
        pos = list(correct(xs, ss) for xs in ans)
        if not seq_all_different(pos): continue
        # check each item is correct in one of the answers
        if not all(any(xs[i] == x for xs in ans) for (i, x) in enumerate(ss)): continue
        # output solution
        printf("{ss} -> {pos}", ss=join(ss, sep=" ", enc="()"))
      

      Solution: The photographs were presented in the following order: R, P, U, V, T, S, Q.

      And the number of photographs correctly identified by the constables were: 1, 2, 3, 0, 4, 5.

      There is only a single solution without the condition that each criminal was identified correctly by (at least) one of the constables. But we see that the last constable got Q and V wrong (and the rest right), but the penultimate constable got these two right, so each miscreant was identified correctly by one of the constables.

      Like

    • Frits's avatar

      Frits 10:12 am on 15 October 2021 Permalink | Reply

      Borrowing from above program and the solve() function in Teaser 3080 (One of a kind).
      The program runs in 2ms.

        
      # find members of each set, such that each member is used exactly once
      def solve(ss, ns=[], ds=set()):
        # are we done?
        if not(ss):
          yield ns
        else:
          # choose an element from the next set
          for n in ss[0]:
            if not(ds.intersection(n)):
              yield from solve(ss[1:], ns + [n], ds.union(n))
      
      # count number of items in correct position
      correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys))
      
      # the six answers
      ans = [
        'PQRSTUV',
        'PQRUTSV',
        'PSUVRTQ',
        'PSQURTV',
        'PURVTSQ',
        'RPUQTSV',
      ]
      
      nr_criminals = len(ans[0])
      nr_answers = len(ans)
      
      # make list of used values
      lst = [set(a[i] for a in ans) for i in range(nr_criminals)]
      
      for ss in solve(lst):
        pos = set(correct(xs, ss) for xs in ans)
        if len(pos) != nr_answers: continue
        
        print(f"{', '.join(ss)} --> {pos}")
      

      Like

      • Jim Randell's avatar

        Jim Randell 2:57 pm on 15 October 2021 Permalink | Reply

        You can use the following to turn a collection of rows into the corresponding collection of columns:

        cols = zip(*rows)
        

        (And [[ unzip() ]] is an alias for this in enigma.py).

        We can also simplify the way the [[ solve() ]] function operates. (I’ve renamed it [[ select() ]]).

        from enigma import unzip, seq_all_different, join, printf
        
        # the six answers
        ans = [
          'PQRSTUV',
          'PQRUTSV',
          'PSUVRTQ',
          'PSQURTV',
          'PURVTSQ',
          'RPUQTSV',
        ]
        
        # select distinct elements from each set in list of sets <ss>
        def select(ss, rs=[]):
          # are we done?
          if not ss:
            yield rs
          else:
            for x in ss[0].difference(rs):
              yield from select(ss[1:], rs + [x])
        
        # count number of items in correct position
        correct = lambda xs, ys: sum(x == y for (x, y) in zip(xs, ys))
        
        # each photograph was identified correctly in at least one answer
        # so the the correct value occurs in each column
        
        # consider possible correct orderings
        for ss in select(list(set(col) for col in unzip(ans))):
          # how many are in the correct position?
          pos = list(correct(xs, ss) for xs in ans)
          if not seq_all_different(pos): continue
          # output solution
          printf("{ss} -> {pos}", ss=join(ss, sep=" ", enc="()"))
        

        I did try a version of [[ select() ]] that chooses from the column with the smallest number of values available, but it is no advantage on a problem of this size.

        Like

  • Unknown's avatar

    Jim Randell 10:11 am on 12 October 2021 Permalink | Reply
    Tags:   

    Teaser 2830: Time for Christmas 

    From The Sunday Times, 18th December 2016 [link] [link]

    For Christmas George has bought Martha a novelty digital 24-hour clock. It has an eight-digit display forming four two-digit numbers. The first three of these two-digit numbers are very conventional – the hours, the minutes and the seconds. However, the final two-digit display shows the sum of the first six displayed digits!

    On two occasions today all eight digits displayed were different and three of the four two-digit numbers were perfect squares. Between those two occasions there was a time when the eight digits displayed were again all different, but this time the sum of the eight digits was a perfect square!

    What was the eight-digit display at that in-between time?

    [teaser2830]

     
    • Jim Randell's avatar

      Jim Randell 10:12 am on 12 October 2021 Permalink | Reply

      If we accept that there are only “two occasions” in any given day, then we don’t need to consider all the times, just look until we find the second condition (p2) occurring between the two occurrences of the first condition (p1).

      This code stops when it finds the second occurrence of the first condition and runs in 61ms.

      Run: [ @replit ]

      from enigma import (irange, flatten, product, printf)
      
      # output format: <hour>:<minute>:<second>:<sum> [<state>]
      fmt = "{h:02d}:{m:02d}:{s:02d}:{t:02d} [{state}]"
      
      # 2 digit squares
      squares = set(i * i for i in irange(0, 8))
      
      # initial state
      state = 0
      
      # consider times in order
      for (h, m, s) in product(irange(0, 23), irange(0, 59), irange(0, 59)):
        # collect digits
        digits = flatten((divmod(x, 10) for x in (h, m, s)), fn=list)
        # add in the sum
        t = sum(digits)
        digits.extend(divmod(t, 10))
        # all digits are different
        if len(set(digits)) != 8: continue
      
        # mark special states
        # p1 = "three of the four two digits numbers are perfect squares"
        p1 = (len(squares.intersection((h, m, s, t))) == 3)
        # p2 = "the sum of all 8 digits is a perfect square"
        p2 = (sum(digits) in squares)
        if not (p1 or p2): continue
      
        # if we're in the initial state, wait until we see a p1
        if state == 0:
          if p1:
            printf(fmt, state='first')
            state = 1
      
        # if we've seen the first p1
        elif state == 1:
          # look for the second p1
          if p1:
            printf(fmt, state='second')
            # and we are done
            break
          # or for an intermediate p2
          elif p2:
            printf(fmt, state='in-between')
      

      Solution: The display at the in-between time was 01:48:59:27.

      The times are:

      01:38:49:25 [first]
      01:48:59:27 [in-between]
      01:49:38:25 [second]

      Like

    • Jim Olson's avatar

      Jim Olson 5:24 pm on 13 October 2021 Permalink | Reply

      Am I missing something but the middle digits sum to 54 which is not a perfect square.

      Like

      • Jim Randell's avatar

        Jim Randell 5:32 pm on 13 October 2021 Permalink | Reply

        At the in-between time the sum of the 8 digits is:

        0+1+4+8+5+9+2+7 = 36 = 6²

        Like

    • Jim Olson's avatar

      Jim Olson 5:51 pm on 13 October 2021 Permalink | Reply

      Thank you I was adding the number 27 instead of the digit sum of 9.

      Like

  • Unknown's avatar

    Jim Randell 4:39 pm on 8 October 2021 Permalink | Reply
    Tags:   

    Teaser 3081: Connect four 

    From The Sunday Times, 10th October 2021 [link] [link]

    I have four different two-digit numbers, each having at least one digit which is a three. When I multiply any three of these numbers together I get a product that, with the inclusion of a leading zero, is one or more repetitions of the repetend of the reciprocal of the fourth two-digit number. A repetend is the repeating or recurring decimal of a number. For example 1 divided by 27 is 0.037037…, giving a repetend of 037; in that case, the product would be 37 or 37037 or 37037037 etc.

    What, in ascending order, are the four two-digit numbers?

    [teaser3081]

     
    • Jim Randell's avatar

      Jim Randell 4:59 pm on 8 October 2021 Permalink | Reply

      This Python program uses the [[ recurring() ]] function from the enigma.py library to calculate the repetends. It runs in 51ms.

      Run: [ @replit ]

      from enigma import (enigma, irange, nsplit, subsets, multiply, format_recurring, printf)
      
      # find the repetend of 1/n (recurring part of the recurring decimal representation)
      recurring = enigma.cache(lambda n: enigma.recurring(1, n, recur=1))
      
      # consider 2-digit numbers containing the digit 3
      ns = list(n for n in irange(10, 99) if 3 in nsplit(n))
      
      # check this set of numbers
      def check(ss, verbose=0):
        m = multiply(ss)
        for n in ss:
          # calculate the product
          p = str(m // n)
          # find the repetend (rr)
          (i, nr, rr) = recurring(n)
          # if the repetend has leading zeros extend the product
          for d in rr:
            if d != '0': break
            p = '0' + p
          # remove copies of the repetend from the product
          k = len(rr)
          while p.startswith(rr):
            p = p[k:]
          # anything left?
          if p: return False
          if verbose: printf("-> 1/{n} = {f}; product = {p}", f=format_recurring((i, nr, rr)), p=m // n)
        # if we get this far we've done it
        return True
      
      # select 4 of the numbers
      for ss in subsets(ns, size=4):
        if check(ss):
          printf("{ss}:")
          check(ss, verbose=1)
          printf()
      

      Solution: The four numbers are: 13, 33, 37, 63.


      Without the requirement that each of the 2-digit number must contain at least one 3, we can find further sets:

      (11, 27, 37, 91)
      (11, 37, 39, 63)
      (13, 21, 37, 99)
      (13, 27, 37, 77)
      (13, 33, 37, 63) [solution to the puzzle]
      (21, 33, 37, 39)

      All these sets have a product of 999999.

      And this is a consequence of the fact that for any fraction f ≤ 1, if a k-length repdigit of 9, multiplied by f gives an integer result, then that result is a k-length repetend of f (suitably padded with leading zeros, and there will be no non-repeating part of the decimal expansion).

      So any set of numbers that multiplies to a 9-repdigit will form such a set.

      For example, multiplying the numbers in the solution to give pairs:

      999999 = (13×33)×(37×63) = 429 × 2331: 1/429 = 0.(002331)…, 1/2331 = 0.(000429)…
      999999 = (13×37)×(33×63) = 481 × 2079: 1/481 = 0.(002079)…, 1/2079 = 0.(000481)…
      999999 = (13×63)×(33×37) = 819 × 1221: 1/819 = 0.(001221)…, 1/1221 = 0.(000819)…

      Or:

      9999999 = 9 × 239 × 4649:
      1/9 = 0.(1)…; 239 x 469 = 1111111
      1/239 = 0.(0041841)…; 9 × 4649 = 41841
      1/4649 = 0.(0002151)…; 9 × 239 = 2151

      Programatically we can easily find 4-sets from the candidates that multiply together to give a 9-repdigit:

      from enigma import (irange, nsplit, subsets, multiply, seq_all_same, printf)
      
      # candidate numbers (can't be divisible by 2 or 5)
      ns = list(n for n in irange(10, 99) if 3 in nsplit(n) and n % 2 and n % 5)
      
      # look for 4-subsets that multiply to a 9-repdigit
      for ss in subsets(ns, size=4):
        p = multiply(ss)
        if seq_all_same(nsplit(p), value=9):
          printf("{ss}; product={p}")
      

      Which we could then pass to the [[ check() ]] function above to verify the solution and output information for each number.

      But there are sets that don’t have a 9-repdigit product, and do have decimal expansions with non-repeating parts, e.g.:

      9990 = 54 × 185
      1/54 = 0.0(185)…
      1/185 = 0.0(054)

      (Although I haven’t found any that aren’t a 9-repdigit multiplied by a power of 10).

      But I did find that the four 2-digit numbers {28, 74, 78, 99} have the following property:

      1/28 = 0.3(571428)…; 74 × 78 × 99 = 571428

      Unfortunately it doesn’t work for the reciprocals of the other terms, but I did notice:

      1/78 = 0.0(128205)… = 0.0128(205128)…; 28 × 74 × 99 = 205128

      Like

      • Jim Randell's avatar

        Jim Randell 11:15 pm on 8 October 2021 Permalink | Reply

        And here is a faster (but slightly longer) version, using the same [[ check() ]] function, that avoids looking at all possible 4-subsets of the candidate numbers, by discarding those with a repetend that is larger than the largest possible product.

        It runs in 48ms (and the internal runtime is 884µs).

        Run: [ @replit ]

        from enigma import (enigma, irange, nsplit, multiply, subsets, format_recurring, printf)
        
        # find the repetend of 1/n (recurring part of the recurring decimal representation)
        recurring = enigma.cached(lambda n: enigma.recurring(1, n, recur=1))
        
        # consider 2-digit numbers containing the digit 3
        ns = list(n for n in irange(10, 99) if 3 in nsplit(n))
        
        # map numbers to repetends
        # [[ and rr[0] == '0' ]] to require repetend has a leading zero
        # [[ and (not nr) ]] to require no non-repeating part
        rep = dict((n, rr) for (n, (i, nr, rr)) in ((n, recurring(n)) for n in ns) if rr)
        
        # remove any numbers with repetends that are too big
        while True:
          M = multiply(ns[-3:])
          ks = list()
          for (k, v) in rep.items():
            if int(v) > M: ks.append(k)
          if not ks: break
          for k in ks: del rep[k]
          ns = sorted(rep.keys())
        
        # check a set of numbers
        def check(ss, verbose=0):
          m = multiply(ss)
          if verbose: printf("{ss}: product = {m}")
          for n in ss:
            # calculate the product
            p = str(m // n)
            # find the repetend (rr)
            rr = rep[n]
            # if the repetend has leading zeros extend the product
            for d in rr:
              if d != '0': break
              p = '0' + p
            # remove copies of the repetend from the product
            k = len(rr)
            while p.startswith(rr):
              p = p[k:]
            # anything left?
            if p: return False
            if verbose: printf("-> 1/{n} = {f}; product = {p}", f=format_recurring(*recurring(n)), p=m // n)
          # if we get this far we've done it
          return True
        
        # select 4 of the numbers
        for ss in subsets(ns, size=4):
          if check(ss):
            check(ss, verbose=1)
            printf()
        

        Like

    • GeoffR's avatar

      GeoffR 10:35 pm on 8 October 2021 Permalink | Reply

      Instead of looking for repeating digit patterns, I thought I would try equating sets of digits in the multiplication of the three two-digit numbers with the digits in the reciprocal of the fourth two-digit number, Not a conventional approach, but it seems to work.

      A set length of nine for reciprocals/multiplications proved adequate to capture the repeating digits patterns for this set comparison method. I omitted the leading zero and decimal point for reciprocal digits set to compare them with multiplication digits. I found the full length decimal reciprocal could give a last digit which was not part of the general repeating pattern.

      from enigma import irange, nsplit
      from itertools import combinations
      
      N4 = []  # list for the groups of four two-digit numbers
      
      # consider 2-digit numbers containing the digit 3
      ns = list(n for n in irange(10, 99) if 3 in nsplit(n))
      
      for p in combinations(ns, 4):
        ab, cd, ef, gh = p
        # check 1st tuple of three numbers and the reciprocal
        s1 = set(str(ab * cd * ef)[:9]).difference(set(['0']))
        r1 = set(str(1 / gh)[:9]).difference(set(['.', '0']))
        if s1 == r1:
          # check 2nd tuple of three numbers and the reciprocal
          s2 = set(str(ab * cd * gh)[:9]).difference(set(['0']))
          r2 = set(str(1 / ef)[:9]).difference(set(['.', '0']))
          if s2 == r2:
            # check 3rd tuple of three numbers and the reciprocal
            s3 = set(str(ab * ef * gh)[:9]).difference(set(['0']))
            r3 = set(str(1 / cd)[:9]).difference(set(['.', '0']))
            if s3 == r3:
              # check 4th tuple of three numbers and the reciprocal
              s4 = set(str(cd * ef * gh)[:9]).difference(set(['0']))
              r4 = set(str(1 / ab)[:9]).difference(set(['.', '0']))
              if s4 == r4:
                L = sorted([ab, cd, ef, gh])
                if L not in N4:
                  N4.append(L)
      
      if len(N4) == 1:
        print(f"Four two digit numbers are {N4[0]}")
      
      
      

      Like

    • Frits's avatar

      Frits 5:09 pm on 9 October 2021 Permalink | Reply

      from enigma import divisors
      
      # consider 2-digit numbers containing the digit 3
      nbrs = list(n for n in range(10, 100) if '3' in str(n))
        
      # retrieve repeating decimals of 1 / n
      def repdec(n):
        c = 100
        dgts = ""
        done = dict()
        i = 0
        while True:
          if c in done:
            rep = dgts[done[c]:]
            if rep[-1] == '0':
              rep = '0' + rep[:-1]
            return rep
          done[c] = i
          q, r = divmod(c, n)
          dgts += str(q)
          c = 10 * r
          i += 1   
         
      # decompose number <t> into <k> numbers from <ns>
      # so that product of <k> numbers equals <t>
      def decompose(t, k, ns, s=[]):
        if k == 1:
          if t in ns and t >= s[-1]:
            yield s + [t]
        else:
          for n in ns:
            if n not in s and (not s or n >= s[-1]):
              yield from decompose(t // n, k - 1, ns.difference([n]), s + [n])
      
      d = dict()
      for n in nbrs:
        rep = repdec(n)
        lngth = len(rep)
        if lngth > 7 or rep == 0: continue 
        r = rep
       
        # extend up to 6 digits
        for _ in range(6 // lngth):
          prod = int(r)
         
          for _ in range(1):     # dummy loop used for breaking
            # 13 * 23 * 30 <= product <= 73 * 83 * 93 
            if not(8970 <= prod <= 563487): break 
           
            # collect all divisors of product
            divs = [x for x in divisors(prod) if x in nbrs]
           
            # skip if less than 3 valid divisors
            if len(divs) < 3: break
           
            # check if product of 3 numbers equals <prod>
            for decomp in decompose(prod, 3, set(divs)):
              n4 = tuple(sorted([n] + decomp))
              d[n4] = d.get(n4, 0) + 1
        
          r += rep  # extend up to 6 digits
      
      # sets of four 2-digit numbers must have been encountered four times
      for k, v in d.items():
        if v == 4:
          print(f"answer: {k}")
       

      Like

      • Frits's avatar

        Frits 11:15 am on 11 October 2021 Permalink | Reply

        or with a recursive function:

          
        # https://codegolf.stackexchange.com/questions/78850/find-the-repetend-of-the-decimal-representation    
        def replist(n, t=1, ns=[], *d):
          return ns[(*d, t).index(t):] or replist(n, (t % n) * 10, ns + [t // n], *d, t)   
         
        # retrieve repeating decimals of 1 / n 
        def repdec(n):
          return "".join(str(x) for x in replist(n)) 
        

        Like

        • Jim Randell's avatar

          Jim Randell 12:34 pm on 11 October 2021 Permalink | Reply

          It’s certainly short, although not necessarily very readable. But it does produce the right answer for positive reciprocals with a non-terminating decimal expansion.

          The puzzle text isn’t completely clear what the intended repetend is for fractions with a normally terminating decimal expansion. You could argue that in this case there is no repetend, and these numbers can be discarded.

          But I prefer the definition that the repetend is the shortest and earliest repeating section of the non-terminating decimal representation of the fraction.

          As every non-zero fraction has a unique non-terminating decimal expansion, this is well-defined.

          And it just so happens it is what the [[ recurring() ]] function returns when [[ recur=1 ]] is set. (The [[ recurring() ]] function was originally written for Enigma 1247, but has proved useful in several other Enigma puzzles).

          So, for example, we have (with repetends in parentheses):

          1/3 = 0.(3)…
          1/1 = 0.(9)…
          1/27 = 0.(037)…
          1/28 = 0.3(571428)…
          1/32 = 0.03124(9)…

          But, it does seem to be possible to solve this puzzle with a less well defined version of “repetend”.

          Like

    • Alex. T,Sutherland's avatar

      Alex. T,Sutherland 1:59 pm on 10 October 2021 Permalink | Reply

      Each of the answer numbers must have a repeatable characteristic.
      (a) Number of possible x3,3x numbers =17. (b) Only 6 from these 17 are repeatable.
      (c) Evaluate any 4 from these 6 (15 possibles) to satisfy the constraints.Ony one solution.
      The sum of the solution is between 140-150. Run time 25ms.

      Like

      • Jim Randell's avatar

        Jim Randell 6:41 pm on 10 October 2021 Permalink | Reply

        @Alex: I found that 8 of the numbers had viable repetends. (i.e. ones where a single repeat was not larger than the largest possible product of three numbers). Or 7 if we exclude numbers with a terminating decimal representation.

        Like

        • Tony Brooke-Taylor's avatar

          Tony Brooke-Taylor 9:50 am on 12 October 2021 Permalink | Reply

          Jim I think if you add the requirement for a leading zero you can reduce this number to five possibilities. I’d be curious to compare my 5 with Alex’s 6 and your 7, once the solution embargo is lifted.

          Like

          • Jim Randell's avatar

            Jim Randell 9:57 am on 12 October 2021 Permalink | Reply

            @Tony: Yes. If you require a leading zero in the repetend, then this reduces the number of candidates to 5. (I took the “inclusion of a leading zero” to be “(where necessary)”, rather than a strict requirement).

            Reducing the set of candidates to 5, means there are only 5 sets of 4 numbers to check.

            In fact multiplying the (numerically) first 3 numbers together, gives the repetend of one of the other 2, so the answer drops out immediately. All that’s left is to check the remaining combinations work as expected. A very simple manual solution.

            Like

    • Jim Olson's avatar

      Jim Olson 4:12 pm on 11 October 2021 Permalink | Reply

      Jim is your EnigmaticCode site down temporarily?

      Like

      • Jim Randell's avatar

        Jim Randell 4:15 pm on 11 October 2021 Permalink | Reply

        Unfortunately, yes.

        It seems WordPress.com suspended the site without warning earlier today. I have contacted them to get it reinstated.

        Hopefully normal service will be resumed before too long.

        Like

    • Jim Randell's avatar

      Jim Randell 6:06 pm on 12 October 2021 Permalink | Reply

      So here’s an interesting fact, repetend fans:

      The repetend of the reciprocal of the square of the k-length 9-repdigit, consists of the concatenation of all the k-length numbers starting from zero, in order, up to the k-length 9-repdigit itself, except that the penultimate k-length number is missing.

      (And there are corresponding results for other bases).

      Let’s see:

      >>> format_recurring(recurring(1, sq(9)))
      '0.(012345679)...'
      
      >>> format_recurring(recurring(1, sq(99)))
      '0.(0001020304050607080910111213141516171819
          2021222324252627282930313233343536373839
          4041424344454647484950515253545556575859
          6061626364656667686970717273747576777879
          80818283848586878889909192939495969799)...'
      
      >>> format_recurring(recurring(1, sq(999)))
      '0.(000001002003004005006007008009010011012013014015016017018019
          020021022023024025026027028029030031032033034035036037038039
          ...
          980981982983984985986987988989990991992993994995996997999)...'
      

      And if you want to know where that penultimate term has gone, here is Numberphile to explain it all [@youtube].

      Like

  • Unknown's avatar

    Jim Randell 9:16 am on 7 October 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 851: Hay fever foiled 

    From The Sunday Times, 6th November 1977 [link]

    Our garden has paths as shown in the plan above. At each junction of paths there is a different variety of plant. The shortest walk (along the paths) from the Marigold to the Sneezewort passes only the Tickseed, and the shortest walk from the Nasturtium to the Quitch passes two plants, one of which is the Lobelia.

    My wife suffers from hay fever and so she never walks past the irritating Rosemary, Sneezewort or Tickseed. We are still able to walk together on the shortest route from the Polyanthus to the Nasturtium (past one other plant), but she has to walk over twice as far as in going from the Orpine to the Marigold.

    List the plants, in order, which my wife passes when taking her shortest route from the Nasturtium to the Marigold.

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser851]

     
    • Jim Randell's avatar

      Jim Randell 9:17 am on 7 October 2021 Permalink | Reply

      This Python program runs in 144ms.

      Run: [ @replit ]

      from enigma import (empty, Accumulator, ordered, tuples, SubstitutedExpression, irange, translate, join, cached, printf)
      
      # adjacency matrix
      adj = [
        [1, 3, 4], # 0
        [0, 2, 5], # 1
        [1, 3, 6], # 2
        [0, 2, 7], # 3
        [0, 5, 7, 8], # 4
        [1, 4, 6, 8], # 5
        [2, 5, 7, 8], # 6
        [3, 4, 6, 8], # 7
        [4, 5, 6, 7], # 8
      ]
      
      # distances for each edge
      dist = {
        (0, 1): 314, (1, 2): 314, (2, 3): 314, (0, 3): 314, # = 100 pi
        (0, 4): 100, (1, 5): 100, (2, 6): 100, (3, 7): 100,
        (4, 5): 157, (5, 6): 157, (6, 7): 157, (4, 7): 157, # = 50 pi
        (4, 8): 100, (5, 8): 100, (6, 8): 100, (7, 8): 100,
      }
      
      # find paths from A to B, avoiding Xs
      def paths(A, B, Xs=empty, s=[]):
        if A == B:
          yield s
        else:
          # move forward from A
          for x in adj[A]:
            if x not in s and x not in Xs:
              yield from paths(x, B, Xs, s + [x])
      
      # find minimal distance paths
      @cached
      def min_path(A, B, Xs=empty):
        r = Accumulator(fn=min, collect=1)
        for s in paths(A, B, Xs, [A]):
          d = sum(dist[ordered(x, y)] for (x, y) in tuples(s, 2))
          r.accumulate_data(d, s)
        return r
      
      # is there a minimal path with n intermediates, that include incs?
      def check(A, B, Xs, n=None, incs=[]):
        incs = set(incs)
        r = min_path(A, B, frozenset(Xs))
        for p in r.data:
          if len(p) == n + 2 and incs.issubset(p): return True
        return False
      
      # check distance avoiding Xs is over twice as far
      def check2(A, B, Xs):
        r1 = min_path(A, B)
        r2 = min_path(A, B, frozenset(Xs))
        return (r2.value > 2 * r1.value)
      
      # make a solver
      p = SubstitutedExpression(
        [
          # M -> S passes only T
          "check(M, S, [], 1, [T])",
          # N -> Q passes L and 1 other
          "check(N, Q, [], 2, [L])",
          # P -> N passes 1 other plant
          "check(P, N, [R, S, T], 1)",
          # but she has to walk over twice as far going from O -> M
          "check2(O, M, [R, S, T])",
        ],
        digits=irange(0, 8),
        env=dict(check=check, check2=check2),
        verbose=0,
      )
      
      # run the solver
      for s in p.solve():
        # map positions to symbols
        m = dict((v, k) for (k, v) in s.items())
        # remove duplicate layouts
        if not (m[1] < m[3] and m[0] < min(m[1], m[2], m[3])): continue
      
        # output solution: (outer) (inner) (centre) -> wife's path from N to M
        printf("{m}", m=translate("({0} {1} {2} {3}) ({4} {5} {6} {7}) ({8})", (lambda x: m[int(x)])))
        r = min_path(s['N'], s['M'], frozenset([s['R'], s['S'], s['T']]))
        for p in r.data:
          printf("-> {p}", p=join((m[x] for x in p), sep=" "))
        printf()
      

      Solution: The plants passed are: Orpine, Polyanthus, Quitch.


      Here is a layout of the plants (reflections/rotations of this layout also work), with the wife’s best route from N to M indicated:

      Like

      • Frits's avatar

        Frits 11:39 am on 7 October 2021 Permalink | Reply

        @Jim, I would say there is no solution as normally “twice as far” would mean twice the distance, not at least twice the distance.

        Like

        • Jim Randell's avatar

          Jim Randell 11:50 am on 7 October 2021 Permalink | Reply

          @Frits: the puzzle text says “she has to walk over twice as far”, so the wife has to travel more than twice the distance of the shortest path.

          Like

  • Unknown's avatar

    Jim Randell 9:16 am on 5 October 2021 Permalink | Reply
    Tags:   

    Teaser 2828: Return to Zenda 

    From The Sunday Times, 4th December 2016 [link] [link]

    Each postcode in Ruritania consists of ten digits, the first three showing the area, the next three showing the town, and the final four showing the house. Just twelve houses have “lucky” postcodes that have no repeated digit and which consist of two three-figure squares followed by a four-figure square. Rudolph lives in such a house and he recently met a girl called Flavia. She only told him that hers was the only house in her area with a lucky postcode, and that her area/town/house numbers were all bigger than his. He has found a postcode satisfying these conditions and sent her a Christmas card, but it has been returned as it was not her postcode.

    What is Rudolph’s postcode?

    [teaser2828]

     
    • Jim Randell's avatar

      Jim Randell 9:16 am on 5 October 2021 Permalink | Reply

      I think the wording of this puzzle could be a bit clearer. But with a reasonable assumption that leading zeros are not allowed in the squares we get a unique solution.

      This Python program runs in 54ms.

      Run: [ @replit ]

      from enigma import (irange, is_duplicate, cproduct, subsets, filter_unique, unpack, join, printf)
      
      # find n-digit squares from a^2 to b^2 with no repeated digits (as
      # strings) leading zeros are allowed
      def squares(n, a, b):
        r = list()
        for i in irange(a, b):
          s = str(i * i).zfill(n)
          if not is_duplicate(s):
            r.append(s)
        return r
      
      # 3 and 4 digit squares
      s3s = squares(3, 10, 31)
      s4s = squares(4, 32, 99)
      
      # collect "lucky" (pandigital) postcodes
      lucky = list((area, town, house)
        for (area, town, house) in cproduct([s3s, s3s, s4s])
          if len(set(area + town + house)) == 10)
      printf("[{n} lucky postcodes]", n=len(lucky))
      
      # choose the 12 lucky postcodes
      for postcodes in subsets(lucky, size=12):
      
        # flavia's postcode is the only (possible) lucky postcode in the area
        fs = filter_unique(postcodes, unpack(lambda a, t, h: a)).unique
      
        # consider rudolph's postcode
        for (ra, rt, rh) in postcodes:
          # find possible postcodes for flavia
          f = list((a, t, h) for (a, t, h) in fs if a > ra and t > rt and h > rh)
          # and there must be more than one possibility
          if len(f) > 1:
            fmt = lambda s: join(s, sep=":", enc="()")
            printf("{r} -> {f}",r=fmt((ra, rt, rh)), f=join(map(fmt, f), sep=' '))
      

      Solution: Rudolph’s postcode is (324:576:1089).

      And there are two possible postcodes for Flavia: (961:784:3025) and (361:784:9025). So Rudolph chose the wrong one.

      We can allow the squares to have leading zeros by changing the value of the [[ a ]] parameter in the calls to [[ squares() ]]. But if we do this we find that are many possible solutions.


      The 12 “lucky” postcodes (area:town:house) are:

      (169:784:3025)
      (196:784:3025)
      (324:576:1089)
      (324:576:9801)
      (361:784:9025)
      (576:324:1089)
      (576:324:9801)
      (784:169:3025)
      (784:196:3025)
      (784:361:9025)
      (784:961:3025)
      (961:784:3025)

      Flavia’s postcode is unique by area:

      (169:784:3025)
      (196:784:3025)
      (361:784:9025)
      (961:784:3025)

      And Rudolph’s postcode must be one of the 12 lucky code above, that has two candidate postcodes for Flavia that are larger in each component. There is only one possibility for Rudolph’s postcode:

      (324:576:1089) → (361:784:9025) or (961:784:3025)


      Without leading zeros there are exactly 12 lucky postcodes, so we know all possible lucky postcodes are in use. (And this is probably the scenario the setter had in mind).

      However, if we allow leading zeros in the squares we can find 34 lucky postcodes, but we know only 12 of them are in use. But how does Flavia know that hers is the only lucky postcode in her area? Is it the only possible lucky postcode in her area? Or just the only one currently in use? In my program I have implemented the latter.

      And using this interpretation we find there are many thousands of situations that lead to a solution, with 6 possible values for Rudolph’s postcode. So I think it is probably safe to assume that the setter intended there to be no leading zeros in the squares.

      Like

      • Frits's avatar

        Frits 11:49 am on 9 June 2025 Permalink | Reply

        or more efficient

        # collect "lucky" (pandigital) postcodes
        lucky = list()
        for (area, house) in product(s3s, s4s):
          if len(ah := set(area + house)) == 7:
            for town in s3s:
              if ah.isdisjoint(town):
                lucky.append((area, town, house))
        

        Like

    • Frits's avatar

      Frits 11:53 am on 5 October 2021 Permalink | Reply

      Checking lucky postcodes outside “SubstitutedExpression” would have been easier and faster but this was more challenging.

        
      from enigma import SubstitutedExpression
      
      # the alphametic puzzle 
      p = SubstitutedExpression(
        [# Rudolph
         "is_square(ABC)",
         "is_square(DEF)",
         "is_square(GHIJ)",
         
         # does more than one Flavia candidate exist for Rudolph (ABC:DEF:GHIJ) ? 
         # Flavia's postcode (a:b:c) is unique by area
         "sum(1 for a in range(ABC + 1, 1000) if is_square(a) and \
                   sum(1 for t in range(100, 1000) if is_square(t) \
                           if len(set(str(a) + str(t))) == 6 \
                         for u in range(1000, 10000) if is_square(u) and \
                         len(set(str(a) + str(t) + str(u))) == 10 \
                       ) == 1 \
                for b in range(DEF + 1, 1000) if is_square(b) \
                  if len(set(str(a) + str(b))) == 6 \
                for c in range(GHIJ + 1, 10000) if is_square(c) and \
                len(set(str(a) + str(b) + str(c))) == 10 \
             ) > 1",
         
        ],
        answer="(str(ABC) + ':' + str(DEF) + ':' + str(GHIJ))",
        distinct=("ABCDEFGHIJ"),
        verbose=0,
      )
      
      # print answer
      for (_, ans) in p.solve():
        print(f"Rudolph's postcode is {ans}")
      

      Like

    • Frits's avatar

      Frits 1:33 pm on 5 October 2021 Permalink | Reply

      Similar.

        
      import sqlite3
      
      sqs = [str(i**2) for i in range(10, 100)]
      
      luckies = []
      for a in [x for x in sqs if len(x) == 3]:
        for b in [x for x in sqs if len(x) == 3]:
          ab = a + b
          if len(set(ab)) != 6: continue
          for c in [x for x in sqs if len(x) == 4]:
            if len(set(ab + c)) != 10: continue
            luckies.append([a, b, c])
      
      # connect to SQLite database that resides in the memory
      sqlite_Connection = sqlite3.connect('temp.db')
      c1 = sqlite3.connect(':memory:')
      
      c1.execute("CREATE TABLE postcodes ( \
                          area INT, \
                          town INT, \
                          house INT \
                  )")
      
      # insert entries into table
      for (x, y, z) in luckies:
        stmnt = "INSERT INTO postcodes VALUES ("
        stmnt += x + ", " + y + ", " + z + ")"
        c1.execute(stmnt)
      
      c1.commit()
      
      stmnt = "SELECT area, town, house, " 
      stmnt += "(SELECT COUNT(1) FROM postcodes B" 
      stmnt += " WHERE B.AREA > A.AREA and" 
      stmnt += "       B.TOWN > A.TOWN and" 
      stmnt += "       B.HOUSE > A.HOUSE and"
      stmnt += "       NOT EXISTS (SELECT 1"  # Flavia's postcode is unique by area
      stmnt += "         FROM postcodes C"
      stmnt += "          WHERE C.AREA = B.AREA and" 
      stmnt += "          (C.TOWN != B.TOWN or"
      stmnt += "           C.HOUSE != B.HOUSE)" 
      stmnt += "         )"
      stmnt += "       ) as nFlavia "
      stmnt += "FROM postcodes A " 
      stmnt += "WHERE nFlavia > 1;"
      
      for ans in c1.execute(stmnt):
        print(f"Rudolph's postcode is {ans[:-1]}")
        
      c1.close()  
      
      if (sqlite_Connection):
        sqlite_Connection.close()
      

      Like

    • GeoffR's avatar

      GeoffR 3:41 pm on 5 October 2021 Permalink | Reply

      A good variety of solutions for this teaser.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      set of int: sq3 = {x*x | x in 10..31};  
      set of int: sq4 = {x*x | x in 32..99};
       
      % Rudolph's digits in 3:3:4 digits for area:town:house numbers
      var 1..9:A; var 0..9:B; var 0..9:C; 
      var 1..9:D; var 0..9:E; var 0..9:F; 
      var 1..9:G; var 0..9:H; var 0..9:I; var 0..9:J;
      constraint all_different ([A,B,C,D,E,F,G,H,I,J]);
      
      % Flavia's digits - 1st set are abc:def:ghij
      var 1..9:a; var 0..9:b; var 0..9:c; 
      var 1..9:d; var 0..9:e; var 0..9:f; 
      var 1..9:g; var 0..9:h; var 0..9:i; var 0..9:j;
      constraint all_different([a,b,c,d,e,f,g,h,i,j]);
      
      % Flavia's digits - 2nd set are mno:pqr:stuv
      var 1..9:m; var 0..9:n; var 0..9:o; 
      var 1..9:p; var 0..9:q; var 0..9:r; 
      var 1..9:s; var 0..9:t; var 0..9:u; var 0..9:v;
      constraint all_different ([m,n,o,p,q,r,s,t,u,v]);
      
      % Rudolph's numbers
      var 100..999: ABC = 100*A + 10*B + C;
      var 100..999: DEF = 100*D + 10*E + F;
      var 1000..9999: GHIJ = 1000*G + 100*H + 10*I + J;
      
      % Flavia's numbers - 1st set
      var 100..999: abc = 100*a + 10*b + c;
      var 100..999: def = 100*d + 10*e + f;
      var 1000..9999: ghij = 1000*g + 100*h + 10*i + j;
      
      % Flavia's numbers - 2nd set
      var 100..999: mno = 100*m + 10*n + o;
      var 100..999: pqr = 100*p + 10*q + r;
      var 1000..9999: stuv = 1000*s + 100*t + 10*u + v;
      
      % Square constraints for Rudolph's and Flavia's numbers
      constraint ABC in sq3 /\ DEF in sq3 /\ GHIJ in sq4;
      constraint abc  in sq3 /\ def in sq3 /\ ghij in sq4;
      constraint mno in sq3 /\ pqr in sq3 /\ stuv in sq4;
      
      % Flavia's area/town/house numbers were all bigger than Rodolph's numbers
      % We conclude she has two numbers as Rudolph made a mistake on the first.
      constraint abc > ABC /\ def > DEF /\ ghij > GHIJ;
      constraint mno > ABC /\ pqr > DEF /\ stuv > GHIJ;
      
      % Flavia's two sets of numbers are different areas/ house numbers
      % ... but make Flavia's town the same in both sets of numbers
      constraint mno > abc /\ pqr == def /\ ghij > stuv;
      
      solve satisfy;
      
      output ["Rudolph's numbers : " ++ show([ABC, DEF, GHIJ])
      ++ "\n" ++ "Flavia's numbers (1st set): " ++ show([abc, def,ghij])
      ++ "\n" ++ "Flavia's numbers (2nd set): " ++ show([mno, pqr, stuv]) ];
      
      % Rudolph's numbers : [324, 576, 1089]
      % Flavia's numbers (1st set): [361, 784, 9025]
      % Flavia's numbers (2nd set): [961, 784, 3025]
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:06 am on 3 October 2021 Permalink | Reply
    Tags: by: Cdr. P. A. Maitland   

    Brain Teaser: What’s his number? 

    From The Sunday Times, 20th December 1959 [link]

    A professor of mathematics was driving in an unfamiliar transatlantic city to visit a friend who lived in a very long street. Stopping to check the street-name he found he had entered the street he was looking for at the end where the high numbers finished. The final number started a train of thought: after a short calculation he smiled in satisfaction and drove on to his friend’s house.

    His friend was astonished when he said: “I have just discovered a most curious thing: did you know that, in this street, the sum of the house-numbers larger than your number is equal to the sum of the house-numbers smaller than your number?”

    Given that the street contained between 500 and 3,500 houses, how many houses were there in it and what was the number of the friend’s house?

    This is one of the occasional Holiday Brain Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of 3 guineas was offered.

    [teaser-1959-12-20] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 9:06 am on 3 October 2021 Permalink | Reply

      (See also: Enigma 1725).

      We assume the houses are numbered consecutively from 1 without duplication or omission.

      If the friend’s house is x of y, we have:

      sum(1 … x − 1) = sum(x + 1 … y)
      sum(1 … x − 1) = sum(1 … y) − sum(1 … x)
      T(x − 1) + T(x) = T(y)
      (1/2)(x − 1)x + (1/2)x(x + 1) = T(y)
      x² = T(y)

      And y is in the range 500 to 3500.

      We can easily solve this programatically. The following short Python program runs in 53ms.

      Run: [ @replit ]

      from enigma import (irange, tri, is_square, printf)
      
      for y in irange(500, 3500):
        x = is_square(tri(y))
        if x:
          printf("house {x} of {y}")
      

      Solution: There were 1681 houses in the street. The friend’s house was number 1189.


      Manually, we need to find a triangular number that is also a square number (and in a specific range).

      A recurrence relation for the square triangular numbers [@wikipedia] is:

      N[0] = 0
      N[1] = 1
      N[k + 2] = 34 N[k + 1] − N[k] + 2

      and if: N[k] = (s[k])² = T[t[k]], we have:

      s[0] = t[0] = 0
      s[1] = t[1] = 1
      s[k + 2] = 6 s[k + 1] − s[k]
      t[k + 2] = 6 t[k + 1] − t[k] + 2

      Calculating these sequences we see:

      s[2] = 6; t[2] = 8
      s[3] = 35; t[3] = 49
      s[4] = 204; t[4] = 288
      s[5] = 1189; t[5] = 1681
      s[6] = 6930; t[6] = 9800

      Only the values for k = 5 are in the required range, so:

      x = s[5] = 1189
      y = t[5] = 1681

      We can implement these steps programatically as follows:

      from enigma import (fib, unpack, printf)
      
      S = fib(0, 1, fn=unpack(lambda a, b: 6 * b - a))
      T = fib(0, 1, fn=unpack(lambda a, b: 6 * b - a + 2))
      
      for (k, (s, t)) in enumerate(zip(S, T)):
        f = (499 < t < 3501)
        printf("s[{k}] = {s}; t[{k}] = {t} {f}", f=['', '[*** SOLUTION ***]'][f])
        if t > 3500: break
      

      As these sequences increase t[k] / s[k] gives rational approximations to √2. In the case of the solution we have:

      t[5] / s[5] = 1681 / 1189 = 41 / 29 = 1.4137931…

      Like

    • John Crabtree's avatar

      John Crabtree 2:55 pm on 5 October 2021 Permalink | Reply

      Fortunately for manual solvers this brain teaser is accessible via the Pell equation, ie
      (2y + 1)^2 – 2(2x)^2 = 1
      where (2y + 1)/(2x) are rational approximations to √2.
      Letting a = 2x and b = 2y + 1 leads to
      a[0] = 0, b[0] = 1, x[0] = 0, y[0] = 0
      a[1] = 2, b[1] = 3, x[1] = 0, y[1] = 1
      a[2] = 12, b[2] = 17, x[2] = 6, y[2] = 8
      a[k + 2] = 6 a[k + 1] – a[k]
      b[k + 2] = 6 b[k + 1] – b[k]
      Then one needs to calculate three more (a, b) pairs so that y is in the permitted range.

      My guess is that this was the first appearance of the Pell equation in a ST Brain Teaser.

      Like

    • GeoffR's avatar

      GeoffR 6:36 pm on 7 October 2021 Permalink | Reply

      I programmed this teaser as the arithmetic sum of the house numbers left (smaller) and right (larger) than my friend’s house number.

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 500..3500: N;    % His_House
      var 500..3500: L;    % Last_House
      
      % There are (N - 1) houses left of house N, and last house is (N - 1)
      % There are (L - N) houses right of house N, and 1st house is (N + 1)
      % Using the arithmetic sum formula: S = n/2 * (2*a + (n-1)* d)
      % Left sum of numbers = Right sum of numbers
      
      constraint (N - 1) * N div 2 == (L - N) * (2 *(N + 1) + (L - N - 1)) div 2;
      
      solve satisfy;
      
       output [ "Number of my friend's house is " ++ show(N)
       ++ "\n" ++ "Number of houses in road is " ++ show(L) ];
       
      % Number of my friend's house is 1189
      % Number of houses in road is 1681
      % ----------
      % ========== 
      
      

      Like

    • GeoffR's avatar

      GeoffR 8:53 am on 8 October 2021 Permalink | Reply

      A short Python programme verified the answer.

      # check sums of lower and higher house numbers for his number (1189)
      from enigma import irange
      
      lower_sum, higher_sum = 0, 0
      
      # sum of numbers lower than his number
      for n in irange(1, 1188):
          lower_sum += n
      
      # sum of numbers higher than his number
      for m in irange(1190, 1681):
          higher_sum += m
      
      print(f"Lower sum = {lower_sum}, Higher sum = {higher_sum}")
      # Lower sum = 706266, Higher sum = 706266
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:15 pm on 1 October 2021 Permalink | Reply
    Tags:   

    Teaser 3080: One of a kind 

    From The Sunday Times, 3rd October 2021 [link] [link]

    The raffle tickets at the Mathematical Society Dinner were numbered from 1 to 1000. There were four winning tickets and together they used each of the digits from 0 to 9 once only. The winning numbers could be classified uniquely as one square, one cube, one prime and one triangular number. For example, 36 is a triangular number as 1+2+3+4+5+6+7+8 = 36, but it cannot be a winner as 36 is also a square. The tickets were all sold in strips of five, and two of the winning numbers were from consecutive strips. The first prize was won by the holder of the smallest-numbered winning ticket, which was not a cube.

    List the four winning numbers in ascending order.

    [teaser3080]

     
    • Jim Randell's avatar

      Jim Randell 4:34 pm on 1 October 2021 Permalink | Reply

      The following Python program runs in 64ms.

      Run: [ @replit ]

      from enigma import (irange, singleton, is_cube, is_square, is_triangular, is_prime, empty, tuples, printf)
      
      # collect numbers with no repeated digits as strings
      def collect(fns, a=1, b=1000):
        rs = list(list() for _ in fns)
        for n in irange(a, b):
          # remove numbers with repeated digits
          s = str(n)
          if len(s) != len(set(s)): continue
          # does the number satisfy a single condition
          j = singleton(i for (i, fn) in enumerate(fns) if fn(n))
          if j is not None: rs[j].append(s)
        return rs
      
      # collect each category
      cats = collect([is_cube, is_square, is_triangular, is_prime])
      
      # find members of each set, such that each digit is used exactly once
      def solve(ss, ns=[], ds=empty):
        # are we done?
        if not ss:
          if len(ds) == 10:
            yield ns
        else:
          # choose an element from the next set
          for n in ss[0]:
            if not ds.intersection(n):
              yield from solve(ss[1:], ns + [n], ds.union(n))
      
      # allocate tickets to strips
      strip = lambda n: (n + 4) // 5
      
      # find candidate winning tickets
      for ns in solve(cats):
        # smallest number is not a cube
        ns = sorted(int(n) for n in ns)
        if is_cube(ns[0]): continue
        # find numbers on consecutive strips
        if any(b == a + 1 for (a, b) in tuples(map(strip, ns), 2)):
          printf("{ns}")
      

      Solution: The winning numbers are: 49, 53, 216, 780.

      Like

    • GeoffR's avatar

      GeoffR 12:05 pm on 4 October 2021 Permalink | Reply

      For the 10 unique digits used in this teaser, I found only two arrangements to partition the 10 different digits to give four numbers, so that no number was greater than 1000.

      These two 10-digit arrangements were 1:3:3:3 and 2:2:3:3.

      The first 10-digit arrangement i.e. 1:3:3:3 did not produce a solution, using a similar MiniZinc programme for the second 10-digit arrangement i.e. 2:2:3:3, shown below.

      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 1..9:A; var 0..9:B; var 1..9:C; var 1..9:D; var 1..9:E; 
      var 0..9:F; var 0..9:G; var 1..9:H; var 0..9:I; var 0..9:J; 
      
      % four winning tickets used each of the digits from 0 to 9 once only
      constraint all_different([A, B, C, D, E, F, G, H, I, J]);
      
      predicate is_sq(var int: y) =
        exists(z in 1..ceil(sqrt(int2float(ub(y))))) (
              z*z = y );
              
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
      
      predicate is_tri( var int: n) =
        exists ( x in 1..n div 2)
             ( x * (x+1) div 2 = n);
      
      predicate is_cube(var int: c) =
         let {
            var 0..ceil(pow(int2float(ub(c)),(1/3))): z
         } in z*z*z = c;
         
      % Assume number digit splits are 2:2:3:3 to use the ten different digits
      var 10..99:AB = 10*A + B;
      var 10..99:CD = 10*C + D;
      var 100..999:EFG = 100*E + 10*F + G;
      var 100..999:HIJ = 100*H + 10*I + J;
      
      % Reducing 24 no.possible permutations, for 4 items, 
      % to say 9 permutations for two repeated groups of digit patterns,
      % omitting AB as a cube for the first 2-digit number, as specified,
      % proved sufficient to find the unique answer for the four numbers.
      constraint 
         (is_sq(AB) /\ is_prime(CD) /\ is_cube(EFG) /\ is_tri(HIJ)  )
      \/ (is_sq(AB) /\ is_cube(CD) /\ is_prime(EFG) /\ is_tri(HIJ)  )
      \/ (is_sq(AB) /\ is_tri(CD) /\ is_prime(EFG) /\ is_cube(HIJ)  )
      
      \/ (is_prime(AB) /\ is_sq(CD) /\ is_cube(EFG) /\ is_tri(HIJ)  )
      \/ (is_prime(AB) /\ is_cube(CD) /\ is_sq(EFG) /\ is_tri(HIJ)  )
      \/ (is_prime(AB) /\ is_tri(CD) /\ is_sq(EFG) /\ is_cube(HIJ)  )
      
      \/ (is_tri(AB) /\ is_sq(CD) /\ is_prime(EFG) /\ is_cube(HIJ)  )
      \/ (is_tri(AB)/\ is_prime(CD) /\ is_sq(EFG)  /\ is_cube(HIJ)  )
      \/ (is_tri(AB) /\ is_cube(CD) /\ is_sq(EFG) /\ is_prime(HIJ)  );
      
      % put four numbers in increasing order
      constraint increasing ( [ AB, CD, EFG, HIJ]);
      
      % two of the winning numbers were from consecutive strips of 5 tickets
      constraint ((CD + 4) div 5 - (AB + 4) div 5 == 1)
      \/ ((EFG + 4) div 5 - (CD + 4) div 5 == 1)
      \/ ((HIJ + 4) div 5 - (EFG + 4) div 5 == 1) ;
      
      output [ "Four numbers are " ++ show([AB, CD, EFG, HIJ]) ];
      

      Like

    • Frits's avatar

      Frits 3:29 pm on 4 October 2021 Permalink | Reply

      from enigma import SubstitutedExpression
      from itertools import permutations as perm
      
      checknum = lambda n: n in td
      gettype = lambda n: td[n]
      
      # two ticket numbers must be in adjacent strips
      def consecutive(li):
       strips = [(x - 1) // 5 for x in li]
       if any(strips[i + 1] == x + 1 for i, x in enumerate(strips[:-1])):
         return True
       return False
      
      # perfect squares and perfect cubes between 1000 and 9999
      squares = set(i ** 2 for i in range(1, 32))
      # as smallest ticket may not be a cube start with 27
      cubes = set(i ** 3 for i in range(3, 11))
      tris = set((i * (i + 1)) // 2 for i in range(1, 45))
      
      # set of prime numbers up to 997
      P = {2, 3, 5, 7}
      P |= {x for x in range(11, 33, 2) if all(x % p for p in P)}
      P |= {x for x in range(33, 1000, 2) if all(x % p for p in P)}
      
      td = dict()  # dictionary of tickets
      # build dictionary of "classified uniquely" ticket numbers
      for n in range(1, 1000):
       # skip numbers with duplicate digits
       if len(set(str(n))) != len(str(n)): continue
       cnt = 0
       for i, y in enumerate([squares, cubes, tris, P]):
         if n in y:
           cnt += 1
           i1 = i
       # classified uniquely?
       if cnt == 1:
         td[n] = i1
      
      # determine digits that occur in all cubes
      cubes = [k for k, v in td.items() if v == 1]
      mand_dgts = set(str(cubes[0]))
      for c in cubes[1:]:
       mand_dgts &= set(str(c))
      
      # remove non-cube entries which use digits that occur in all cubes
      if mand_dgts:
       td = dict((k, v) for k, v in td.items()
                 if v == 1 or all(c not in str(k) for c in mand_dgts))
      
      # the alphametic puzzle (numbers AB, CDE, FGH and IJK are increasing)
      p = SubstitutedExpression(
       [
        "A == 0 or C == 0",   # AB and CDE must have total length of 4
        "A + C = X",          # X is non-zero and represents A or C
      
        # check AB
        "checknum(AB)",
        "gettype(AB) != 1",   # not a cube
      
        # check CDE
        "AB < CDE and checknum(CDE)",
        "gettype(CDE) not in {gettype(AB)}",
        "consecutive([AB, CDE]) = Y",
      
        # check FGH
        "C < F",
        "checknum(FGH)",
        "gettype(FGH) not in {gettype(AB), gettype(CDE)}",
        "Y or (G == 9 and F < 8) or consecutive([CDE, FGH])",
        #"consecutive([CDE, FGH]) = Z",
        #"Y or Z or (G == 9 and F < 8)",
      
        # check IJK
        "F < I",
        "checknum(IJK)",
        "gettype(IJK) not in {gettype(AB), gettype(CDE), gettype(FGH)}",
      
        # two ticket numbers must be in adjacent strips
        #"consecutive([AB, CDE, FGH, IJK])",
        "Y or consecutive([CDE, FGH, IJK])",
        #"Y or Z or consecutive([FGH, IJK])",
       ],
      
       answer="AB, CDE, FGH, IJK",
       verbose=0,
       d2i=dict([(0, "FI")] +
                [(1, "I")] +
                [(8, "C")] +
                [(9, "CF")]),
       distinct="BDEFGHIJKX",
       env=dict(consecutive=consecutive, checknum=checknum, gettype=gettype),
       reorder=0,
      )
      
      # print answer
      for (_, ans) in p.solve():
       print(f"{ans}")
      

      Like

    • GeoffR's avatar

      GeoffR 4:47 pm on 4 October 2021 Permalink | Reply

      This Python programme lists all 3-digit primes, squares, cubes and triangular numbers, without repeating digits, with reference to a 1:3:3:3 digits split solution.

      from enigma import is_prime
      
      L1, L2, L3, L4 = [], [], [], []
      
      #1. 3-digit squares without repeating digits
      print('3-digit squares')
      L1 = [ x * x for x in range(10, 32) if len(set(str(x*x))) == len(str(x*x)) ]
      print(L1)
      
      #2. set of 3-digit tri numbers, without repeating digits
      print('3-digit triangular numbers')
      for n in range(14,45):
          tri = n * (n + 1)//2
          if len(set(str(tri))) == len(str(tri)):
              L2.append(tri)
      
      print(L2); print(len(L2))
      
      #3. 3-digit cubes. without repeating digits
      print('3-digit cubes')
      
      L3 = [ x*x*x for x in range(5,10) if len(set(str(x*x*x))) == len(str(x*x*x)) ]
      print(L3)
      
      #4. 3-digit primes, without repeating digits
      print('3-digit primes')
      for n in range(100,1000):
          if is_prime(n) and len(set(str(n))) == len(str(n)):
              L4.append(n)
      
      print(L4); print(len(L4))
      
      # 3-digit squares, non repeating digits
      # [169, 196, 256, 289, 324, 361, 529, 576, 625, 729, 784, 841, 961] 13 no.
      
      # 3-digit triangular numbers, non repeating digits
      # 105, 120, 136, 153, 190, 210, 231, 253, 276, 325, 351, 378,
      # 406, 435, 465, 496, 528, 561, 630, 703, 741, 780, 820, 861,
      # 903, 946] 26 no.
      
      # 3-digit cubes, non repeating digits
      # [125, 216, 512, 729] 4 no.
      
      # 3-digit primes, non repeating digits
      # 103, 107, 109, 127, 137, 139, 149, 157, 163, 167, 173, 179,
      # 193, 197, 239, 241, 251, 257, 263, 269, 271, 281, 283, 293,
      # 307, 317, 347, 349, 359, 367, 379, 389, 397, 401, 409, 419,
      # 421, 431, 439, 457, 461, 463, 467, 479, 487, 491, 503, 509,
      # 521, 523, 541, 547, 563, 569, 571, 587, 593, 601, 607, 613,
      # 617, 619, 631, 641, 643, 647, 653, 659, 673, 683, 691, 701,
      # 709, 719, 739, 743, 751, 761, 769, 809, 821, 823, 827, 829,
      # 839, 853, 857, 859, 863, 907, 937, 941, 947, 953, 967, 971,
      # 983] 97 no.
      
      
      
      

      Manual Analysis – why a 1:3:3:3 digits split is not viable
      ———————————————————-

      Considering a 1:3:3:3 digits split, the single digit can be a square (1,4,9), a prime (2,3,5), a triangular number (1,3,6) but not a cube(1,8) as per teaser conditions.

      For any combination of the three remaining 3-digit numbers, I found there was usually a duplicate hundreds digit (1-9), after choosing the first 3-digit number. The 400 series for 3-digit squares, as 400, 441 etc was excluded due to repeating digits.

      Obviously, this prevents finding four separate numbers with ten different digits.

      I also checked for any possibility of consecutive numbers in two strips of five tickets at the end of one hundred series e.g. end of 190’s and the start of a new hundred series e.g. start of 200’s.

      One instance of a 3-digit triangular number(496) and a consecutive 3-digit prime(503) was found, leaving the digits (1,2,7,9) to form a 3-digit cube and a single digit square, or a single digit cube and a 3-digit square. This was not found to be possible. The only 3-digit numbers found were either not in any of the four number categories, or duplicate primes to 503.

      The over-riding reasons why three 3-digit suitable numbers could not be found was the duplication of the hundreds digit and the teaser requirement to obtain two numbers in two consecutive strips of five tickets.

      Like

  • Unknown's avatar

    Jim Randell 10:04 am on 30 September 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 869: New type football 

    From The Sunday Times, 19th March 1978 [link]

    In this puzzle four football teams are going to play each other once during the course of the season. The incomplete table below shows the situation when some of the matches have been played (2 points for a win, 1 for a draw as usual).

    The digits have been replaced by letters and each letter stands for the same digit wherever it appears and different letters stand for different digits.

    The table looks like this:

    List the matches played and the score in each match.

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book.

    [teaser869]

     
    • Jim Randell's avatar

      Jim Randell 10:04 am on 30 September 2021 Permalink | Reply

      We know the goals for/against values for A and B, so we can calculate scores for matches involving A or B, which only leaves the score in the C vs D match to be decided, and as the overall goals for C is t, we can calculate potential scores for C in the match. If the match is a draw, then D’s score is the same as C’s. If it is a win for C, then D scored fewer goals. And if it is a win for D, then D scored more goals. I put an upper limit on the number of goals in this last case, but it is not a situation that is reached.

      This Python program uses the [[ Football() ]] helper class from the enigma.py library. It runs in 54ms.

      Run: [ @replit ]

      from enigma import Football, irange
      
      # scoring system
      football = Football(points=dict(w=2, d=1))
      
      # the table
      table = dict(played='x?pk', w='?hx?', l='??h?', d='k?k?', points='??m?')
      (gf, ga) = ('hmt?', 'pm??')
      
      # label the teams
      (A, B, C, D) = (0, 1, 2, 3)
      
      # find match outcomes
      for (ms, d) in football.substituted_table(table):
      
        # calculate scores for games involving A and B
        for ss in football.substituted_table_goals(gf, ga, ms, d=d, teams=[A, B]):
      
          # only the C vs D match is undecided
          sCD = list()
          m = ms[(C, D)]
          if m == 'x':
            sCD = [None]
          else:
            # calculate known goals for/against C
            (f, a) = football.goals([ss[(A, C)], ss[(B, C)]], [1, 1])
            # so the score in the C vs D match is (x, y)
            for x in irange(0, 9 - f):
              t = f + x
              if t in d.values(): continue
              if m == 'd':
                sCD.append((x, x))
              elif m == 'w':
                sCD.extend((x, y) for y in irange(0, x - 1))
              elif m == 'l':
                sCD.extend((x, y) for y in irange(x + 1, 10))  # upper limit on goals scored
      
          for ss[(C, D)] in sCD:
            # output solution
            football.output_matches(ms, ss, teams="ABCD", d=d)
      

      Solution: The played matches are: A vs B = 0-0; A vs C = 0-3; B vs C = 5-5; C vs D = 1-0.

      The A vs D, B vs D matches are not yet played.

      Like

  • Unknown's avatar

    Jim Randell 9:49 am on 28 September 2021 Permalink | Reply
    Tags:   

    Teaser 2827: Password 

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

    My computer password consists of different digits written in decreasing order.

    I can rearrange the digits to form a perfect cube.

    A further rearrangement gives a perfect square.

    Another rearrangement gives a prime number.

    A further rearrangement gives a number that is divisible by the number of digits in the password.

    Yet another rearrangement gives a number that is divisible by all but one of its digits.

    What is my password?

    [teaser2827]

     
    • Jim Randell's avatar

      Jim Randell 9:50 am on 28 September 2021 Permalink | Reply

      This Python program runs in 245ms.

      Run: [ @replit ]

      from enigma import group, unpack, powers, inf, is_duplicate, ordered, nsplit, intersect, nconcat, subsets, is_prime, find, update, printf
      
      # collect numbers by digit content
      def numbers(s):
        def generate():
          for n in s:
            if n > 9876543210: break
            # only collect numbers with distinct digits
            if is_duplicate(n): continue
            yield (ordered(*nsplit(n), reverse=1), n)
        return group(generate(), by=unpack(lambda ds, n: ds), f=unpack(lambda ds, n: n))
      
      cubes = numbers(powers(0, inf, 3))
      squares = numbers(powers(0, inf, 2))
      
      divides = lambda n, ds: sum(d != 0 and n % d == 0 for d in ds)
      
      # select distinct elements from each set
      def select(ss, xs):
        i = find(xs, None)
        if i == -1:
          yield xs
        else:
          # choose an elements from ss[i]
          for x in ss[i]:
            if x not in xs:
              yield from select(ss, update(xs, [(i, x)]))
      
      # look for keys common to squares and cubes
      for ds in intersect(s.keys() for s in (squares, cubes)):
        k = len(ds)
      
        # find rearrangements (we need at least 6)...
        rs = set(nconcat(*s) for s in subsets(ds, size=k, select="P") if s[0] > 0)
        if len(rs) < 6: continue
      
        # ... divisible by k
        r1s = set(n for n in rs if n % k == 0)
        if not r1s: continue
      
        # ... divisible by all but one of the digits in ds
        r2s = set(n for n in rs if divides(n, ds) == k - 1)
        if not r2s: continue
      
        # ... primes
        r3s = set(n for n in rs if is_prime(n))
        if not r3s: continue
      
        # select distinct members for each set
        n = nconcat(*ds)
        for xs in select([[n], cubes[ds], squares[ds], r3s, r1s, r2s], [None] * 6):
          printf("password = {n}")
          printf("-> cubes = {rs}", rs=cubes[ds])
          printf("-> squares = {rs}", rs=squares[ds])
          printf("-> primes = {r3s}")
          printf("-> divisible by {k} = {r1s}")
          printf("-> divisible by all but one of {ds} = {r2s}")
          printf("-> selected = {xs}")
          break  # only need one example
      

      Solution: The password is 9721.

      The program ensures five different rearrangements of the password satisfying the conditions can be made, for example:

      9721 = password
      2197 = cube
      7921 = square
      2179 = prime
      7912 = divisible by 4 (length of password)
      1792 = divisibly by 7, 2, 1 (but not 9)

      In fact there are 50 different sets of rearrangements that we could choose for this particular password.


      We can do some analysis to reduce the number of passwords considered by looking at digital roots (which are unchanged by rearrangement):

      the digital root of a square is 1, 4, 7, 9
      the digital root of a cube is 1, 8, 9
      the digital root of prime (except 3) is 1, 2, 4, 5, 7, 8

      So we need only consider passwords with a digital root of 1. (At this point considering cubes leads to 12 candidate passwords).

      Also, there must be at least 6 different rearrangements, so we need at least 3 digits, but it cannot have exactly 3 digits, as then the requirement for a rearrangement divisible by the number of digits would require a rearrangement that is divisible by 3, which would also have a digital root that was divisible by 3. (At this point considering cubes leads to 10 candidate passwords).

      Similarly, the number cannot have 9 digits, as it would have to have a rearrangement with a digital root of 9. Nor 10 digits, as again, the digital root would have to be 9.

      And if the password had length 6, it would have to have a rearrangement divisible by 6, which would require the rearrangement to be divisible by 3, and so its digital root would also be divisible by 3.

      So the password has 4, 5, 7, or 8 digits, and a digital root of 1. (At this point considering cubes leads to 6 candidate passwords).

      Additionally the password is not divisible only one of its digits, and it cannot be divisible by 0, 3, 6, 9, so no more than one of those four digits can occur in it.

      This is enough to narrow the password down to a single candidate, which gives the answer to the puzzle. (Although for completeness we should check that the appropriate rearrangements can be made).

      Like

    • Hugh Casement's avatar

      Hugh Casement 1:36 pm on 28 September 2021 Permalink | Reply

      All six rearrangements ending in 2 are integer multiples of 4 (and of course of 1 and 2).
      1279, 1297, 2179, 2719, 2791, 2917, 2971, 7129, 7219, 9127, 9721 are all prime.
      I have certainly known more satisfying Teasers.

      Like

    • Frits's avatar

      Frits 12:02 pm on 4 October 2021 Permalink | Reply

      The singles and lst2 logic is not really needed as Jim’s select function is sufficient.
      The program runs in 15ms.

       
      from enigma import is_cube, is_square, is_prime, find, update
      from itertools import permutations as perm
      
      # select distinct elements from each row
      def select(ss, xs):
        i = find(xs, None)
        if i == -1:
          yield xs
        else:
          # choose an elements from ss[i]
          for x in ss[i]:
            if x not in xs:
              yield from select(ss, update(xs, [(i, x)]))
      
      def check(n):
        # we need 6 arrangements
        
        # 0: I can rearrange the digits to form a perfect cube.
        # 1: A further rearrangement gives a perfect square.
        # 2: Another rearrangement gives a prime number.
        # 3: A rearrangement that is divisible by the number of digits.
        # 4: A rearrangement that is divisible by all but one of its digits.
        # 5: original number
      
        s = str(n)
        ndigits = len(s)
        lst = [[] for _ in range(5)]
        
        # check all permutations of original number
        for p in perm(s):
          m = int("".join(p)) 
          
          if m == n: continue
        
          if is_cube(m): 
            lst[0] += [m]
          if is_square(m): 
            lst[1] = lst[1] + [m]
          if is_prime(m): 
            lst[2] += [m]
          if m % ndigits == 0: 
            lst[3] += [m]
          if sum(x != '0' and m % int(x) == 0 for x in p) == ndigits - 1: 
            lst[4] += [m]
            
        # if any row is empty return False
        if any(not x for x in lst): return False
        
        # add 6th arrangement (original number)
        lst += [[n]]
        
        singles = [x[0] for x in lst if len(x) == 1]
        
        # build list of non-singles with numbers not occurring in singles
        lst2 = [[y for y in x if y not in singles] for x in lst if len(x) > 1]  
        lngth = len(lst2)
        
        # if each remaining row contains enough items return True
        if all(len(x) >= lngth for x in lst2):
          return True
        
        # select distinct members for each row
        for x in select(lst, [None] * 6):
          return True
        
        return False
      
      # add digit to each number in the list so that order remains decreasing
      def addLastDigit(li=range(1, 10)):
        lngth = len(str(li[0])) if li else 0
        return [10 * n + i for n in li for i in range(10 - lngth) if i < (n % 10)]
            
            
      # list of 3-digit numbers with different digits written in decreasing order
      nums = addLastDigit(addLastDigit())
      
      while True:
        for n in nums:
          if check(n):
            print("password:", n)
            break  # only need one example
        else:  # no break
          if len(str(nums[0])) > 9: break
          # add another digit
          nums = addLastDigit(nums)
          continue
          
        break    # break if nested break occurred 
      

      Like

  • Unknown's avatar

    Jim Randell 10:47 am on 26 September 2021 Permalink | Reply
    Tags:   

    Brain-Teaser: Silver collection 

    From The Sunday Times, 31st July 1960 [link]

    Seven South Sea Island brothers found on the beach a broken box and mixed silver coins scattered. These were old British coins (six-pence, shilling, florin, half-crown, and crown). 135 of them bore a man’s head and 54 a woman’s. The two younger brothers claimed the latter and their elders shared the former.

    Being ignorant of the value of the coins they agreed to take twenty-seven each. First they laid the coins in heaps of each size. The senior brother then took seven coins from each of two heaps and smaller numbers from the other three heaps to make up twenty-seven coins. Each other brother then took the same numbers but each from a different order of heaps.

    Unknown to themselves, the five elders had equal money value. The boys also had equal value, but greater than their elders.

    What were the values?

    [Note: Respective values: 6d, 12d, 24d, 30d, 60d]

    This is one of the occasional Holiday Brain-Teasers published in The Sunday Times prior to the start of numbered Teasers in 1961. A prize of 3 guineas was offered.

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

    [teaser-1960-07-31] [teaser-unnumbered]

     
    • Jim Randell's avatar

      Jim Randell 10:49 am on 26 September 2021 Permalink | Reply

      This Python program runs in 51ms.

      Run: [ @replit ]

      from enigma import (decompose, group, subsets, printf)
      
      # denominations of coins
      denominations = [6, 12, 24, 30, 60]
      
      # total for quantities of coins in qs (wrt denominations)
      total = lambda qs: sum(x * y for (x, y) in zip(qs, denominations))
      
      # decompose 27 - 14 = 13 into 3 numbers between 1 and 6
      for qs in decompose(13, 3, min_v=1, max_v=6, sep=0):
        qs += (7, 7)
      
        # collect permutations by total amount
        d = group(subsets(qs, size=len, select="mP"), by=total)
      
        # look for 5 permutations for the elders
        for (k1, vs1) in d.items():
          if not (len(vs1) > 4): continue
          # and 2 permutations, with a greater sum for the youngers
          for (k2, vs2) in d.items():
            if not (k2 > k1 and len(vs2) > 1): continue 
            printf("elders = {k1} [from {vs1}]")
            printf("youngers = {k2} [from {vs2}]")
            printf()
      

      Solution: The elders had selected coins with a value of 798d (= 66s 6d). The youngers had selected coins with a value of 834d (= 69s 6d).

      Like

    • Hugh Casement's avatar

      Hugh Casement 3:13 pm on 26 September 2021 Permalink | Reply

      I confess I cheated by working backward from Jim’s solution, but was able to deduce that there were 48 crowns, 44 half crowns, 38 florins, 32 shillings, and 27 sixpences.
      Total 189 coins with a value £23 11s 6d = 5658 d.

      If the five piles were arranged in order of decreasing value, the seniors (in some order) took
      7, 7, 4, 3, 6; 7, 7, 3, 6, 4; 7, 6, 4, 7, 3; 7, 4, 7, 6, 3; 6, 7, 7, 3, 4 coins from the respective piles.
      The youngsters took 7, 7, 6, 3, 4 and 7, 6, 7, 4, 3.

      There would be other ways of making up the values 798 and 834 with 27 coins, but not with as many as five of one and two of the other, using the same numbers in different orders.

      Like

  • Unknown's avatar

    Jim Randell 5:23 pm on 24 September 2021 Permalink | Reply
    Tags:   

    Teaser 3079: Hall of residence 

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

    Oak Hall at Woodville University has groups of five study bedrooms per flat and they share a kitchen/diner. In one flat live language students Andy, Bill, Chris, Dave and Ed. Bill, whose home town is Dunstable is reading French. The person in room 5 comes from Colchester and Dave comes from Brighton. The chap reading German has the room with a number one greater than the man from Gloucester. Chris occupies room 3, and Ed is reading Italian. The man in room 2 is reading Spanish, and the man reading English has a room whose number is two different from the student from Reigate.

    What is Andy’s subject and where is his home?

    [teaser3079]

     
    • Jim Randell's avatar

      Jim Randell 9:42 pm on 24 September 2021 Permalink | Reply

      We can use the [[ SubstitutedExpression ]] solver from the enigma.py library to assign room numbers to the names, subjects and towns according to the given conditions.

      This run-file executes in 68ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      # assign room numbers 1-5 to each of the groups:
      #
      #   Andy(A) Bill(B) Chris(C) Dave(D) Ed(E)
      #   English(F) French(G) German(H) Italian(I) Spanish(J)
      #   Brighton(K) Colchester(L) Dunstable(M) Gloucester(N) Reigate(P)
      
      SubstitutedExpression
      
      --digits="1-5"
      --distinct="ABCDE,FGHIJ,KLMNP"
      
      # "Bill, whose home town is Dunstable is reading French"
      "B = M"
      "B = G"
      
      # "The person in room 5 comes from Colchester"
      "L = 5"
      
      # "Dave comes from Brighton"
      "D = K"
      
      # "The chap reading German has the room with a number one greater than the man from Gloucester"
      "N + 1 = H"
      
      # "Chris occupies room 3"
      "C = 3"
      
      # "Ed is reading Italian"
      "E = I"
      
      # "The man in room 2 is reading Spanish"
      "J = 2"
      
      # "The man reading English has a room whose number is two different from the student from Reigate"
      "abs(F - P) = 2"
      
      # mention A (so it gets assigned a value)
      "A > 0"
      
      --template="(A B C D E) (F G H I J) (K L M N P)"
      --solution=""
      
      

      Solution: Andy is reading Spanish, and is from Gloucester.

      The complete allocations are:

      Room 1: Dave, English, Brighton
      Room 2: Andy, Spanish, Gloucester
      Room 3: Chris, German, Reigate
      Room 4: Bill, French, Dunstable
      Room 5: Ed, Italian, Colchester


      Manually we find that there are only two possible room values for the (Bill/French/Dunstable) group. Trying both possible values quickly leads to a solution in one case and a contradiction in the other.

      Like

      • Jim Randell's avatar

        Jim Randell 4:20 pm on 25 September 2021 Permalink | Reply

        I’ve seen people talk about solving these kind of puzzles like a jigsaw, and as it only takes a few minutes to write a program or solve this one manually, I thought it might be interesting to look at transforming the constraints into an “exact cover” problem.

        It’s a bit more work, but it does run quickly:

        from enigma import union, irange, chunk, update, join, printf
        
        ###############################################################################
        
        # in-place algorithmX implementation (X, soln are modified)
        def algorithmX(X, Y, soln):
          if not X:
            yield soln
          else:
            c = min(X.keys(), key=lambda k: len(X[k]))
            # copy X[c], as X is modified (could use sorted(X[c]) for stability)
            for r in list(X[c]):
              soln.append(r)
        
              # cols = select(X, Y, r)
              cols = list()
              for j in Y[r]:
                for i in X[j]:
                  for k in Y[i]:
                    if k != j:
                      X[k].remove(i)
                cols.append(X.pop(j))
        
              yield from algorithmX(X, Y, soln)
        
              # deselect(X, Y, r, cols)
              for j in reversed(Y[r]):
                X[j] = cols.pop()
                for i in X[j]:
                  for k in Y[i]:
                    if k != j:
                      X[k].add(i)
        
              soln.pop()
        
        # input: ss = sequence of collections of sets [ [a0, a1, ...], [b1, b2, ...], [c1, c2, ...] ... ]
        # output: sequence of sets (a, b, c, ...) one from each collection
        def exact_cover(sss):
          # map elements to indices
          xs = sorted(union(union(ss) for ss in sss))
          n = len(xs)
          m = dict((x, i) for (i, x) in enumerate(xs))
        
          # set up Y, one row for each position
          Y = list()
          for (j, ss) in enumerate(sss, start=n):
            for s in ss:
              y = list(m[x] for x in s)
              y.append(j)
              Y.append(y)
        
          # set up X as a dict of sets
          X = dict((k, set()) for k in irange(0, j))
          for (i, y) in enumerate(Y):
            for k in y:
              X[k].add(i)
        
          # find exact covers using algorithmX
          k = len(sss)
          for rs in algorithmX(X, Y, list()):
            # turn the selected rows of Y, back into sets
            r = [None] * k
            for i in rs:
              y = Y[i]
              r[y[-1] - n] = list(xs[i] for i in y[:-1])
            yield r
        
        ###############################################################################
        
        # we fit the pieces into a grid:
        #
        #  1:  0   1   2
        #  2:  3   4   5
        #  3:  6   7   8
        #  4:  9  10  11
        #  5: 12  13  14
        
        # each piece represents a constraint
        pieces = [
        
          # "Bill, whose home town is Dunstable is reading French" [Bill, French, Dunstable]
          [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]],
        
          # "The person in room 5 comes from Colchester" [Colchester]
          [[14]],
        
          # "Dave comes from Brighton" [Dave, Brighton]
          [[0, 2], [3, 5], [6, 8], [9, 11], [12, 14]],
        
          # "The chap reading German has the room with a number one greater than the man from Gloucester" [German, Gloucester]
          [[4, 2], [7, 5], [10, 8], [13, 11]],
        
          # "Chris occupies room 3" [Chris]
          [[6]],
        
          # "Ed is reading Italian" [Ed, Italian]
          [[0, 1], [3, 4], [6, 7], [9, 10], [12, 13]],
        
          # "The man in room 2 is reading Spanish" [Spanish]
          [[4]],
        
          # "The man reading English has a room whose number is two different from the student from Reigate" [English, Reigate]
          [[1, 8], [4, 11], [7, 14], [7, 2], [10, 5], [13, 8]],
        
          # make sure A can be placed [Andy]
          [[0], [3], [6], [9], [12]],
        ]
        
        # the labels for each piece
        labels = [
          ['Bill', 'French', 'Dunstable'],
          ['Colchester'],
          ['Dave', 'Brighton'],
          ['German', 'Gloucester'],
          ['Chris'],
          ['Ed', 'Italian'],
          ['Spanish'],
          ['English', 'Reigate'],
          ['Andy'],
        ]
        
        # find exact covers
        for ss in exact_cover(pieces):
          # output solution
          g = ['???'] * 15
          for (ks, vs) in zip(ss, labels):
            g = update(g, ks, vs)
          for (i, x) in enumerate(chunk(g, 3), start=1):
            printf("{i}: {x}", x=join(x, sep=", "))
          printf()
        

        Like

    • Frits's avatar

      Frits 11:30 am on 30 September 2021 Permalink | Reply

      A lot slower.

        
      from enigma import matrix
      from itertools import product
      
      # assign room numbers 1-5 to each of the groups:
      #
      #   Andy(A) Bill(B) Chris(C) Dave(D) Ed(E)
      #   English(F) French(G) German(H) Italian(I) Spanish(J)
      #   Brighton(K) Colchester(L) Dunstable(M) Gloucester(N) Reigate(P)
      
      eqs = [
        # [     NAMES     ]   [    STUDY      ]   [    TOWNS      ]  
        # A   B   C   D   E   F   G   H   I   J   K   L   M   N   P
        ( 0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, -1,  0,  0 ),  # B = M
        ( 0,  1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  0,  0,  0,  0 ),  # B = G
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0 ),  # L = 5
        ( 0,  0,  0,  1,  0,  0,  0,  0,  0,  0, -1,  0,  0,  0,  0 ),  # D = K
        ( 0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0, -1,  0 ),  # N + 1 = H
        ( 0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # C = 3
        ( 0,  0,  0,  0,  1,  0,  0,  0, -1,  0,  0,  0,  0,  0,  0 ),  # E = I
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 ),  # J = 2
        ( 1,  1,  1,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # A + B + C + D + E = 15
        ( 0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0 ),  # F + G + H + I + J = 15
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1 ),  # K + L + M + N + P = 15
        ( 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # A = ?
        ( 0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0 ),  # F = ?
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0 ),  # N = ?
        ( 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1 ),  # P = ?
       ]
      
      towns = ["Brighton", "Colchester", "Dunstable", "Gloucester", "Reigate"]
      langs = ["English", "French", "German", "Italian", "Spanish"]
      
      for (A, F, N, P) in product(range(1, 6), repeat = 4):
        # "The man reading English has a room whose number is two different
        # from the student from Reigate"
        if abs(F - P) != 2 or N == P: continue
      
        # solve equations
        s = matrix.linear(eqs, [0, 0, 5, 0, 1, 3, 0, 2, 15, 15, 15, A, F, N, P])
        
        # check that all numbers lie between 1 and 5
        if sorted(s[:5]) != [1, 2, 3, 4, 5]: continue
        if sorted(s[5:10]) != [1, 2, 3, 4, 5]: continue 
        if sorted(s[10:]) != [1, 2, 3, 4, 5]: continue
        
        lang = langs[s[5:10].index(A)]
        town = towns[s[10:].index(A)]
        print(f"Andy's home town is {town} and studies {lang}")
      

      Like

      • Frits's avatar

        Frits 12:13 pm on 30 September 2021 Permalink | Reply

        @Jim,

        Is there a more efficient way to enforce that 5 variables have distinct values 1, 2, 3, 4 and 5 (using matrix.linear)?

        Like

        • Jim Randell's avatar

          Jim Randell 1:20 pm on 30 September 2021 Permalink | Reply

          @Frits: The [[ matrix.linear() ]] solver finds solutions for a system of linear equations over a field (in this case the rationals), so if you want to further restrict the values of the solutions you have to check them after they are returned.

          In this case though there aren’t very many possible sets of permissible values, so we can just consider them directly:

          from enigma import (subsets, diff, singleton, printf)
          
          # start with towns (D, 5, B, H - 1, P)
          for (D, B, H1, P) in subsets((1, 2, 3, 4), size=4, select="P"):
            H = H1 + 1
            if H > 5: continue
          
            # then subjects (F = P +/- 2, B, H = H1 - 1, E, 2)
            ss = diff([1, 3, 4, 5], [B, H])
            if len(ss) != 2: continue
            for (F, E) in subsets(ss, size=2, select="P"):
              if abs(F - P) != 2: continue
          
              # calculate A
              A = singleton(diff([1, 2, 4, 5], [B, D, E]))
              if A is not None:
                printf("names = ({A}, {B}, 3, {D}, {E}); subjs = ({F}, {B}, {H}, {E}, 2); towns = ({D}, 5, {B}, {H1}, {P})")
          

          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