Recent Updates Page 10 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 7:02 am on 2 February 2025 Permalink | Reply
    Tags:   

    Teaser 3254: Pizza pans 

    From The Sunday Times, 2nd February 2025 [link] [link]

    In a large pan, James baked three identical circular pizzas whose radius was a whole number of cm (less than 75). He laid them on a platter so that one pizza overlapped the other two. The pizza centres formed a right-angled triangle, with sides that were whole numbers of cm. The two lengths of overlap and the gap between the two non-overlapping pizzas (all measured along the lines joining the pizza centres) were all whole numbers of cm and could have formed another right-angled triangle.

    He baked a fourth, smaller, circular pizza and it just fitted inside the triangle formed by the centres of the other three. Even if you knew the radii of the pizzas, you couldn’t work out the size of those right-angled triangles.

    What was the radius of the smallest pizza?

    [teaser3254]

     
    • Jim Randell's avatar

      Jim Randell 7:22 am on 2 February 2025 Permalink | Reply

      The following Python program runs in 69ms. (Internal runtime is 571µs).

      from enigma import (defaultdict, irange, pythagorean_triples, divc, rdiv, printf)
      
      # generate possible dimensions
      def generate():
        # consider pythagorean triples (x, y, z)
        for (x, y, z) in pythagorean_triples(225):
      
          # consider possible radii for the pizza (R)
          for R in irange(divc(y + 1, 2), 74):
            RR = 2 * R
            (X, Y, Z) = (RR - y, RR - x, RR + z)
            if not (X * X + Y * Y == Z * Z): continue
      
            # calculate inradius of X, Y, Z (r = area / semiperimeter)
            r = rdiv(X * Y, X + Y + Z)
      
            # return (<radii>, <large triangle>, <small triangle>)
            yield((R, r), (X, Y, Z), (x, y, z))
      
      # collect triangles by radii (R, r)
      d = defaultdict(list)
      for ((R, r), Ts, ts) in generate():
        printf("[R={R} {Ts} -> {ts} r={r}]")
        d[(R, r)].append((Ts, ts))
      
      # look for non-unique radii pairs
      for ((R, r), vs) in d.items():
        if len(vs) < 2: continue
        # output solution
        printf("({R}, {r}) -> {vs}")
      

      Solution: The smaller pizza has a radius of 30 cm.

      And the larger pizzas have radii of 60cm. (So each pizza is 120cm across! – that’s a big pizza).

      One of the possible arrangements looks like this:

      The overlaps are 10 cm and 24 cm, and the gap is 26 cm.

      And the other possible arrangement looks quite similar, but the overlaps are 15 cm and 20 cm, and the gap is 25 cm.

      Like

    • Frits's avatar

      Frits 6:36 am on 3 February 2025 Permalink | Reply

      from enigma import pythagorean_triples
      
      # let x, y, z be the lengths of the triangle of the overlaps and gap
      # let R be the radius of the 3 identical pizzas
      # RR = 2 * R
      # (X, Y, Z) = (RR - y, RR - x, RR + z)
      # right-angled: 4 * R**2 = 4 * (y * R + x * R + z * R)
      #               so R must be equal to y + x + z  
      
      seen = set()
      # consider pythagorean triples (X, Y, Z)
      for (X, Y, Z) in pythagorean_triples(4 * 75 - 1):
        # small and big radius
        # (x, y, z) = sorted([R2 - X, R2 - Y, Z - R2])
        # x + y + z = 2R - X - Y + Z = 2R - 2r = R --> R = 2r
        R = (X + Y - Z)  # r = R / 2
        
        if (R2 := 2 * R) > 2 * 75: continue
        
        # x, y, z must be positive numbers
        if min(R2 - X, R2 - Y, Z - R2) < 1: continue
        
        if R in seen:
          print(f"answer: {r} cm")
        seen.add(R)  
      

      Like

  • Unknown's avatar

    Jim Randell 10:39 am on 31 January 2025 Permalink | Reply
    Tags: ,   

    Teaser 2581: Resplendency 

    From The Sunday Times, 11th March 2012 [link] [link]

    Our local Hibernian band will lead the St Patrick’s Day parade next Saturday, resplendent in their new tunics and finery. The total cost of this was one thousand pounds and it was generously paid for by the parish council. Each tunic cost the same two-figure number of pounds (and that number happens to be double the number of girls in the band). In addition, each girl in the band has some extra finery, and the cost of this for each girl was the same as the cost of the tunic but with the two digits in reverse order.

    How many boys and how many girls are there in the band?

    [teaser2581]

     
    • Jim Randell's avatar

      Jim Randell 10:40 am on 31 January 2025 Permalink | Reply

      If:

      b = number of boys
      g = number of girls
      t = cost of tunic (pounds)
      f = cost of finery (pounds) = reverse of t

      we have:

      t = 2 × g
      t × b + (t + f) × g = 1000

      Hence b, g, f can all be expressed in terms of t, which is a 2-digit even number.

      This Python program runs in 66ms. (Internal runtime is 57µs).

      from enigma import (irange, cproduct, div, printf)
      
      # the cost of each tunic is XY, a 2-digit even number
      # the cost of each finery is YX, a 2 digit number
      for (X, Y) in cproduct([irange(1, 9), [2, 4, 6, 8]]):
        (t, f) = (10 * X + Y, 10 * Y + X)
      
        # calculate the number of girls and boys
        g = t // 2
        b = div(1000 - (t + f) * g, t)
        if b is None or b < 0: continue
      
        # output solution
        printf("b={b} g={g}; t={t} f={f}")
      

      Solution: There are 24 boys and 8 girls.

      The tunics cost £ 16, and the finery costs £ 61.

      £16 × 24 + £(16 + 61) × 8 = £1000

      Like

      • Ruud's avatar

        Ruud 3:58 pm on 2 February 2025 Permalink | Reply

        Very straightforward:

        import peek
        import istr
        
        for n_girls in istr(range(5, 50)):
            tunic_cost = 2 * n_girls
            finery_cost = tunic_cost.reversed()
            girls_total = n_girls * (tunic_cost + finery_cost)
            boys_total = 1000 - girls_total
            if finery_cost[0] != 0 and boys_total.is_divisible_by(tunic_cost):
                n_boys = boys_total / tunic_cost
                peek(girls_total, boys_total, n_girls, n_boys, tunic_cost, finery_cost)
        

        Like

    • GeoffR's avatar

      GeoffR 9:03 am on 1 February 2025 Permalink | Reply

      # the cost of each tunic is XY, a 2-digit even number
      # the cost of each finery is YX, a 2 digit number
      
      for X in range(1, 9):
        for Y in range(2, 10, 2):
          XY, YX = 10*X + Y, 10*Y + X
          for girls in range(1, 100):
            # cost of a tunic is double the number of girls
            if XY != 2 * girls:continue
            for boys in range(1, 100):
              tunics = (boys + girls) * XY
              finery = girls * YX
              # total cost = £1,000
              if tunics + finery == 1000:
                print(f"Band = {boys} boys and {girls} girls.")
      
      # Band = 24 boys and 8 girls.
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:41 am on 28 January 2025 Permalink | Reply
    Tags:   

    Teaser 2582: Anyone for tennis? 

    From The Sunday Times, 18th March 2012 [link] [link]

    The girls of St Trinian’s choose two games from the four on offer. In Felicity’s gang of four, no game was chosen by both Daphne and Erica; Chloe and Miss Brown chose lacrosse; Miss Smith chose netball; and only Miss Jones excluded hockey. In Harriet’s gang of four, Clara, Debbie and Ellen all chose netball but only one of the four chose hockey. To avoid having two girls with the same first name initial in any one game, one of the girls in the second group (but not Debbie) moved from one game to another. This meant that there was an odd number of these [eight] girls playing each game.

    Which of the eight girls played tennis?

    [teaser2582]

     
    • Jim Randell's avatar

      Jim Randell 10:42 am on 28 January 2025 Permalink | Reply

      We want each of the sports to end up with an odd number of participants after the swap.

      So before the swap there must be two sports that have an even number of participants (and the other two sports have an odd number of participants). And the swap must occur from one of the sports with an even number of participants to the other sport with an even number of participants. Leaving all sports with an odd number.

      Furthermore, the girl who swaps (who is in the second group, and shares and initial with a member of the first group, and is not Debbie – so must be Clara or Ellen), must have chosen one of the even sports (that was also chosen by her counterpart with the same initial), and not chosen the other even sport (that was also not chosen by her counterpart with the same initial, otherwise the swap would not remedy the situation). And this must be the only situation where a sport has been chosen by two girls with the same initial.

      This Python program uses the [[ SubstitutedExpression() ]] solver from the enigma.py library to determine possible choices for each of the groups separately, and then combines possibilities to consider possible choices for the 8 girls, and then checks the remaining conditions for the combined choices.

      It runs in 82ms. (Internal runtime is 11.5ms).

      from enigma import (
        SubstitutedExpression, cproduct, chain, filter2, multiset,
        singleton, diff, join, printf
      )
      
      # possible choices are:
      #
      #   k   L N H T
      #   0 = - - x x (HT)
      #   5 = x x - - (LN)
      #   1 = - x - x (NT)
      #   4 = x - x - (LH)
      #   2 = - x x - (NH)
      #   3 = x - - x (LT)
      #
      # such that for a choice k the unchosen options are (5 - k)
      
      # map choice numbers to sports (0 - 5)
      choice = ['HT', 'NT', 'NH', 'LT', 'LH', 'LN']
      
      # sets for the four sports
      macros = {
        'lacrosse': "{3, 4, 5}",
        'netball': "{1, 2, 5}",
        'hockey': "{0, 2, 4}",
        'tennis': "{0, 1, 3}",
      }
      
      # group 1:
      # for sport choices allocate: C, D, E, F to k-values (0-5)
      # for surnames allocate: B, S, J to values 0-3 (= C - F) [distinct]
      exprs1 = [
        # "no game was chosen by both D and E"
        "D + E == 5",
        # "C chose lacrosse"
        "C in @lacrosse",
        # "Miss Brown (who is not C) also chose lacrosse"
        "B != 0",
        "[C, D, E, F][B] in @lacrosse",
        # "Miss Smith chose netball"
        "[C, D, E, F][S] in @netball",
        # only Miss Jones excluded hockey
        "all((x in @hockey) == (i != J) for (i, x) in enumerate([C, D, E, F]))",
      ]
      
      names1 = "Chloe Daphne Erica Felicity".split()
      surnames1 = "Brown Smith Jones".split()
      
      def solve1():
        # find the first group
        p1 = SubstitutedExpression(
          exprs1, base=6, macro=macros,
          distinct="BSJ", d2i={ 4: "BSJ", 5: "BSJ" },
        )
        for s in p1.solve(verbose=0):
          # map names to choices
          d1 = dict((x, choice[s[x[0]]]) for x in names1)
          # map names to surnames
          sn = dict((names1[s[x[0]]], x) for x in surnames1)
          yield (d1, sn)
      
      
      # group 2:
      # for sport choices allocate: C, D, E, H to k-values (0-5)
      exprs2 = [
        # "C, D, E all chose netball"
        "{C, D, E}.issubset(@netball)",
        # "only one of C, D, E, H chose hockey"
        "sum(x in @hockey for x in [C, D, E, H]) == 1",
      ]
      
      names2 = "Clara Debbie Ellen Harriet".split()
      
      def solve2():
        # find the second group
        p2 = SubstitutedExpression(exprs2, base=6, macro=macros, distinct="")
        for s in p2.solve(verbose=0):
          # map names to choices
          d2 = dict((x, choice[s[x[0]]]) for x in names2)
          yield d2
      
      
      # output a group
      def output(g, sn=None):
        for k in sorted(g.keys()):
          s = (sn.get(k) if sn else None)
          name = (k + " " + s if s else k)
          printf("{name} -> {v}", v=join(g[k], sep=' + '))
        printf()
      
      # choose solutions for each group
      for ((g1, sn), g2) in cproduct([solve1(), solve2()]):
        # make the combined solution
        g = dict(chain(g1.items(), g2.items()))
        # map each sport to a list of girls
        s = dict((k, list()) for k in 'LNHT')
        for (k, vs) in g.items():
          for v in vs:
            s[v].append(k)
      
        # find sports with even and odd numbers of participants
        (even, odd) = filter2((lambda k: len(s[k]) % 2 == 0), s.keys())
        # there must be 2 even sports
        if len(even) != 2: continue
      
        # find sports that have more than one participant with a given initial
        ms = list()
        for (k, vs) in s.items():
          ds = list(x for (x, n) in multiset.from_seq(v[0] for v in vs).items() if n > 1)
          if ds: ms.append((k, ds))
        # there must be just one sport with just one duplicated initial
        if not (len(ms) == 1 and len(ms[0][1]) == 1): continue
        (x, i) = (ms[0][0], ms[0][1][0])
        # the duplicate sport must have an even number of participants
        # and we must swap to the other sport with an even number of participants
        y = singleton(diff(even, {x}))
        if y is None: continue
        # the duplicate initial must be C or E
        swap = other = None
        if i == 'C':
          # Clara needs to swap from x to y
          (swap, other) = ('Clara', 'Chloe')
        elif i == 'E':
          # Ellen needs to swap from x to y
          (swap, other) = ('Ellen', 'Erica')
        # check the swap fixes the issue
        if swap is None or not (x in g[swap] and y not in g[swap] and y not in g[other]): continue
      
        # final list for tennis
        ts = list(s['T'])
        if x == 'T': ts.remove(swap)
        if y == 'T': ts.append(swap)
      
        # output solution:
        # first output the choices for each group
        output(g1, sn)
        output(g2)
        # output the swap, and the final list for tennis
        printf("{swap} swaps {x} -> {y}")
        printf("-> tennis = {ts}", ts=join(ts, sep=", ", sort=1))
        printf()
      

      Solution: The girls playing tennis are: Clara, Debbie, Erica.

      The initial choices made are:

      Chloe = lacrosse + hockey
      Daphne Brown = lacrosse + hockey
      Erica Jones = netball + tennis
      Felicity Smith = netball + hockey

      Clara = netball + tennis
      Debbie = netball + tennis
      Ellen = lacrosse + netball
      Harriet = netball + hockey

      But both Erica and Ellen have chosen netball, so there would be 2 girls with the same initial in the netball group.

      To fix this Ellen swaps from netball to hockey, giving the following assignments:

      lacrosse = Chloe, Daphne, Ellen (CDE, 3)
      netball = Clara, Debbie, Erica, Felicity, Harriet (CDEFH, 5)
      hockey = Chloe, Daphne, Ellen, Felicity, Harriet (CDEFH, 5)
      tennis = Clara, Debbie, Erica (CDE, 3)

      Like

  • Unknown's avatar

    Jim Randell 1:45 am on 26 January 2025 Permalink | Reply
    Tags:   

    Teaser 3253: Romanesque Numerals 

    From The Sunday Times, 26th January 2025 [link] [link]

    Equations in an archaic number system have been discovered:

    BBC + BC = CBA
    ABA#AB + ABAACA = ССВ
    АСААС × ААС = ACAABABA

    One letter (shown as #) is unclear, and could be either A or B. Letters basically represented different positive whole numbers; they might appear in any order in a string, but could contribute positively or negatively. The letter that was rightmost counted as a plus, but otherwise its basic value was compared with that of its right neighbour: if larger, it would count as plus; if smaller, as minus; if the same, then the same sign as that neighbour. (This works for interpreting Roman numerals too, e.g., VII = 5 + 1 + 1 = 7, and XLIX = −10 + 50 − 1 + 10 = 49).

    What are the basic values of A, B & C, in that order?

    [teaser3253]

     
    • Jim Randell's avatar

      Jim Randell 2:17 am on 26 January 2025 Permalink | Reply

      This Python program runs in 73ms. (Internal runtime is 4.9ms).

      from enigma import (irange, inf, decompose, tuples, rev, printf)
      
      # evaluate string <s> for symbol assignments <d>
      def val(s, d):
        # process the values in reverse order
        vs = rev(d[x] for x in s)
        # start with the rightmost
        rs = vs[0:1]
        # and process the rest in pairs
        for (x, y) in tuples(vs, 2):
          if y > x:
            rs.append(y)
          elif y < x:
            rs.append(-y)
          else:
            rs.append(rs[-1])
        return sum(rs)
      
      # evaluate strings <ss> for symbol assignments <d>
      def vals(ss, d): return list(val(s, d) for s in ss)
      
      # consider increasing totals for A + B + C
      for (A, B, C) in decompose(irange(6, inf), 3, increasing=0, sep=1, min_v=1):
        d = dict(A=A, B=B, C=C)
      
        # evaluate the given expressions:
        # [1] BBC + BC = CBA
        (a1, a2, a3) = vals(["BBC", "BC", "CBA"], d)
        if not (a1 + a2 == a3): continue
      
        # [2] (ABAAAB | ABABAB) + ABAACA = CCB"
        (b1a, b1b, b2, b3) = vals(["ABAAAB", "ABABAB", "ABAACA", "CCB"], d)
        if not (b1a + b2 == b3 or b1b + b2 == b3): continue
      
        # [3] ACAAC * AAC = ACAABABA
        (c1, c2, c3) = vals(["ACAAC", "AAC", "ACAABABA"], d)
        if not (c1 * c2 == c3): continue
      
        # output solution
        printf("A={A} B={B} C={C}")
        break  # we only need the first solution
      

      Solution: A=4; B=3; C=10.

      We have:

      BBC → [−3, −3, +10] = 4
      BC → [−3, +10] = 7
      CBA → [+10, −3, +4] = 11
      BBC + BC = CBA → 4 + 7 = 11

      ABAAAB → [+4, −3, +4, +4, +4, +3] = 16
      ABAACA → [+4, −3, −4, −4, +10, +4] = 7
      CCB → [+10, +10, +3] = 23
      ABAAAB + ABAACA = CCB → 16 + 7 = 23

      ACAAC → [−4, +10, −4, −4, +10] = 8
      AAC → [−4, −4, +10] = 2
      ACAABABA → [−4, +10, +4, +4, −3, +4, −3, +4] = 16
      ACAAC × AAC = ACAABABA → 8 × 2 = 16

      Note that these are not necessarily the simplest way to write these numbers. For example in the first sum 4 is denoted by BBC, when it would be simpler to use A.

      More compact forms of the sums are:

      4 + 7 = 11 → A + AB = AAB
      16 + 7 = 23 → CBB + BC = CCB
      8 × 2 = 16 → AA × AAC = ACC

      Like

      • Jim Randell's avatar

        Jim Randell 1:29 pm on 3 February 2025 Permalink | Reply

        If we relax the condition on the values being positive integers, we can find further solutions using (non-zero) rational numbers.

        For example:

        C = −2/9; A = 4/9; B = 5/9

        gives:

        BBC + BC = CBA → 8/9 + 1/3 = 11/9
        ABAAAB + ABAACA = CCB → −2/3 + 5/3 = 1
        ACAAC × AAC = ACAABABA → 4/3 × 2/3 = 8/9

        Other rational values that work are:

        A = -23/435; B = 161/870; C = 299/435
        A = -38/1225; B = 228/1225; C = 874/1225

        Like

    • Ruud's avatar

      Ruud 12:21 pm on 26 January 2025 Permalink | Reply

      import sys
      import itertools
      
      expressions = """\
      BBC + BC == CBA
      ABAXAB + ABAACA == CCB
      ACAAC * AAC == ACAABABA
      """.splitlines()
      
      
      def calc(s):
          l = [-1] + [globals()[ch] for ch in reversed(s)]
          result = 0
          for value, value_right in zip(l[1:], l):
              if value_right > value:
                  sign = -1
              if value_right < value:
                  sign = 1
              result += sign * value
          return str(result)
      
      
      def eval_expression(expression):
          return eval("".join(part if set(part) - set("ABCX") else calc(part) for part in expression.split()))
      
      
      for n in itertools.count(4):
          for A, B, C in itertools.permutations(range(20), 3):
              for X in (A, B, C):
                  if all(eval_expression(expression) for expression in expressions):
                      print(f"{A=} {B=} {C=}")
                      sys.exit()
      
      

      Like

      • Ruud's avatar

        Ruud 6:42 pm on 27 January 2025 Permalink | Reply

        I see now that # (X in my code) can only be A or B.
        So the second for loop should read
        for X in (A, B)
        It doesn’t change the solution, by the way.

        Like

    • Frits's avatar

      Frits 8:37 am on 3 February 2025 Permalink | Reply

      From the first expression we can deduce that B may not be greater than A and C.
      From the last expression we can deduce that A and C are both even.

      Like

  • Unknown's avatar

    Jim Randell 7:58 am on 24 January 2025 Permalink | Reply
    Tags:   

    Teaser 2583: Playing cards 

    From The Sunday Times, 25th March 2012 [link] [link]

    Oliver wrote down some numbers and then used a letter-for-digit code to make them into words. In this way his numbers became JACK, QUEEN, KING and ACE. In fact JACK and KING were perfect squares, QUEEN was odd, and ACE was a prime. Furthermore, appropriately enough, the sum of the four was divisible by 52.

    What number was represented by QUEEN?

    [teaser2583]

     
    • Jim Randell's avatar

      Jim Randell 7:59 am on 24 January 2025 Permalink | Reply

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

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # JACK and KING are perfect squares
      "is_square(JACK)"
      "is_square(KING)"
      
      # QUEEN is odd
      "N % 2 = 1"
      
      # ACE is prime
      "is_prime(ACE)"
      
      # total sum is divisible by 52
      "(JACK + QUEEN + KING + ACE) % 52 = 0"
      
      --answer="QUEEN"
      

      Solution: QUEEN = 89115.

      We have:

      JACK = 2704 = 52^2
      KING = 4356 = 66^2
      QUEEN = 89115 (odd)
      ACE = 701 (prime)
      sum = 97056 = 52 × 1863

      Like

    • GeoffR's avatar

      GeoffR 3:52 pm on 24 January 2025 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:J; var 1..9:A; var 0..9:C; var 1..9:K;
      var 1..9:Q; var 0..9:U; var 0..9:E; var 0..9:N;
      var 0..9:I; var 0..9:G; 
      
      var 1000..9999:JACK == 1000*J + 100*A + 10*C + K;
      var 10000..99999:QUEEN == 10000*Q + 1000*U + 110*E + N;
      var 1000..9999:KING == 1000*K + 100*I + 10*N + G;
      var 100..999:ACE == 100*A + 10*C + E;
      
      constraint all_different([J,A,C,K,Q,U,E,N,I,G]);
      
      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_sq(var int: c) =
         let {
            var 0..ceil(pow(int2float(ub(c)),(1/2))): z
         } in z*z = c;
         
      constraint is_sq(JACK) /\ is_sq(KING);
      constraint QUEEN mod 2 == 1;
      constraint is_prime(ACE);
      constraint (JACK + QUEEN + KING + ACE) mod 52 == 0;
       
      solve satisfy;
      
      output["[JACK, QUEEN, KING, ACE ] =" ++ show ( [JACK, QUEEN, KING, ACE] ) ];
      
      % [JACK, QUEEN, KING, ACE ] =[2704, 89115, 4356, 701]
      % ----------
      % ==========
       
      
      

      Like

    • Ruud's avatar

      Ruud 5:57 pm on 24 January 2025 Permalink | Reply

      import istr
      
      for j, a, c, k, q, u, n, i, g, e in istr.permutations(istr.digits()):
          if (
              (j | a | c | k).is_square()
              and (k | i | n | g).is_square()
              and (q | u | e | e | n).is_odd()
              and (a | c | e).is_prime()
              and ((j | a | c | k) + (k | i | n | g) + (q | u | e | e | n) + (a | c | e)).is_divisible_by(52)
          ):
              print(f"queen={q | u | e | e | n}")
      

      Like

    • GeoffR's avatar

      GeoffR 2:17 pm on 25 January 2025 Permalink | Reply

      from itertools import permutations
      from enigma import is_prime
      sq4 = [x * x for x in range(32,100)]
      digits = set(range(10))
      
      # First stage permutation
      for p1 in permutations(digits, 4):
        k, i, n, g = p1
        if k == 0:continue
        king = 1000*k + 100*i + 10*n + g
        if king not in sq4:continue
        
        # Second stage permutation
        q1 = digits.difference(p1)
        for p2 in permutations(q1, 4):
          j, a, c, e = p2
          if 0 in (j, a): continue
          jack = 1000*j + 100*a + 10*c + k
          if jack not in sq4:continue
          ace = 100*a + 10*c + e
          if not is_prime(ace):continue
          
          # Third stage permutation
          q2 = q1.difference(p2) 
          for p3 in permutations(q2):
            q, u = p3
            if q == 0:continue
            queen = 10000*q + 1000*u + 110*e + n
            if queen % 2 == 1:
              if (jack + king + ace + queen) % 52 == 0:
                print(f"QUEEN = {queen}")
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 7:49 am on 21 January 2025 Permalink | Reply
    Tags:   

    Teaser 2511: [Medallions] 

    From The Sunday Times, 7th November 2010 [link] [link]

    I have a collection of silver medallions, each of which weighs an exact whole number of grams. If I take pairs of them in every possible combination, I can weigh out an almost complete sequence of 16 consecutive weights, with only two weights, 100 grams and 101 grams, missing from the sequence. Even so, I can still manage an unbroken sequence of 13 weights, with only one of them, a prime number of grams, duplicated.

    What, in ascending order, are the weights in grams of my medallions?

    This puzzle was originally published with no title.

    [teaser2511]

     
    • Jim Randell's avatar

      Jim Randell 7:50 am on 21 January 2025 Permalink | Reply

      I reused the code written to solve Enigma 1600, which is a similar puzzle.

      With 6 weights we can combine them in pairs to make C(6, 2) = 15 pairs, so to make exactly 14 values only one of them is repeated.

      So we can either make:

      [13 values], [not 100, 101], [102] = [87..102] \ {100, 101}
      [99], [not 100, 101], [13 values] = [99..114] \ {100, 101}

      This Python 3 program runs in 68ms. (Internal runtime is 1.6ms).

      from enigma import (irange, diff, C, div, multiset, subsets, is_prime, printf)
      
      # extend <ss> with <k> more values to make numbers in <ns>
      def pairs(ns, ss, k):
        # are we done?
        if k == 0 or not ns:
          if k == 0 and not ns:
            yield ss
        elif C(k, 2) + k * len(ss) >= len(ns):
          # add in the next smallest value
          for x in irange(ss[-1] + 1, max(ns) - ss[-1]):
            # and solve for the remaining numbers
            ns_ = diff(ns, set(x + y for y in ss))
            yield from pairs(ns_, ss + [x], k - 1)
      
      # find <k> values that can be used in pairs to make <ns>
      def solve(ns, k):
        # suppose the values start [0, a, ...]
        # then the pairs start [a, ...]
        m = ns[-1] - ns[0] + 5 - 2 * k
        for a in irange(1, m):
          # reduce the numbers so that they start at a
          d = div(ns[0] - a, 2)
          if d is None: continue
          vs = list(n - 2 * d for n in ns)
          # solve the reduced list
          for ss in pairs(vs[1:], [0, a], k - 2):
            # return (<values>, <pair sums>)
            ss = tuple(v + d for v in ss)
            ps = sorted(x + y for (x, y) in subsets(ss, size=2))
            yield (ss, ps)
      
      # consider a sequence of 16 consecutive values that includes 100, 101
      for i in [87, 99]:
        ns = diff(irange(i, i + 15), {100, 101})
        for (ss, ps) in solve(ns, 6):
          # now find what values we can make, and check duplicates
          (ds, xs) = multiset.from_seq(ps).differences(ns)
          if xs or ds.size() != 1: continue
          # the duplicate value is prime
          d = ds.peek()
          if not is_prime(d): continue
          # output solution
          printf("{ss} -> {ns}; duplicate = {d}")
      

      Solution: The weights of the medallions are: 43, 44, 45, 46, 49, 53 grams.

      Taken in pairs these make the values:

      87, 88, 89 (twice), 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 102

      Like

  • Unknown's avatar

    Jim Randell 6:58 am on 19 January 2025 Permalink | Reply
    Tags:   

    Teaser 3252: Family tree 

    From The Sunday Times, 19th January 2025 [link] [link]

    George and Martha have five daughters; they are, in order of arrival, Andrea [eldest], Bertha, Caroline, Dorothy and Elizabeth [youngest]. They now have ages that are all [different] prime numbers in the range 28 to 60. The average [of the numbers] is also a prime number, different from the other five.

    Each daughter has a son, Adam, Brian, Colin, David and Edward respectively. They are all mathematics students, and are studying how to calculate square roots without using a calculator. One of them was given a perfect square with three or four digits (none repeated) and told to work out the square root. This he did accurately, getting his mother’s age, but he noted that the sum of the digits of that perfect square was also prime.

    Which boy was it, and what is his mother’s age?

    [teaser3252]

     
    • Jim Randell's avatar

      Jim Randell 7:10 am on 19 January 2025 Permalink | Reply

      This Python program runs in 65ms. (Internal runtime is 141us).

      from enigma import (primes, subsets, iavg, nsplit, printf)
      
      # choose the five ages (youngest (E) to eldest (A))
      for ps in subsets(primes.between(28, 60), size=5):
        # the mean is a different prime
        m = iavg(ps)
        if m is None or m in ps or m not in primes: continue
      
        # one of the primes has a square with a prime digit sum
        for (t, p) in zip("EDCBA", ps):
          s = p * p
          ds = nsplit(s)
          if not (len(set(ds)) == len(ds) and sum(ds) in primes): continue
          # output solution
          printf("ages = {ps}, mean = {m} -> {t} = {p}, square = {s}")
      

      Solution: David did the calculation. His mother is 37.

      The ages are:

      Andrea = 59
      Bertha = 47
      Caroline = 41
      Dorothy = 37
      Elizabeth = 31
      → mean = 43

      And the square of Dorothy’s age is 37^2 = 1369, which has a digit sum of 19.

      Like

    • Frits's avatar

      Frits 9:50 am on 19 January 2025 Permalink | Reply

      from enigma import SubstitutedExpression
      
      # invalid digit / symbol assignments
      d2i = dict()
      for dgt in range(0, 10):
        vs = set()
        if dgt < 2 or dgt > 5: vs.update('ACEGI')
        if dgt % 2 == 0 or dgt == 5: vs.update('BDFHJ')
        d2i[dgt] = vs
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          "AB > 28",
          "is_prime(AB)",
          "CD > AB",
          "is_prime(CD)",
          "EF > CD",
          "is_prime(EF)",
          "GH > EF",
          "is_prime(GH)",
          "IJ > GH",
          "is_prime(IJ)",
          # the mean is a different prime
          "is_prime(mean := (AB + CD + EF + GH + IJ) / 5)",
          "mean not in {AB, CD, EF, GH, IJ}",
          
          # the sum of the digits of that perfect square was also prime
          "len(res := [(i, m) for i, m in enumerate([AB, CD, EF, GH, IJ]) \
               if len(set((s := str(m * m)))) == len(s) and \
                  is_prime(sum([int(x) for x in s]))] ) > 0",
        ],
        answer="res",
        d2i=d2i,
        distinct="",
        reorder=0,
        verbose=0,    # use 256 to see the generated code
      )
      
      boys = "Edward David Colin Brian Adam".split()
      # print answers
      for ans in p.answers():
        for a in ans:
          print(f"the boy was {boys[a[0]]} and his mother’s age was {a[1]}")
      

      Like

      • Frits's avatar

        Frits 10:19 am on 19 January 2025 Permalink | Reply

        @Jim, I just noticed that “is_prime(2.5)” yields “True”.
        Are you going to change the subroutine?

        Like

        • Jim Randell's avatar

          Jim Randell 10:32 am on 19 January 2025 Permalink | Reply

          @Frits: If you don’t know if you are passing a non-negative integer you can turn on argument validation with the [[ validate=1 ]] parameter to check it for you (a ValueError exception will be raised on invalid input).

          Like

    • ruudvanderham's avatar

      ruudvanderham 2:34 pm on 20 January 2025 Permalink | Reply

      import istr
      
      istr.repr_mode("int")
      primes = [n for n in range(28, 61) if istr.is_prime(n)]
      
      for ages in istr.combinations(primes, 5):
          if (average := int(sum(ages)) / 5) in primes and not (average in ages):
              for age, name in zip(ages, "Adam Brian Colin David Edward".split()):
                  if sum(i for i in age**2).is_prime() and (age**2).all_distinct():
                      print(f"Name boy={name} Mother's age={age} [Ages={ages} Average={int(average)}]")

      Like

  • Unknown's avatar

    Jim Randell 7:42 am on 17 January 2025 Permalink | Reply
    Tags: ,   

    Teaser 2588: On-line 

    From The Sunday Times, 29th April 2012 [link] [link]

    Linesmen Alf and Bert were checking the railway line from Timesborough to Teaserton. Alf started at Timesborough, Bert at Teaserton, and they walked towards each other at their own steady speeds. A train from Timesborough, travelling at constant speed, reached Alf at noon and passed him ten seconds later. At 12:06 pm the train reached Bert and passed him eight seconds later.

    At what time did the two men meet?

    [teaser2588]

     
    • Jim Randell's avatar

      Jim Randell 7:42 am on 17 January 2025 Permalink | Reply

      This puzzle is solved with a straightforward bit of analysis.

      Suppose A is walking with a speed of a (units/s) and B with speed of b, and the train travels with a speed of v.

      The train takes 10s to pass A, so in that 10s A walks a distance 10a, and the front of the train travels a distance 10v (in the same direction), so the length of the train t is:

      t = 10(v − a)

      The train takes 8s to pass B, and in that 8s B walks a distance 8a, and the front of the train travels a distance 8v (in the opposite direction), and so the length of the train can also be expressed as:

      t = 8(v + b)

      So:

      10(v − a) = 8(v + b)
      v = 5a + 4b

      The front of the train is level with A at noon, and at 12:06pm (i.e. 360 seconds later) it is level with B.

      So the train has travelled a distance of 360v in that time, and A has travelled a distance of 360a, so at 12:06 pm the distance between A and B is:

      d = 360(v − a)

      And the time taken for A and B to reduce this distance to zero is:

      360(v − a) / (a + b)
      = 360 (4a + 4b) / (a + b)
      = 360 × 4

      So it takes another 4× 6 minutes = 24 minutes after 12:06 pm for A and B to meet.

      Solution: Alf and Bert met at 12:30 pm.

      Like

  • Unknown's avatar

    Jim Randell 10:07 am on 14 January 2025 Permalink | Reply
    Tags: by: Graham Smith   

    Teaser 2590: Moving downwards 

    From The Sunday Times, 13th May 2012 [link] [link]

    A three-by-three array of the nine digits 1 – 9 is said to be “downward moving” if each digit is less than the digit to the east of it and to the south of it — see, for example, the keypad on a mobile phone.

    Megan has produced a downward moving array that is different from that on a mobile phone. If she were to tell you the sum of the digits in its left-hand column and whether or not the central digit was even, then you should be able to work out Megan’s array.

    What is Megan’s array?

    [teaser2590]

     
    • Jim Randell's avatar

      Jim Randell 10:09 am on 14 January 2025 Permalink | Reply

      This Python program uses the [[ SubstitutedExpression ]] solver from the enigma.py library to generate all possible “downward moving” arrays. And then uses the [[ filter_unique() ]] function to find solutions that are unique by the required criteria.

      It runs in 74ms. (Internal runtime is 9ms).

      from enigma import (SubstitutedExpression, irange, filter_unique, unpack, chunk, printf)
      
      # for a "downward moving" grid the rows and columns are in ascending order
      # consider the grid:
      #   A B C
      #   D E F
      #   G H I
      exprs = [
        "A < B < C", "D < E < F", "G < H < I",
        "A < D < G", "B < E < H", "C < F < I",
      ]
      
      # make a solver to find downward moving grids
      p = SubstitutedExpression(
        exprs,
        digits=irange(1, 9),
        answer="(A, B, C, D, E, F, G, H, I)",
      )
      
      # look for a non-standard layout, unique by <f>
      non_standard = lambda x: x != (1, 2, 3, 4, 5, 6, 7, 8, 9)
      # f = (sum of digits in LH column, central digit parity)
      f = unpack(lambda A, B, C, D, E, F, G, H, I: (A + D + G, E % 2))
      rs = filter_unique(p.answers(verbose=0), f, st=non_standard).unique
      
      # output solution(s)
      for r in rs:
        printf("{r}", r=list(chunk(r, 3)))
      

      Solution: Megan’s downward moving array looks like this:

      Of the 42 possible downward moving arrays, this is the only one that is unique by LH column sum (11) and parity of the central digit (even).

      (Of course moving from left-to-right and top-to-bottom the numbers are increasing, so maybe this should be considered an “upward moving” array).

      Liked by 1 person

    • Ruud's avatar

      Ruud 8:27 am on 16 January 2025 Permalink | Reply

      import itertools
      import collections
      
      
      collect = collections.defaultdict(list)
      for a in itertools.permutations(range(1, 10)):
          if (
              all(a[row * 3] < a[row * 3 + 1] < a[row * 3 + 2] for row in range(3))
              and all(a[col] < a[col + 3] < a[col + 6] for col in range(3))
              and a != tuple(range(1, 10))
          ):
              collect[a[0] + a[3] + a[6], a[4] % 2].append(a)
      
      for ident, solutions in collect.items():
          if len(solutions) == 1:
              for row in range(3):
                  for col in range(3):
                      print(solutions[0][row * 3 + col], end="")
                  print()
      

      Like

    • GeoffR's avatar

      GeoffR 9:28 am on 18 January 2025 Permalink | Reply

      #   A B C
      #   D E F
      #   G H I
      
      from itertools import permutations
      from collections import defaultdict
      
      MEG = defaultdict(list)
      digits = set(range(1, 10))
      
      for p1 in permutations(digits, 6):
        A, B, C, D, E, F = p1
        if A < B < C and D < E < F and \
          A < D and B < E and C < F:
          q1 = digits.difference(p1)
          for p2 in permutations(q1):
            G, H, I = p2
            if G < H < I:
              if A < D < G and B < E < H and C < F < I:
                MEG[(A + D + G, E % 2)].append([A, B, C, D, E, F, G, H, I])
                
      # check E is even for Meghans array as 5th element(E)
      # ..of standard telephone array is odd i.e. 5
      for k, v in MEG.items():
        if len(v) == 1 and v[0][4] % 2 == 0:
          print("Meghans array: ")
          print(v[0][0], v[0][1], v[0][2])
          print(v[0][3], v[0][4], v[0][5])
          print(v[0][6], v[0][7], v[0][8])
      
      '''
      Meghans array:  
      1 2 5
      3 4 6
      7 8 9
      '''
      
      

      Like

  • Unknown's avatar

    Jim Randell 7:00 am on 12 January 2025 Permalink | Reply
    Tags:   

    Teaser 3251: Number test 

    From The Sunday Times, 12th January 2025 [link] [link]

    I asked my daughter to find as many three-digit numbers as she could, each of which had the property that the number equalled the sum of the cubes of its individual digits. If she only found one such number, I asked her to give me this number — otherwise, if she had found more than one, to give me the sum of all such numbers found.

    She gave me a prime number, from which I could see that not all answers had been found.

    Which number or numbers did she not find?

    [teaser3251]

     
    • Jim Randell's avatar

      Jim Randell 7:07 am on 12 January 2025 Permalink | Reply

      It is straightforward to test all 3-digit numbers for the required property, and then look for (proper) subsets of the numbers found, to find a subset with a prime sum.

      This Python program runs in 65ms. (Internal runtime is 1.1ms).

      from enigma import (irange, nsplit, subsets, is_prime, diff, printf)
      
      # find 3-digit numbers that are the sum of the cube of their digits
      cb = list(d**3 for d in irange(0, 9))
      ns = list(n for n in irange(100, 999) if n == sum(cb[d] for d in nsplit(n)))
      printf("numbers = {ns}")
      
      # find (proper) subsets that sum to a prime
      for ss in subsets(ns, min_size=1, max_size=len(ns) - 1):
        t = sum(ss)
        if not is_prime(t): continue
        # output solution
        missing = diff(ns, ss)
        printf("sum{ss} = {t}; missing = {missing}")
      

      Solution: The numbers not found are: 371, 407.

      There are only four such numbers (if leading zeros are disallowed):

      153, 370, 371, 407

      And the only strict subset of these with a prime sum is:

      153 + 370 = 523

      If all four numbers had been found, the sum would also have been prime:

      153 + 370 + 371 + 407 = 1301

      Like

    • Frits's avatar

      Frits 2:56 pm on 12 January 2025 Permalink | Reply

      from itertools import combinations
      
      def is_prime(n):            
        if n < 4:
          return n > 1
        if n % 2 == 0 or n % 3 == 0:
          return False
      
        # all primes > 3 are of the form 6n +/- 1
        # start with f=5 (which is prime) and test f, f+2 for being prime
        f, r = 5, int(n**0.5)
        while f <= r:
          if n % f == 0: return False
          if n % (f + 2) == 0: return False
          f += 6
      
        return True
      
      nums = []
      for a in range(1, 10):
        a3 = a * a * a
        for b in range(10):
          a3b3 = a3 + b * b * b
          for c in range(10):
            t = a3b3 + c * c * c
            if t // 100 != a: continue
            if t % 10 != c: continue
            if (t % 100) // 10 != b: continue
            nums += [t]   
      sum_nums = sum(nums)
      
      print("answer(s):")
      for k in range(1, len(nums)):
        # pick <k> numbers from <ns> she has missed
        for s in combinations(nums, k):
          # she gave me a prime number
          if is_prime(sum_nums - sum(s)):
            print(s)
      

      Like

    • ruudvanderham's avatar

      ruudvanderham 12:09 pm on 13 January 2025 Permalink | Reply

      Rather straightforward:

      import istr
      
      istr.repr_mode("str")
      
      solutions = {i for i in istr(range(100, 1000)) if sum(digit**3 for digit in i) == i}
      print(*list(f"missing: {solutions - set(x)}" for r in range(1, len(solutions)) for x in istr.combinations(solutions, r=r) if sum(x).is_prime()))

      Like

  • Unknown's avatar

    Jim Randell 8:29 am on 10 January 2025 Permalink | Reply
    Tags:   

    Teaser 2594: Just enough 

    From The Sunday Times, 10th June 2012 [link] [link]

    I have two identical bags of balls, each containing two reds, two yellows and two blues. Blindfolded throughout, I remove just enough balls from the first to be sure that the removed balls include at least two colours. I place these balls in the second bag. Then I remove some balls from the second bag and put them in the first: I move just enough to be sure that the first bag will now contain at least one ball of each colour.

    What (as a percentage) is the probability that all the balls left in the second bag are the same colour?

    [teaser2594]

     
    • Jim Randell's avatar

      Jim Randell 8:29 am on 10 January 2025 Permalink | Reply

      This Python program determines the minimal number of balls moved in each of the two steps, to meet the required conditions.

      It then generates all possible (equally likely) outcomes using those numbers to determine the proportion that leave all balls the same colour in the second bag.

      from enigma import (Accumulator, irange, multiset, subsets, fdiv, printf)
      
      # initial state of the bags
      bag0 = multiset(R=2, Y=2, B=2)
      
      # move balls <ss> from <X> to <Y>
      def move(X, Y, ss): return (X.difference(ss), Y.combine(ss))
      
      # find minimum subset size to transfer from <X> to <Y>
      # that ensures condition <fn> (on the modified <X>, <Y>)
      def solve(X, Y, fn):
        for k in irange(1, X.size()):
          fail = 0
          for ss in X.subsets(size=k):
            # move ss from X to Y
            (X_, Y_) = move(X, Y, ss)
            if not fn(X_, Y_):
              fail = 1
              break
          if fail == 0: return k
      
      # initially move <k1> balls, such that at least 2 colours are moved
      k1 = solve(bag0, bag0, (lambda X, Y: sum(v > 2 for v in Y.values()) >= 2))
      printf("[k1 = {k1}]")
      
      # find the number of balls required to move back
      r = Accumulator(fn=max)
      # consider all bags with <k1> balls moved
      for ss in bag0.subsets(size=k1):
        (bag1, bag2) = move(bag0, bag0, ss)
        # move balls from bag2 to bag1, such that bag1 has at least one of each colour
        r.accumulate(solve(bag2, bag1, (lambda X, Y: Y.distinct_size() >= 3)))
      k2 = r.value
      printf("[k2 = {k2}]")
      
      # count all possible outcomes (t), and those where the second bag only
      # contains balls of one colour (n)
      t = n = 0
      bag1_0 = bag2_0 = bag0
      
      # step (1) move k1 balls from bag1 to bag2
      for ss1 in subsets(bag1_0, size=k1):
        (bag1_1, bag2_1) = move(bag1_0, bag2_0, ss1)
      
        # step (2) move k2 balls from bag2 to bag1
        for ss2 in subsets(bag2_1, size=k2):
          (bag2_2, bag1_2) = move(bag2_1, bag1_1, ss2)
      
          t += 1
          # does bag2 only contain balls of one colour?
          if bag2_2.distinct_size() == 1: n += 1
      
      # output solution
      printf("P = {n}/{t} = {f:.2%}", f=fdiv(n, t))
      

      Solution: The probability that the balls remaining in the second bag are all the same colour is 5%.

      It is easy to see that the first step requires 3 balls to be moved: if we moved only 2 balls we might choose 2 of the same colour, but with 3 balls they cannot all be of the same colour (as there are only 2 of each colour).

      The second step requires 6 balls to be moved to ensure that there is at least one ball of each colour in the first bag. For example: if the first step moves RRY to bag 2, then bag 1 contains YBB and bag 2 contains RRRRYYYBB, and we need to move at least 6 balls (as if we only choose 5 we might choose YYYBB, which would leave bag 1 with only blue and yellow balls).


      Manually:

      For the first step, moving 3 balls from bag 1 to bag 2, we might move 3 different coloured balls, with a probability of 5/5 × 4/5 × 2/4 = 2/5. And the remaining 3/5 of the time we would remove 2 balls of one colour and 1 ball of a different colour.

      So we have two possible patterns of balls in the bags:

      (2/5) XYZ + XXXYYYZZZ
      (3/5) YZZ + XXXXYYYZZ

      We can then look at moving 6 balls back to the first bag. Which is the same as choosing 3 balls to leave behind, and we want to look at the probability that these balls are the same colour.

      In the first case the probability this happening is: 9/9 × 2/8 × 1/7 = 1/28.

      In the second case we can choose to leave 3 X’s or 3 Y’s to leave behind. The probabilities are:

      P(XXX) = 4/9 × 3/8 × 2/7 = 1/21
      P(YYY) = 3/9 × 2/8 × 1/7 = 1/84

      Giving a final probability of:

      P = (2/5) × (1/28) + (3/5) × (1/21 + 1/84) = 1/20

      Like

  • Unknown's avatar

    Jim Randell 8:07 am on 8 January 2025 Permalink | Reply
    Tags:   

    Teaser 2595: Stylish fences 

    From The Sunday Times, 17th June 2012 [link] [link]

    Farmer Giles has a hexagonal field bounded by six straight fences. The six corners lie on a circle of diameter somewhere around 250 metres. At three alternate corners there are stiles. The angles of the hexagon at the corners without stiles are all the same. All six fences are different whole numbers of metres long. I have just walked in a straight line from one of the stiles to another and the distance walked was a whole number of metres.

    How many?

    [teaser2595]

     
    • Jim Randell's avatar

      Jim Randell 8:07 am on 8 January 2025 Permalink | Reply

      Suppose the field is:

      If the stiles are at A, C, E, then the angles at B, D, F are all equal, which means they are all 120°, and so the lines AC, AE, CE (= paths between stiles) are all equal length and so ACE is an equilateral triangle. (See: [ @wikipedia ]).

      If the diameter of the circle is d, then the length of the paths, p, is given by:

      p = d √(3/4)

      And d is around 250, let’s say in the range [245, 255], so the path is in the range [212.176, 220.836], so we can consider path lengths of 212 .. 221.

      For a given path length p (= AC) we can look for integer fence lengths (x, y) (= (AB, BC)) that complete the triangle ABC. By the cosine rule in ABC:

      p^2 = x^2 + y^2 + xy

      y^2 + xy + (x^2 − p^2) = 0

      which is a quadratic equation in y.

      So we can consider values for x and use the equation to find possible corresponding y values.

      And we need to find (at least) 3 different (x, y) pairs to complete the hexagon, so that all 6 fences are different lengths.

      The following Python program runs in 76ms. (Internal runtime is 7.5ms).

      from enigma import (irange, quadratic, sq, sqrt, printf)
      
      r43 = sqrt(4, 3)
      
      # consider possible path lengths
      for p in irange(212, 221):
        # collect possible (x, y) pairs
        ss = list()
        for x in irange(1, p - 1):
          for y in quadratic(1, x, sq(x) - sq(p), domain='Z'):
            if x < y < p:
              ss.append((x, y))
      
        # we need (at least) 3 different pairs
        if len(ss) < 3: continue
        # output solution
        printf("p={p} d={d:.3f} -> {ss}", d=p * r43)
      

      Solution: The path is 217m long.

      There are actually 4 possible (x, y) pairs, and any 3 of them can be used to construct the hexagon:

      p=217 d=250.570 → [(17, 208), (77, 168), (87, 160), (93, 155)]

      If the puzzle had specified that the diameter of the circle was “around 400 m”, then we would have been able to give the lengths of the 6 fences:

      p=343 d=396.062 → [(37, 323), (112, 273), (147, 245)]

      Like

  • Unknown's avatar

    Jim Randell 6:48 am on 5 January 2025 Permalink | Reply
    Tags:   

    Teaser 3250: Very similar triangles 

    From The Sunday Times, 5th January 2025 [link] [link]

    I have a piece of A6 paper on which I draw two triangles. The triangles are very similar in two ways. First of all, they are both the same shape. Not only that, the lengths of two of the sides of one of the triangles are the same as the lengths of two of the sides of the other triangle, but one triangle is larger than the other.

    If the sides of the triangles are whole numbers of millimetres and the triangles don’t have any obtuse angles:

    What are the lengths of sides of the larger triangle?

    Note: A sheet of A6 paper measures 105 mm × 148 mm.

    [teaser3250]

     
    • Jim Randell's avatar

      Jim Randell 7:02 am on 5 January 2025 Permalink | Reply

      (See also: Enigma 1198).

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

      from enigma import (irange, hypot, intf, printf)
      
      # consider triangles with sides (a, b, c), (b, c, d)
      # we have: ratio(a, b, c) = ratio(b, c, d)
      M = intf(hypot(105, 148))  # max length
      for a in irange(1, M - 1):
        for b in irange(a + 1, M):
          (c, r) = divmod(b * b, a)
          if c > M: break
          if r != 0: continue
          # check triangle is not obtuse
          if a + b <= c or c * c > a * a + b * b: continue
          (d, r) = divmod(c * c, b)
          if d > M: break
          if r != 0: continue
          # output solution
          printf("{t1} + {t2}", t1=(a, b, c), t2=(b, c, d))
      

      There is only a single candidate solution, and it is easy to demonstrate that the triangles can both fit onto a single piece of A6.

      Solution: The sides of the larger triangle are: 80 mm, 100 mm, 125 mm.

      And the smaller triangle has sides: 64 mm, 80 mm, 100 mm.

      The internal angles of the triangles are (approximately) 40°, 53°, 87°.

      Here is a diagram showing both triangles on a piece of A6 paper (dimensions in mm):

      Like

    • Tony Smith's avatar

      Tony Smith 4:29 pm on 5 January 2025 Permalink | Reply

      In fact there is nothing to stop the triangles overlapping.

      Like

      • Jim Randell's avatar

        Jim Randell 4:57 pm on 5 January 2025 Permalink | Reply

        @Tony: True enough. I was imagining we wanted to cut the triangles out, but that isn’t what the puzzle text says.

        Like

      • Ellen Napier's avatar

        Ellen Napier 12:18 am on 10 February 2026 Permalink | Reply

        In that case, the smaller size B7 would suffice.

        Like

  • Unknown's avatar

    Jim Randell 10:22 pm on 3 January 2025 Permalink | Reply
    Tags:   

    Teaser 2589: A certain age 

    From The Sunday Times, 6th May 2012 [link] [link]

    My cousin and I share a birthday. At the moment my age, in years, is the same as hers but with the order of the two digits reversed. On one of our past birthdays I was five times as old as her but, even if I live to a world record “ripe old age”, there is only one birthday in the future on which my age will be a multiple of hers.

    How old am I?

    [teaser2589]

     
    • Jim Randell's avatar

      Jim Randell 10:22 pm on 3 January 2025 Permalink | Reply

      Suppose my age is XY and my cousin’s age is YX, then we have X > Y.

      This Python program runs in 63ms. (Internal runtime is 306µs).

      from enigma import (irange, subsets, printf)
      
      # ages are the reverse of each other
      for (Y, X) in subsets(irange(1, 9), size=2):
        (XY, YX) = (10 * X + Y, 10 * Y + X)
      
        # look for an age in the past where I was five times the age of the cousin
        past = ((XY - n, YX - n) for n in irange(1, YX - 1))
        x1 = list((x, y) for (x, y) in past if x == 5 * y)
        if not x1: continue
      
        # look for ages in the future where my age is a multiple of hers
        future = ((XY + n, YX + n) for n in irange(1, 130 - XY))
        x2 = list((x, y) for (x, y) in future if x % y == 0)
        if len(x2) != 1: continue
      
        # output solution
        printf("XY={XY} YX={YX} [past={x1} future={x2}]")
      

      Solution: Your age is 62.

      And the cousin’s age is 26.

      17 years ago the ages were 45 and 9 (and 45 = 5 × 9).

      In 10 years time the ages will be 72 and 36 (and 72 = 2 × 36).

      Like

    • Hugo's avatar

      Hugo 7:23 am on 5 January 2025 Permalink | Reply

      The difference between the ages is 9(X – Y).
      That remains the same each year, and at one time it was 4 times the cousin’s age.
      So (X – Y) is either 4 or 8.
      The current ages could be 15, 51; 26, 62; 37, 73; 48, 84; or 59, 95.
      In all cases the difference is 36.
      Ages when one age is an integer multiple of the other are 1, 37; 2, 38; 4, 40; 9, 45; 18, 54; 36, 72.
      Only one of those is allowed to be in the future.

      Alternatively, the current ages could be 19, 91, with difference 72.
      Then the ‘multiple’ ages are 1, 73; 2, 74; 4, 76; 8, 80; 9, 81; 18, 90; 36, 108; 72, 144.
      But two of those are in the future.
      The only valid solution appears to be 26, 62.

      Like

    • Hugo's avatar

      Hugo 7:32 am on 6 January 2025 Permalink | Reply

      I forgot some pairs of ages where one is an integer multiple of the other.
      With a difference 36: 3, 39; 6, 42; 12, 48.
      Similarly with a difference 72, but we’ve eliminated that as a potential solution.

      Like

  • Unknown's avatar

    Jim Randell 4:43 pm on 31 December 2024 Permalink | Reply
    Tags:   

    Teaser 2596: Unusual factors 

    From The Sunday Times, 24th June 2012 [link] [link]

    There are no neat tests for divisibility by 7 or 13, so it’s unusual for these factors to feature in Teasers. Today we put that right.

    If you start with any three different non-zero digits, then you can make six different three-digit numbers using precisely those digits. For example, with 2, 5 and 7 you get 257, 275, 527, 572, 725 and 752. Here, one of their differences (572 − 257) is divisible by 7, and another (725
    − 257) is divisible by 13.

    Your task today is to find three different digits so that none of the six possible three-digit numbers and none of the differences between any pair of them is a multiple of either 7 or 13.

    What are the three digits?

    My review of puzzles posted in 2024 is at Enigmatic Code: 2024 in review.

    [teaser2596]

     
    • Jim Randell's avatar

      Jim Randell 4:44 pm on 31 December 2024 Permalink | Reply

      This Python program looks at possible sets of three digits, and then uses the [[ generate() ]] function to generate possible arrangements and differences. This means as soon as we find a number that fails the divisibility tests we can move on to the next set of digits.

      It runs in 65ms. (Internal runtime is 491µs).

      from enigma import (irange, subsets, nconcat, printf)
      
      # generate numbers to test from digits <ds>
      def generate(ds):
        seen = list()
        # consider all arrangements of the digits
        for n in subsets(ds, size=len, select='P', fn=nconcat):
          # return the arrangement
          yield n
          # return the differences
          for x in seen:
            yield abs(n - x)
          seen.append(n)
      
      # choose 3 digits
      for ds in subsets(irange(1, 9), size=3):
        # check numbers are not divisible by 7 or 13
        if any(n % 7 == 0 or n % 13 == 0 for n in generate(ds)): continue
        # output solution
        printf("digits = {ds}")
      

      Solution: The three digits are: 1, 4, 9.

      See also: A video by Matt Parker on divisibility tests [@youtube].

      Like

    • GeoffR's avatar

      GeoffR 7:34 pm on 1 January 2025 Permalink | Reply

      from itertools import permutations
      from enigma import nsplit
      
      N = []
      
      for a, b, c in permutations(range(1, 10), 3): 
          n1, n2 = 100*a + 10*b + c, 100*a + 10*c + b
          n3, n4 = 100*b + 10*a + c, 100*b + 10*c + a
          n5, n6 = 100*c + 10*a + b, 100*c + 10*b + a
          # Check three-digit numbers are not divisible by 7 or 13
          if any(x % 7 == 0 for x in (n1 ,n2, n3, n4, n5, n6)):
              continue
          if any(x % 13 == 0 for x in (n1, n2, n3, n4, n5, n6)):
              continue
          N.append(n1)
      
      # check differences for each number in the list
      # of potential candidates
      for m in N:
          x, y, z = nsplit(m)
          m1, m2 = m, 100*x + 10*z + y
          m3, m4 = 100*y + 10*x + z, 100*y + 10*z + x
          m5, m6 = 100*z + 10*x + y, 100*z + 10*y + x
          # a manual check of the 15 differences of each 6-digit number
          if abs(m1-m2) % 7 == 0 or abs(m1-m2) % 13 == 0: continue
          if abs(m1-m3) % 7 == 0 or abs(m1-m3) % 13 == 0: continue
          if abs(m1-m4) % 7 == 0 or abs(m1-m4) % 13 == 0: continue
          if abs(m1-m6) % 7 == 0 or abs(m1-m5) % 13 == 0: continue
          if abs(m1-m6) % 7 == 0 or abs(m1-m6) % 13 == 0: continue
          #
          if abs(m2-m3) % 7 == 0 or abs(m2-m3) % 13 == 0: continue
          if abs(m2-m4) % 7 == 0 or abs(m2-m4) % 13 == 0: continue
          if abs(m2-m5) % 7 == 0 or abs(m2-m5) % 13 == 0: continue
          if abs(m2-m6) % 7 == 0 or abs(m2-m6) % 13 == 0: continue
          #
          if abs(m3-m4) % 7 == 0 or abs(m3-m4) % 13 == 0: continue
          if abs(m3-m5) % 7 == 0 or abs(m3-m5) % 13 == 0: continue
          if abs(m3-m6) % 7 == 0 or abs(m3-m6) % 13 == 0: continue
          #
          if abs(m4-m5) % 7 == 0 or abs(m4-m5) % 13 == 0: continue
          if abs(m4-m6) % 7 == 0 or abs(m4-m6) % 13 == 0: continue
          if abs(m5-m6) % 7 == 0 or abs(m5-m6) % 13 == 0: continue
          if x < y < z:
              print(f"The three digit numbers are {x}, {y} and {z}.")
      
      # The three digit numbers are 1, 4 and 9.
      

      I resorted to a manual check of the differences of the 6 numbers arising from each 3 digit candidate, as it was not coming out easily in code.

      Like

  • Unknown's avatar

    Jim Randell 1:37 am on 29 December 2024 Permalink | Reply
    Tags:   

    Teaser 3249: Test to twelve 

    From The Sunday Times, 29th December 2024 [link] [link]

    I have been testing for divisibility by numbers up to twelve. I wrote down three numbers and then consistently replaced digits by letters (with different letters used for different digits) to give:

    TEST
    TO
    TWELVE

    Then I tested each of these numbers to see if they were divisible by any of 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 or 12. It turned out that each of these eleven divisors was a factor of exactly one of the three numbers.

    What are the three numbers?

    This completes the archive of puzzles from 2024. There is now a complete archive of puzzles (and solutions!) from July 2012 onwards (plus a lot of earlier puzzles). Enjoy!

    [teaser3249]

     
    • Jim Randell's avatar

      Jim Randell 1:55 am on 29 December 2024 Permalink | Reply

      Here is a short, but not particularly quick, solution using the [[ SubstitutedExpression ]] solver from the enigma.py library.

      It runs in 446ms (using PyPy, internal runtime of the generated program is 272ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --code="check = lambda ns, ds, k: all(sum(n % d == 0 for n in ns) == k for d in ds)"
      
      "check([TEST, TO, TWELVE], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1)"
      

      Solution: The numbers are: 3073, 31, 340560.

      So:

      TEST = 3073, is divisible by 7
      TO = 31, is prime
      TWELVE = 340560, is divisible by 2, 3, 4, 5, 6, 8, 9, 10, 11, 12


      Manually:

      Analysis shows that TWELVE must be divisible by 2, 3, 4, 5, 6, 8, 9, 10, 11, 12 (and TO and TEST are not).

      Hence TWELVE is a 6-digit multiple of 3960. The only appropriate candidates are:

      130560
      150480
      170280
      320760
      340560
      380160
      740520
      760320
      780120

      None of of which are divisible by 7. So either TO or TEST is divisible by 7.

      If it is TO the only option is 35, but this is also divisible by 5, so TEST must be divisible by 7, and TO must have none of the divisors.

      Possible values for TEST (divisible by only 7) are:

      TWELVE = 340560 → TEST = 3073, TO = 31
      TWELVE = 380160 → TEST = 3073, TO = [none]

      And so we have found the required solution.

      Like

      • Jim Randell's avatar

        Jim Randell 8:11 am on 29 December 2024 Permalink | Reply

        A bit of analysis provides extra constraints that reduces the internal runtime down to a more acceptable 24ms:

        There can only be one even number (i.e. only one of T, O, E is even), and whichever of the words is even must be the one divisible by 2, 4, 6, 8, 10, 12 (and hence 3, 5, 9). These have a LCM of 360, so O cannot be the even digit, it must be one of T or E, and the digit must be 0. Hence it must be E. And T and O must be odd digits (but not 5).

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        --code="check = lambda ns, ds, k: all(sum(n % d == 0 for n in ns) == k for d in ds)"
        
        "check([TEST, TO, TWELVE], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1)"
        
        # [optional] additional constraints to speed things up:
        # E is 0
        --assign="E,0"
        # T and O are odd digits (but not 5)
        --invalid="0|2|4|5|6|8,TO"
        

        And further constraints can be used to get the runtime down to 0.9ms:

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        --code="check = lambda ns, ds, k: all(sum(n % d == 0 for n in ns) == k for d in ds)"
        
        "check([TEST, TO, TWELVE], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1)"
        
        # [optional] additional constraints to speed things up:
        # E is 0
        --assign="E,0"
        # T and O are odd digits (but not 5)
        --invalid="0|2|4|5|6|8,TO"
        
        # TWELVE is divisible by: 2, 3, 4, 5, 6, 8, 9, 10, 12; LCM = 360
        "TWELVE % 360 = 0"
        
        # and TEST and TO are not
        "check([TO, TEST], [2, 3, 4, 5, 6, 8, 9, 10, 12], 0)"
        
        # which leaves 7, 11
        "check([TEST, TO, TWELVE], [7, 11], 1)"
        
        --template="(TEST TO TWELVE)"
        --reorder="[2,4,3]"
        

        And Frits’ observation that 11 must also be a divisor of TWELVE further reduces the runtime to 0.5ms.

        Like

        • Frits's avatar

          Frits 6:06 pm on 29 December 2024 Permalink | Reply

          from math import ceil
          from itertools import product
          
          # check if number <n> is not divisible by any of <ds>
          check = lambda n, ds: all(n % d for d in ds)        
          
          # a given number can only be completely divided by 11 if the difference of
          # the sum of digits at odd position and sum of digits at even position in a 
          # number is 0 or 11
          
          # So TEST is not divisible by 11 as (S + T) - T = S and S not in {0, 11} 
          # TO is not divisible by 11 as otherwise T would be equal to O
          # So TWELVE must be divisible by 11
          
          # TWELVE must be divisible by 2, 4, 6, 8, 10, 11, 12 (and hence 3, 5 and 9)
          # these have a LCM of 3960 = 11 * 360
          
          # E = 0 and both T and O are odd (see S2T2 page)
          
          lcm = 3960
          for t in range(1, 10, 2):
            # loop over divisor of TWELVE
            for k in range(ceil((t1 := 100000 * t) / lcm), (t1 + 90999) // lcm + 1):
              if ((twelve := k * lcm) // 1000) % 10 != 0: continue # check E being zero
              if len(set12 := set(str(twelve))) != 5: continue     # 4 different digits
              
              # choose 2-12 divisors  or  2-6 and 8-12 divisors
              divs = (set(range(2, 13)) - {7} if (need7 := twelve % 7 > 0) else 
                       range(2, 13))
              
              # candidates for TEST 
              TESTs = [n for s in range(1, 10) if str(s) not in set12 and 
                         check(n := 1001 * t + 10 * s, divs)]
              if not TESTs: continue
              
              T0 = 10 * t
              # candidates for TO 
              TOs = [n for o in range(1, 10, 2) if str(o) not in set12 and 
                     check(n := T0 + o, divs)]
              
              # check combinations of TEST and TO
              for test, to in product(TESTs, TOs): 
                if (test % 100) // 10 == to % 10: continue  # S must be unequal to O
                # we need the right amount of entries divisible by 7
                if sum(z % 7 == 0 for z in [test, to]) == need7:
                  print(f"answer: {test}, {to} and {twelve}")
          

          Like

          • Frits's avatar

            Frits 3:38 pm on 31 December 2024 Permalink | Reply

            If we know one of the values in pair (W, L) then the other value can be calculated. The same is true for the pair (T, V).
            It also reduces the domain of some of the variables T, V, W and L.

            Like

        • Frits's avatar

          Frits 11:12 pm on 29 December 2024 Permalink | Reply

          @Jim, nice to see the improvement to the reorder parameter.

          Surprisingly using even values for V doesn’t speed things up a lot.

          Like

    • bencooperjamin's avatar

      bencooperjamin 8:09 pm on 30 December 2024 Permalink | Reply

      Jim, after selecting all the even divisors you need to add in turn the divisors of the even numbers (3/6,5/10, and then in turn 9 because 3 is a divisor of that number, giving LCM 360) so you are only left with primes 7 and 11 . We need different prime multiples above 12 to leverage these three groups into the 3 compliant numerals given.

      Like

    • GeoffR's avatar

      GeoffR 11:03 am on 31 December 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Using Jim's / Frits' analysis
      % E is 0 
      % T and O are both odd
      % Either TEST or TWELVE is divisible by 7.
      % Only TWELVE is divisible by 11.
      % TWELVE is divisible by 2,3,4,5,6,8,9,10,11, 12.
      % TWELVE cannot be divisible by 7 as it ends in zero.
      % so either TEST or TO must be divisible by 7.
      % Since T an O are odd the only value for TO would be 35.
      % ... but divisor 5 is catered for elsewhere.
      % TEST is not divisible by 11 by the divisibility rule for 11
      % as (T - E + S - T ) is not zero or divisible by 11.
      % TO is not divisible by 11 as T and O are different odd digits.
      
      
      var 1..9:T; var 0..9:E; var 0..9:S; var 0..9:O;
      var 0..9:W; var 0..9:L; var 0..9:V;
      
      constraint all_different([T, E, S, O, W, L, V]);
      
      constraint E == 0 /\ T mod 2 == 1 /\ O mod 2 == 1;
      
      % Using the rule for divisibility by 11 for TWELVE.
      constraint (T - W + E - L + V - E) mod 11 == 0 
      \/ (T - W + E - L + V - E) == 0;   
      
      var 1000..9999:TEST == 1001*T + 100*E + 10*S;
      var 10..99: TO == 10*T + O;
      var 100000..999999: TWELVE == 100000*T + 10000*W + 1001*E + 100*L + 10*V;
      
      % There are 9 divisors for TWELVE here
      % ... the divisors 7 and 11 are tested separately
      constraint sum ([TWELVE mod 2 == 0, TWELVE mod 3 == 0, TWELVE mod 4 == 0, 
      TWELVE mod 5 == 0, TWELVE mod 6 == 0, TWELVE mod 8 == 0, TWELVE mod 9 == 0, 
      TWELVE mod 10 == 0, TWELVE mod 12 == 0]) == 9;
      
      % TEST cannot use the same divisors necessary for TWELVE
      constraint sum ([TEST mod 2 == 0, TEST mod 3 == 0, TEST mod 4 == 0, 
      TEST mod 5 == 0, TEST mod 6 == 0, TEST mod 8 == 0, TEST mod 9 == 0, 
      TEST mod 10 == 0, TEST mod 11 == 0, TEST mod 12 == 0]) == 0;
      
      % Minimun check for lower odd divisors needed as both T and O are odd digits
      % and divisor 5 is used for TWELVE
      constraint TO mod 3 != 0 /\ TO mod 5 != 0;
      
      % Only TWELVE or TEST is divisible by 7.
      constraint TWELVE mod 7 == 0 \/ TEST mod 7 == 0;   
      
      solve satisfy;
      
      output ["TEST, TO, TWELVE = " ++ show(TEST) ++ ", " ++ show(TO) ++ ", " ++ show(TWELVE) 
      ++ "\n" ++ "[T,E,S,O,W,L,V] = " ++ show ([T,E,S,O,W,L,V]) ];
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 1:31 pm on 25 December 2024 Permalink | Reply
    Tags:   

    Teaser 2569: Christmas gift 

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

    I sent Hank, an American friend, a Times subscription for a Christmas present. He is pleased and has responded with this numerical addition sum with a prime total in which he has consistently replaced digits by letters, with different letters for different digits.

    What is TIMES?

    [teaser2569]

     
    • Jim Randell's avatar

      Jim Randell 1:31 pm on 25 December 2024 Permalink | Reply

      A quick one for Christmas Day.

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

      It runs in 87ms. (Internal runtime of the generated code is 12.5ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      "GEE + A + XMAS + TREE + GIFT = TIMES"
      
      --extra="is_prime(TIMES)"
      --answer="TIMES"
      

      Solution: TIMES = 12589.

      Like

    • GeoffR's avatar

      GeoffR 9:00 pm on 25 December 2024 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:G; var 1..9:A; var 1..9:X; var 1..9:T;
      var 0..9:E; var 0..9:M; var 0..9:S; var 0..9:R;
      var 0..9:I; var 0..9:F;
      
      constraint all_different([G, E, A, X, T, M, S, R, I, F]);
        
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
        
      var 100..999:GEE == 100*G + 11*E;
      var 1000..9999 : XMAS == 1000*X + 100*M + 10*A + S;
      var 1000..9999 : TREE == 1000*T + 100*R + 11*E;
      var 1000..9999 : GIFT == 1000*G + 100*I + 10*F + T;
      var 10000..99999: TIMES == 10000*T + 1000*I + 100*M + 10*E + S;
      
      constraint GEE + A + XMAS + TREE  + GIFT == TIMES;
      
      constraint is_prime(TIMES);
      
      solve satisfy;
      output ["TIMES = " ++ show(TIMES) ];
      
      % TIMES = 12589
      % ----------
      % ==========  
      
      
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 7:25 pm on 26 December 2024 Permalink | Reply

      from itertools import permutations
      from enigma import is_prime
      
      # Adding columns from the right hand end being column col1
      # with a carry of c1 to col2
      for p1 in permutations(range(10), 4):
          E, A, S, T = p1
          if 0 in (A, T): continue
          c1, col1 = divmod((2*E + A + S + T), 10)
          if col1 != S: continue
      
          # Column 2
          q1 = set(range(10)).difference({E, A, S, T})
          for p2 in permutations(q1, 1):
              F = p2[0]
              c2, col2 = divmod((2*E + A + F + c1), 10)
              if col2 != E: continue
      
              # Column 3
              q2 = q1.difference({F})
              for p3 in permutations(q2, 4):
                  G, M, R, I = p3
                  if G == 0: continue
                  c3, col3 = divmod((G + M + R + I + c2), 10)
                  if col3 != M: continue
      
                  # Column 4
                  q3 = q1.difference({F, G, M, R, I})
                  X = next(iter(q3))
                  c4, col4 = divmod((X + T + G + c3), 10)
                  if col4 != I: continue
                  if c4 != T: continue
                  
                  TIMES = 10000*T + 1000*I + 100*M + 10*E + S
                  if not is_prime(TIMES): continue
                  print(f"TIMES = ", TIMES)
      
      

      Like

  • Unknown's avatar

    Jim Randell 7:55 am on 22 December 2024 Permalink | Reply
    Tags:   

    Teaser 3248: Miss Scarlet in the study 

    From The Sunday Times, 22nd December 2024 [link] [link]

    In Cluedo, one “room” card (from nine possible ones), one “person” card (from six) and one “weapon” card (from six) are placed in a murder bag. The remaining 18 cards are shared out (four or five per player). Players take turns to suggest the contents of the murder bag. If wrong, another player shows the suggester a card, eliminating one possibility. The first person to know the contents of the murder bag will immediately [reveal their solution and] claim victory.

    My brothers and I are playing, in the order Michael-John-Mark-Simon. After several completed rounds, we all know the murder was committed in the Study. Michael and I know the murderer was Miss Scarlet with one of four weapons. Mark and Simon know the weapon and have narrowed it down to four people. Being new to Cluedo, we don’t learn from others’ suggestions.

    From this point on, what are Michael’s chances of winning (as a fraction in its lowest terms)?

    To clarify the wording, the intention is that once a player has deduced the contents of the murder bag, they reveal their solution, and do not have to wait for their next turn.

    [teaser3248]

     
    • Jim Randell's avatar

      Jim Randell 8:12 am on 22 December 2024 Permalink | Reply

      Not learning from another players accusation means that a player may repeat an accusation that has previously been suggested (and shown to be false), but it does make working out the probabilities easier.

      Each of the players has narrowed down the scenario to one of four possibilities.

      Initially Mike has a 1/4 chance of choosing the right weapon (assuming he suggests one of his four possibilities).

      And a 3/4 chance of choosing the wrong weapon. The card shown to him must be the weapon he suggested, so he is reduced to 3 possibilities.

      And the other players proceed similarly.

      If none of them make a correct accusation, then in the next round each player is down to 3 possibilities.

      And in the following round (if any), each player is down to 2 possibilities.

      Then in the next round (if any), each player is down to 1 possibility, and so Mike (who goes first) will make a correct accusation.

      So we can calculate the probability of Mike winning each round:

      from enigma import (Rational, printf)
      
      Q = Rational()
      
      # each of <n> players has a <k> remaining scenarios
      (n, k, r, q) = (4, 4, 0, 1)
      while True:
        # p = probability of Mike winning this round
        p = Q(1, k)
        # accumulate probability of Mike winning some round
        r += q * p
        if p == 1: break
        k -= 1
        # q = probability of there being a next round
        q *= (1 - p)**n
      
      # output solution
      printf("r = {r}")
      

      Solution: Michael’s chances of winning are 25/64.

      Which is about 39%.

      Note: This applies if the players have to wait for their turn before making a certain accusation, which was my original interpretation of the puzzle. The intended interpretation is that players can declare a solution as soon they are certain of it, so Michael does not have to wait for his fourth turn.

      Like

      • Jim Randell's avatar

        Jim Randell 10:20 am on 23 December 2024 Permalink | Reply

        Originally I took the text “players take turns to suggest the contents of the murder bag” at face value, but the intention of the puzzle is that once a solution has been deduced with certainty it is declared without waiting for the next turn.

        In this case Michael can declare a solution after making his third round suggestion (where he has only two scenarios remaining). If his suggestion is wrong, he knows the remaining scenario is the correct one.

        We can adjust the code given above for this interpretation of the puzzle text:

          p = (Q(1, k) if k > 2 else 1)
        

        Solution: Michael’s chances of winning are 107/256.

        Which is about 42%.

        If he has to wait for his turn before making his certain accusation, his chances of winning are 25/64 (about 39%).

        Like

      • Frits's avatar

        Frits 12:06 pm on 23 December 2024 Permalink | Reply

        “The card shown to him must be the weapon he suggested”.

        “eliminating one possibility”. What is a possibility (one of the 9x6x6 possibilities?).

        I have never played Cluedo.

        I am considering not trying to solve recent Teasers anymore as I have felt for a couple of weeks that solving Teasers has 2 layers. The first layer being to interpret a puzzle text that seems to have been constructed to use as few words as possible instead of explaining the rules in a way that leaves no interpretation.

        Like

    • Pete Good's avatar

      Pete Good 7:21 pm on 22 December 2024 Permalink | Reply

      Jim, I followed the same thought process as you did but there is another interpretation which enables Michael to win in the third round.

      In this round, he has an equal chance of suggesting the right weapon or the wrong weapon. If he suggests the right one then nobody will show him its card so he knows that it is in the murder pack. If he suggest the wrong one then somebody will show him its card so he knows that the other weapon card is in the murder pack. Either way, he knows the contents of the murder bag and can immediately claim victory!

      I can’t see anything in the teaser wording that avoids this except that the fraction you obtain cannot be reduced to lower terms. I’d be interested to know what you think.

      Like

      • Jim Randell's avatar

        Jim Randell 11:11 pm on 22 December 2024 Permalink | Reply

        @Pete: I see what you mean.

        It depends on the exact mechanics of the gameplay. I was assuming that you can only make one accusation on your turn (“players take turns to suggest the contents of the murder bag”), and then your turn ends, so you have to wait until your next turn to make another accusation.

        But it does also say “the first person to know the contents of the murder bag will immediately claim victory”. Does that mean you can make an accusation out of turn? If so, then Mike can jump in with a correct accusation immediately after his third go without waiting for the fourth round. And his chance of winning will be a different fraction to the value I calculated.

        Looks like it is another puzzle where the wording could be clearer.


        Also, although it has been a while since I played Cluedo, I don’t think this is how play proceeds in the actual game. I recall there is an “interrogation” phase of moving around between rooms, “I suspect X in room Y with weapon Z”, and then (if possible) another player shows you a card that can disprove the suspicion (i.e. one of X, Y, Z). But later in the game you can make an accusation (during your turn), and if it is correct you win, but if it is wrong you are out of the game.

        But this doesn’t seem to be the same as the gameplay given in this puzzle.

        Like

        • Brian Gladman's avatar

          Brian Gladman 9:22 am on 23 December 2024 Permalink | Reply

          @Jim : John Owen (the teaser coordinator for the Sunday Times Teaser) has posted on this page to say that players can announce their win as soon as they know it without waiting f or their turn. This rather suggests that the third round win for Michael is the intended solution.

          Like

  • Unknown's avatar

    Jim Randell 12:08 pm on 19 December 2024 Permalink | Reply
    Tags:   

    Teaser 2601: Olympic economics 

    From The Sunday Times, 29th July 2012 [link] [link]

    George and Martha are organising a large sports meeting. Usually a gold medal (costing £36) is given to each winner, silver (£17) to each runner-up, and bronze (£11) to each third. In a minority of events, such as tennis and boxing, both losing semi-finalists get a bronze medal. However, George and Martha are going to economise by having third place play-offs in such events, thus reducing the medals needed by an odd number. George noticed that the total cost of the medals is now a four-figure palindrome, and Martha commented that the same would have been true under the previous system.

    How many events are there?

    [teaser2601]

     
    • Jim Randell's avatar

      Jim Randell 12:09 pm on 19 December 2024 Permalink | Reply

      If there are n events, under the new system each has a 1 gold, 1 silver and 1 bronze medal (assuming each event is for individual competitors). The cost of medals for each event is £36 + £17 + £11 = £64. So the total cost of medals is 64n, and we are told this is a 4-digit palindrome.

      This Python program looks for possible multiples of 64 that give a 4-digit palindrome, and then looks at adding an odd number of extra bronze medals (for less than half of the events), that also gives a 4-digit palindrome for the total cost.

      It runs in 67ms. (Internal runtime is 156µs).

      from enigma import (irange, inf, is_npalindrome, printf)
      
      # consider the number of events
      for n in irange(1, inf):
        # total cost of medals is a 4-digit palindrome
        t = 64 * n  #  (gold + silver + bronze) per event
        if t < 1000: continue
        if t > 9999: break
        if not is_npalindrome(t): continue
      
        # consider an odd number of extra bronze medals
        for x in irange(1, (n - 1) // 2, step=2):
          tx = t + 11 * x
          if tx > 9999: break
          if not is_npalindrome(tx): continue
      
          # output solution
          printf("{n} events -> cost = {t}; +{x} bronze -> cost = {tx}")
      

      Solution: There are 132 events.

      So the total cost of medals under the new system is £64×132 = £8448.

      There are two situations where the cost of additional bronze medals also gives a 4-digit palindromic total cost:

      If there are 51 extra bronze medals required under the old system, then the additional cost is £11×51 = £561, and the total cost would have been £9009.

      If there are 61 extra bronze medals required, then the additional cost is £11×61 = £671, and the total cost would have been £9119.

      Like

  • Unknown's avatar

    Jim Randell 10:02 am on 17 December 2024 Permalink | Reply
    Tags:   

    Teaser 2598: Die hard 

    From The Sunday Times, 8th July 2012 [link] [link]

    I have placed three identical standard dice in a row on a table-top, leaving just eleven faces and an odd number of spots visible. Regarding each face as a digit (with, naturally enough, the five-spotted face being the digit 5, etc.) from the front I can read a three-digit number along the vertical faces and another three-digit number along the top. Similarly, if I go behind the row I can read a three-digit number along the vertical faces and another three-digit number along the top. Of these four three-digit numbers, two are primes and two are different perfect squares.

    What are the two squares?

    [teaser2598]

     
    • Jim Randell's avatar

      Jim Randell 10:02 am on 17 December 2024 Permalink | Reply

      See also: Teaser 3130.

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

      It runs in 80ms. (Internal runtime of the generated code is 2.3ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # layout (viewed from above):
      #
      #       I H G
      #     +-------+
      #   X | A B C | Y
      #     +-------+
      #       D E F
      
      --digits="1-6"
      --distinct="ADIX,BEH,CFGY"
      
      # check for valid numbers
      --code="check = lambda x: is_prime(x) or is_square_p(x)"
      
      # the top can be read both ways
      "check(ABC)"
      "check(CBA)"
      
      # the front and back
      "D + I = 7" "E + H = 7" "F + G = 7"
      "check(DEF)"
      "check(GHI)"
      
      # total number of spots is odd
      "sum([A, B, C, D, E, F, G, H, I, X, Y]) % 2 = 1"
      
      # two of the 3-digit numbers are primes, and two are (different) squares
      --macro="@numbers = (ABC, CBA, DEF, GHI)"
      "len(list(filter(is_prime, @numbers))) = 2"
      "len(set(filter(is_square_p, @numbers))) = 2"
      
      # possible corner configurations (right-handed dice)
      --code="all_corners = lambda x, y, z, k=7: union([(x, y, z), (k - x, k - z, k - y)] for (y, z) in tuples([y, z, k - y, k - z, y], 2))"
      --code="corners = union(repeat(rotate, v, 2) for v in all_corners(3, 2, 1))"  # or [[ (1, 2, 3) ]] for LH
      "(A, D, X) in corners"
      "(A, X, I) in corners"
      "(C, Y, F) in corners"
      "(C, G, Y) in corners"
      
      --code="ans = lambda ns: call(ordered, filter(is_square_p, ns))"
      --answer="ans(@numbers)"
      --template="@numbers (X, Y)"
      --solution=""
      

      Solution: The squares are 361 and 625.

      (Or this arrangement can be viewed from the back).

      From the front the top reads 361 (= 19^2) and the front reads 625 (= 25^2).

      From the back the top reads 163 (= prime) and the back reads 251 (= prime).

      With standard (right-handed) dice the left and right sides are 2 and 4, giving a total visible spot count of 37.

      The same numbers work for mirrored (left-handed) dice, but the left and right sides are 5 and 3, giving a total visible spot count of 39.

      Like

    • Frits's avatar

      Frits 2:12 pm on 17 December 2024 Permalink | Reply

      # as we have 2 primes and 2 squares and primes and squares are mutually
      # exclusive every 3-digit number must either be square or prime
      
      # layout is:
      #
      #       I H G
      #     +-------+
      #   X | A B C | Y
      #     +-------+
      #       D E F
      
      # given two dice faces anti-clockwise at a vertex, find the third
      # face at this vertex (using a western die if 'same' is True)
      def die_third_face(f, s, same=True):
        if s in {f, 7 - f}:
          raise ValueError
        oth = [i for i in range(1, 7) if i not in {f, s, 7 - f, 7 - s}]
        return oth[((f < s) == ((s - f) % 2)) == (same == (s + f > 7))]
      
      dgts = "123456" 
      # opposite side
      opp = {str(i): str(7 - i) for i in range(1, 7)}  
      
      # 3-digit squares
      sqs = {str(i * i) for i in range(10, 32) if all(x in dgts for x in str(i * i))}
         
      # determine valid 3-digit primes
      prms = {3, 5, 7}
      prms |= {x for x in range(11, 100, 2) if all(x % p for p in prms)}
      prms = {str(i) for i in range(101, 700, 2) if all(i % p for p in prms) and
              all(x in dgts for x in str(i))}
      
      # candidates for numbers along the top
      top = sqs | prms
      
      # front and back: DEF and IHG
      fb = [(DEF, GHI[::-1]) for DEF in top if 
            (GHI := (''.join(opp[x] for x in DEF))[::-1]) in top]
              
      # combine 3-digit squares which reverse are square or prime with
      #         3-digit primes which reverse are square or prime
      top = ({s for s in sqs if s[::-1] in top} |
             {p for p in prms if p[::-1] in top})
      
      sols = set()
      for f, b in fb:
        # mix front and back with possible top 
        for t in [t for t in top if all(t[i] not in f[i] + b[i] for i in range(3))]:
          # two are different perfect squares
          used_sqs = tuple(x for x in (f, b[::-1], t, t[::-1]) if x in sqs)
          if len(used_sqs) != 2 or len(set(used_sqs)) == 1: continue
          
          # assume a western die (counterclockwise)
          left  = str(die_third_face(int(f[0]), int(t[0])))
          right = str(die_third_face(int(t[2]), int(f[2])))
          # an odd number of spots visible
          if sum(int(x) for x in f + b + t + left + right) % 2 == 0: continue
          sols.add(used_sqs)
          
      for sq1, sq2 in sols:
        print(f"answer: {sq1} and {sq2}")
      

      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