Design a site like this with WordPress.com
Get started

Updates from October, 2022 Toggle Comment Threads | Keyboard Shortcuts

  • Jim Randell 7:56 am on 6 October 2022 Permalink | Reply
    Tags:   

    Teaser 2750: Granny’s birthdays 

    From The Sunday Times, 7th June 2015 [link] [link]

    At Granny’s birthday this year she was telling us some surprising things about some past birthdays. She told us that each year she writes down the date of her birthday (in the eight-digit form dd/mm/yyyy) and her age in years.

    On two occasions in her lifetime it has turned out that this has involved writing each of the digits 0 to 9 exactly once. The first of these occasions was in 1974.

    What is Granny’s date of birth (in the eight-digit form)?

    Note that in order to solve this puzzle it is important to be aware of the date it was originally set.

    All 200 puzzles included in the book The Sunday Times Brain Teasers Book 1 (2019) are now available on S2T2.

    [teaser2750]

     
    • Jim Randell 8:00 am on 6 October 2022 Permalink | Reply

      The puzzle was originally set on 7th June 2015, and mentions Granny’s birthday “this year” as having already happened. So her birthday must be earlier in the year than 7th June.

      This Python program considers dates in 1974 up to 6th June, if the date includes 8 different digits, then the remaining 2 digits indicate Granny’s age (in some order), and we can then look for other years.

      The program runs in 59ms. (Internal runtime is 4.3ms)

      Run: [ @replit ]

      from datetime import (date, timedelta)
      from enigma import (irange, repeat, nsplit, union, subsets, nconcat, printf)
      
      digits = set(irange(0, 9))
      
      # consider dates earlier than this
      end = date(1974, 6, 7)
      
      # consider dates in 1974
      inc = lambda x, i=timedelta(days=1): x + i
      for d in repeat(inc, date(1974, 1, 1)):
        if not (d < end): break
        # remaining digits
        ds4 = union([nsplit(d.month, 2), nsplit(d.day, 2)])
        ds = digits.difference(nsplit(d.year, 4), ds4)
        if len(ds) != 2: continue
        # construct possible ages
        for ss in subsets(ds, size=2, select='P'):
          age = nconcat(ss)
          year = 1974 - age
          # collect "special" years
          years = list()
          for bd in irange(10, 99):
            y = year + bd
            if y > 2015: break
            if not digits.difference(nsplit(y, 4), ds4, nsplit(bd, 2)):
              years.append(y)
          if len(years) == 2 and years[0] == 1974:
            # output solution
            printf("{d} -> {years}", d=date(year, d.month, d.day).strftime("%d/%m/%Y"))
      

      Solution: Granny’s date of birth is: 26/05/1936.

      So Granny is 38 in 1974 → (26/05/1974, 38).

      And she is 47 in 1983 → (26/05/1983, 47).

      In 2015 (when the puzzle was set) she was 79.

      If we consider all dates in 1974 then there is a further solution of: 25/06/1936.

      So, the puzzle only has a unique solution if posed between 27th May and 24th June. A fact that was not mentioned when the puzzle was included in the book The Sunday Times Brain Teasers 1 (2019).

      Like

  • Jim Randell 8:11 am on 4 October 2022 Permalink | Reply
    Tags:   

    Teaser 2751: Red-handed 

    From The Sunday Times, 14th June 2015 [link] [link]

    I removed an even number of red cards from a standard pack and I then divided the remaining cards into two piles. I drew a card at random from the first pile and it was black (there was a whole-numbered percentage chance of this happening). I then placed that black card in the second pile, shuffled them, and chose a card at random from that pile. It was red (and the percentage chance of this happening was exactly the same as that earlier percentage).

    How many red cards had I removed from the pack?

    [teaser2751]

     
    • Jim Randell 8:11 am on 4 October 2022 Permalink | Reply

      This Python program runs in 56ms. (Internal runtime is 1.7ms).

      Run: [ @replit ]

      from enigma import (irange, cproduct, div, printf)
      
      # start with 26 red, 26 black
      R = B = 26
      
      # remove an even (non-zero) number of red cards from the pack
      for k in irange(2, R, step=2):
      
        # pile 1 has r1 reds and b1 blacks (at least 1 black)
        for (r1, b1) in cproduct([irange(0, R - k), irange(1, B)]):
      
          # chance of drawing a black is a whole number percentage
          pb = div(100 * b1, r1 + b1)
          if pb is None: continue
      
          # a black card is moved to pile 2 which now has r2 reds and b2 blacks
          (r2, b2) = (R - k - r1, B - b1 + 1)
          # chance of a red card is also pb percent
          pr = div(100 * r2, r2 + b2)
          if pr is None or pr != pb: continue
      
          # output solution
          printf("k={k}: p1={r1}r + {b1}b -> pb={pb}%; p2={r2}r + {b2}b -> pr={pr}%")
      

      Solution: 8 red cards were removed from the pack.

      From the 18 red and 26 black cards the two piles can be made in two ways:

      pile 1: 6 red + 24 black; P(black) = 24/30 = 80%
      (1 black card is moved to pile 2)
      pile 2: 12 red + 3 black; P(red) = 12/15 = 80%

      pile 1: 12 red + 3 black; P(black) = 3/15 = 20%
      (1 black card is moved to pile 2)
      pile 2: 6 red + 24 black; P(red) = 6/30 = 20%

      Like

  • Jim Randell 8:48 am on 29 September 2022 Permalink | Reply
    Tags:   

    Teaser 2747: Marble jar 

    From The Sunday Times, 17th May 2015 [link] [link]

    At our local fete one of the games consisted of guessing the number of marbles in a jar: some of the marbles were red and the rest were blue. People had to guess how many there were of each colour.

    The organiser gave me a couple of clues. Firstly, he told me that there were nearly four hundred marbles altogether. Secondly, he told me that if, when blindfolded, I removed four marbles from the jar, then the chance that they would all be red was exactly one in a four-figure number.

    How many red marbles were there, and how many blue?

    [teaser2747]

     
    • Jim Randell 8:49 am on 29 September 2022 Permalink | Reply

      If there are T marbles in total (T = “nearly 400”) and R of them are red, then the probability of removing 4 red marbles is:

      P = (R / T) × ((R − 1) / (T − 1)) × ((R − 2) / (T − 2)) × ((R − 3) / (T − 3))
      P = (R × (R − 1) × (R − 2) × (R − 3)) / (T × (T − 1) × (T − 2) × (T − 3))

      And this probability is “one in a four-figure number”, so the largest it can be is 1/1000 and the smallest is 1/9999.

      The following Python program considers total numbers of marbles from 399 down to 350, and then looks for numbers of red marbles that give an appropriate value for P.

      It runs in 57ms. (Internal run time is 1.2ms).

      Run: [ @replit ]

      from enigma import (irange, printf)
      
      # consider total number of marbles (nearly 400)
      for T in irange(399, 350, step=-1):
        # denominator of P
        d = T * (T - 1) * (T - 2) * (T - 3)
        # R = number of red marbles
        for R in irange(4, T):
          # numerator of P
          n = R * (R - 1) * (R - 2) * (R - 3)
          # calculate p = 1/P
          (p, x) = divmod(d, n)
          if p > 9999: continue
          if p < 1000: break
          if x == 0:
            # output solution
            printf("red = {R}, blue = {B}; total = {T} -> P = 1/{p}",B=T - R)
      

      Solution: There were 45 red marbles and 342 blue marbles.

      So, 387 marbles in total. And the probability of choosing 4 red is 1/6176.

      Like

  • Jim Randell 8:45 am on 27 September 2022 Permalink | Reply
    Tags:   

    Teaser 2749: Round the river 

    From The Sunday Times, 31st May 2015 [link] [link]

    My school holds “Round the river” runs — a whole number of metres to a bridge on the river and then the same number of metres back. Some years ago I took part with my friends Roy, Al, David and Cy. We each did the outward half at our own steady speeds (each being a whole number of centimetres per minute). For the return half I continued at my steady speed, Roy increased his speed by 10%, Al increased his speed by 20%, David increased his by 30%, and Cy increased his by 40%. We all finished together in a whole number of minutes, a little less than half an hour.

    What (in metres) is the total length of the race?

    [teaser2749]

     
    • Jim Randell 8:46 am on 27 September 2022 Permalink | Reply

      The speeds are whole numbers of centimetres per minute, so we will work in units of centimetres and minutes.

      If the distance to bridge is x centimetres, then x must be divisible by 100.

      And the time is t minutes (less than 30).

      If the 5 speeds are: a, b, c, d, e, then we have:

      t = x/a + x/a = 2x / a
      t = x/b + x/1.1b = 21x / 11b
      t = x/c + x/1.2c = 11x / 6c
      t = x/d + x/1.3d = 23x / 13d
      t = x/e + x/1.4e = 12x / 7e

      From which we see that x must also be divisible by 11, 6, 13, 7.

      Placing some sensible limits on distance and speeds, we can suppose x is less than 10km (= 10,000m = 1,000,000cm), and that speeds are less than 15mph (≈ 40,000cm/min).

      This Python program runs in 61ms. (Internal runtime is 129µs).

      Run: [ @replit ]

      from enigma import (irange, mlcm, div, printf)
      
      # x must be a multiple of m
      m = mlcm(100, 11, 6, 13, 7)
      
      # consider possible total times: "a little less than half an hour"
      for t in irange(29, 21, step=-1):
      
        # consider possible values of x (up to 10km)
        for x in irange(m, 1000000, step=m):
      
          # calculate the speeds
          vs = [
            div(20 * x, 10 * t),
            div(21 * x, 11 * t),
            div(22 * x, 12 * t),
            div(23 * x, 13 * t),
            div(24 * x, 14 * t),
          ]
          # check speeds
          if None in vs or vs[-1] * 1.4 > 40000: continue
      
          # output solution
          printf("d={d} metres [t={t} x={x} {vs}]", d=div(2 * x, 100))
      

      Solution: The total length of the race is 6006m.

      And the race took exactly 25 minutes.

      The outward speeds are:

      1: 24024 cm/min (≈ 8.96 mph); 12.5 min out + 12.5 min back (@ 8.96 mph)
      2: 22932 cm/min (≈ 8.55 mph); 13.1 min out + 11.9 min back (@ 9.40 mph)
      3: 22022 cm/min (≈ 8.21 mph); 13.6 min out + 11.4 min back (@ 9.85 mph)
      4: 21252 cm/min (≈ 7.92 mph); 14.1 min out + 10.9 min back (@ 10.30 mph)
      5: 20592 cm/min (≈ 7.68 mph); 14.6 min out + 10.4 min back (@ 10.75 mph)

      Note that the speeds on the return leg are not all whole numbers of cm/min.

      Like

  • Jim Randell 9:38 am on 22 September 2022 Permalink | Reply
    Tags:   

    Teaser 2742: Hymns bored 

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

    Peter became bored during the Sunday service, so his mind turned to the three three-figure hymn numbers displayed on the board, all chosen from the five hundred hymns in the hymnal. He noticed that the sum of the digits for each hymn was the same, that one hymn number was the average of the other two, and that no digit appeared more than once on the board.

    What (in increasing order) were the three hymn numbers?

    [teaser2742]

     
    • Jim Randell 9:39 am on 22 September 2022 Permalink | Reply

      We can treat this as an alphametic puzzle, and use the [[ SubstitutedExpression ]] solver from the enigma.py library to solve the puzzle.

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

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      SubstitutedExpression
      
      # if the hymn numbers are: ABC, DEF, GHI
      --answer="(ABC, DEF, GHI)"
      
      # the hymns are in order
      "A < D < G"
      
      # hymns are numbered from 1 to 500
      "G < 5"
      
      # each hymn has the same digit sum
      "all_same(A + B + C, D + E + F, G + H + I)"
      
      # one hymn number is the average of the other two: DEF = (ABC + GHI) / 2
      "div(ABC + GHI, 2) = DEF"
      
      # allow leading zeros
      --invalid=""
      

      Solution: The hymn numbers are: 157, 283, 409.

      Like

    • GeoffR 12:00 pm on 22 September 2022 Permalink | Reply

      
      from itertools import permutations
      
      for p1 in permutations('1234567890', 9):
          A, B, C, D, E, F, G, H, I = p1
          if '0' in (A, D, G):continue
          
          # check digit sums are same for three numbers
          total = int(A) + int(B) + int(C)
          if int(D) + int(E) + int(F) != total:continue
          if int(G) + int(H) + int(I) != total:continue
          
          # find three hymn numbers in increasing order
          ABC, DEF = int(A + B + C), int(D + E + F)
          GHI = int(G + H + I)
          if not ABC < DEF < GHI:continue
          # check all hymn numbers are less than 500
          if not all( x < 500 for x in (ABC, DEF, GHI)):continue
          
          # One hymn number was the average of the other two
          if 2 * DEF == ABC + GHI:
              print(f"Three hymn numbers are {ABC}, {DEF} and {GHI}.")
      
      # Three hymn numbers are 157, 283 and 409.  
      
      

      Like

    • GeoffR 1:23 pm on 22 September 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      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;
      
      constraint all_different([A,B,C,D,E,F,G,H,I]);
      
      var 100..500:ABC = 100*A + 10*B + C;
      var 100..500:DEF = 100*D + 10*E + F;
      var 100..500:GHI = 100*G + 10*H + I;
      
      constraint A + B + C == D + E + F /\ A + B + C == G + H + I;
      constraint ABC < DEF /\ DEF < GHI;
      constraint 2 * DEF == ABC + GHI;
      
      solve satisfy;
      output ["Three hymn numbers were " ++ show(ABC) ++ ", " ++ show(DEF) 
      ++ " and " ++ show(GHI) ];
      
      % Three hymn numbers were 157, 283 and 409
      % ----------
      % ==========
      
      
      

      Like

  • Jim Randell 7:37 am on 20 September 2022 Permalink | Reply
    Tags:   

    Teaser 2744: The school run 

    From The Sunday Times, 26th April 2015 [link] [link]

    Each of the three houses of Merryhouse School entered four students in the cross-country race. Points were awarded with 12 for the winner, 11 for second, and so on down to 1 for the tail-ender (from Berry House). When the points were added up, all houses had equal points. Three of the runners from Cherry House were in consecutive positions, as were just the two middle-performers from Derry House.

    Which house did the winner come from, and what were the individual scores of its runners?

    [teaser2744]

     
    • Jim Randell 7:38 am on 20 September 2022 Permalink | Reply

      There are tri(12) = 78 points awarded, so each of the three houses gets 26 points.

      We can treat this puzzle as an alphametic problem in base 13, distributing values 1-12 to each of the runners, and use the [[ SubstitutedExpression ]] solver from the enigma.py library to solve it.

      When the puzzle says that 3 from team C and 2 from team D are consecutive, I assumed that this implies that exactly the specified number are consecutive for those teams. (For a looser interpretation we can change the != at line 31 to an or).

      The following run file executes in 67ms. (The internal run time of the generated program is 265µs).

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      # suppose points are:
      #
      #       1  2  3  4
      #  B =  E  F  G  H
      #  C =  I  J  K  L
      #  D =  M  N  P  Q
      
      SubstitutedExpression
      
      # allocate points 1-12
      --base=13
      --digits=1-12
      
      # teams are in order
      "E > F > G > H"  # team B
      "I > J > K > L"  # team C
      "M > N > P > Q"  # team D
      
      # team B has the tail-ender
      "H = 1"
      
      # each team had the same number of points (= 26)
      "E + F + G + H == 26"
      "I + J + K + L == 26"
      "M + N + P + Q == 26"
      
      # team C has 3 consecutive points
      "J == K + 1"
      "(I == J + 1) != (K == L + 1)"
      
      # team D has middle 2 consecutive points
      "N == P + 1"
      "M > N + 1"
      "P > Q + 1"
      
      --template=""
      

      Solution: The winner was from Derry House, which was awarded 12 + 6 + 5 + 3 points.

      The points awarded are:

      B: 11 + 10 + 4 + 1 = 26
      C: 9 + 8 + 7 + 2 = 26
      D: 12 + 6 + 5 + 3 = 26

      Like

  • Jim Randell 8:46 am on 15 September 2022 Permalink | Reply
    Tags:   

    Teaser 2735: Two to choose 

    From The Sunday Times, 22nd February 2015 [link] [link]

    I told Sue a two-figure number and I told Terry another two-figure number, one of which was a multiple of the other. I explained this to them but knew that neither of them would be able to work out the other number. (In fact, if they had to guess the other number Sue had three times as many choices as Terry — but I did not tell them that).

    When Sue confirmed that it was impossible for her to work out Terry’s number, he was then able to work out her number.

    What were their numbers?

    [teaser2735]

     
    • Jim Randell 8:47 am on 15 September 2022 Permalink | Reply

      In the following Python program I construct a map from 2-digit numbers to possible “other” numbers (other 2-digit numbers that are proper multiples or divisors of the first number).

      We know (but S and T do not) that neither of them can work out the others number, so we can remove those numbers that only have a single associated number from consideration.

      When S reveals she cannot work out T’s number, T knows that S’s number must be one of these candidates, so the associated numbers for T’s number must include exactly one of the candidate numbers.

      The program runs in 57ms. (Internal run time is 204µs).

      Run: [ @replit ]

      from enigma import (defaultdict, irange, singleton, intersect, printf)
      
      # collect 2-digit numbers that are proper multiples of other 2-digit numbers
      d = defaultdict(list)
      for a in irange(10, 49):
        for b in irange(2 * a, 99, step=a):
          d[a].append(b)
          d[b].append(a)
      
      # candidates that have multiple other numbers
      ks = set(k for (k, vs) in d.items() if len(vs) > 1)
      
      # T can work out S's number (knowing S cannot work out T's)
      for T in ks:
        ss = d[T]
        S = singleton(intersect([ks, ss]))
        if S is not None:
          ts = d[S]
          # in the solution we are looking for S has 3x as many initial choices as T
          if len(ts) == 3 * len(ss):
            printf("S={S} [-> {ts}; T={T} [-> {ss}]")
      

      Solution: Sue’s number was 14. Terry’s number was 98.

      We have: 98 = 7 × 14.

      S has 14, so she knows that T has one of {28, 42, 56, 70, 84, 98}.

      And T has 98, so initially he knows that S has one of {14, 49}. So S has three times as many options for T as T has for S.

      But if S had 49 there are no 2-digit divisors and the only 2-digit multiple is 98, so S would be able to work out T’s value. And as S cannot work out T’s value she cannot have 49, so T can deduce that she must have 14.

      Like

    • Frits 11:18 am on 15 September 2022 Permalink | Reply

         
      # candidates that have multiple other numbers
      cands = {x: lst for x in range(10, 100) 
               if len(lst := [y for y in range(10, 100) 
                              if x != y and not(y % x and x % y)]) > 1}
      
      # Terry must have 2 candidates and Sue 6 for the "three times" requirement
      for terry, sues in cands.items():
        if len(sues) != 2: continue
        # only one of Terry's numbers for Sue must be ambiguous
        if len([sue := s for s in sues if s in cands]) != 1 or \
           len(cands[sue]) != 6: continue
        print("Terry =", terry, "Sue =", sue)
      

      or

         
      # Terry must have 2 candidates and Sue 6 for the "three times" requirement
      
      # candidates that have two or six other numbers
      cands = {x: lst for x in range(10, 100) 
               if len(lst := [y for y in range(10, 100) 
                              if x != y and not(y % x and x % y)]) in {2, 6}}
      
      for sue, terries in cands.items():
        if len(terries) != 6: continue
        # find Terry's where <sue> is one of the two candidates
        for terry, sues in cands.items():
          if len(sues) != 2 or sue not in sues: continue
          # determine other candidate (not <sue>)
          other = [s for s in sues if s != sue][0]
          # <other> may not have multiple other numbers
          if len([x for x in range(10, 100) if x != other and 
                 not(other % x and x % other)]) > 1: continue
          print("Terry =", terry, "Sue =", sue)
      

      Like

  • Jim Randell 10:07 am on 13 September 2022 Permalink | Reply
    Tags:   

    Teaser 2728: Garden design 

    From The Sunday Times, 4th January 2015 [link] [link]

    I have a square garden with sides a whole number of metres in length. It is surrounded by a fence with posts at the corners and then at one metre intervals. I wish to make the garden into four triangular beds surrounding a lawn that has four sides of different lengths. To mark out the lawn I choose one post on each of the sides of the garden and I stretch a length of string around those four posts. I can create my lawn in various ways but the length of string needed is always one of two possible values. I have chosen one arrangement using the smaller of the two lengths.

    What is the area of my lawn?

    [teaser2728]

     
    • Jim Randell 10:08 am on 13 September 2022 Permalink | Reply

      In this program I round results (in metres) to 4 decimal places, to get measurements to with sub-millimetre accuracy, but we don’t have to worry about floating point inaccuracies when we compare measurements.

      This Python program runs in 53ms. (Internal run time is 1.0ms).

      Run: [ @replit ]

      from enigma import (defaultdict, subsets, irange, hypot, seq_all_different, tuples, printf)
      
      # measure float quantities to 4dp
      measure = lambda x: round(x, 4)
      
      # look for solutions with an <n> by <n> square
      def solve(n, k=None):
        # choose the positions of the posts
        d = defaultdict(set)
        for ps in subsets(irange(1, n - 1), size=4, select="M"):
          # perpendicular dimensions of corner triangles
          ts = list((x, n - y) for (x, y) in tuples(ps, 2, circular=1))
          # calculate the sides of the central quadrilateral
          ds = list(hypot(x, y) for (x, y) in ts)
          # they should all be different lengths
          if not seq_all_different(ds, fn=measure): continue
          # calculate the total length of string required
          s = measure(sum(ds))
          # caclulate area
          a = measure(n * n - 0.5 * sum(x * y for (x, y) in ts))
          d[s].add(a)
          # early termination?
          if k and len(d.keys()) > k: return
        if k and len(d.keys()) != k: return
        return d
      
      # consider an n x n garden
      for n in irange(3, 100):
        d = solve(n, 2)
        if d is None: continue
        # output solution (= shortest length string)
        s = min(d.keys())
        for a in sorted(d[s]):
          printf("n={n}: lawn area = {a:.3f} m^2 [string length = {s:.3f} m]")
        break  # we only need the first garden
      

      Solution: The area of the lawn is: 7 square metres.

      Considering gardens where the central lawn area has sides of 4 different lengths.

      For a garden less than 4m × 4m this is not possible.

      For a 4m × 4m garden there are essentially 2 different layouts (where the central area has sides of different length):

      The lengths of string, and area of lawn are:

      A: string = √5 + √10 + √13 + √ 8 ≈ 11.832 m; lawn = 8.5 m²
      B: string = √2 + √13 + √5 + √18 ≈ 11.498 m; lawn = 7.0 m²

      So layout B uses the least amount of string, and so gives the answer to the puzzle.

      For a garden greater than 4m × 4m there are more than 2 different layouts, which give more than 2 different lengths of string.

      Like

  • Jim Randell 9:19 am on 8 September 2022 Permalink | Reply
    Tags:   

    Teaser 2740: X times 

    From The Sunday Times, 29th March 2015 [link] [link]

    In this long multiplication sum, I am multiplying a three-figure number by itself. Throughout the workings one particular digit has been replaced by X wherever it occurs: all other digits have been replaced by a question mark.

    What is the three-figure number being squared?

    [teaser2740]

     
    • Jim Randell 9:21 am on 8 September 2022 Permalink | Reply

      The intermediate multiplications are presented in the opposite order to how I was taught long multiplication. But from the layout of the columns the intent is obvious.

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

      The following run file executes in 73ms. (Internal runtime of the generated program is 277µs).

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      #       a X b
      #  *    a X b
      #   ---------
      #   c d e      = aXb * a
      #   f g X X    = aXb * X
      #     h i j k  = aXb * b
      #   ---------
      #   X m n p q  = aXb * aXb
      
      SubstitutedExpression
      
      # added symbols are different from X
      --distinct="aX,bX,cX,dX,eX,fX,gX,hX,iX,jX,kX,mX,nX,pX,qX"
      
      # the multiplication sum
      "{aXb} * {aXb} = {Xmnpq}"
      "{aXb} * {a} = {cde}"
      "{aXb} * {X} = {fgXX}"
      "{aXb} * {b} = {hijk}"
      
      --answer="{aXb}"
      

      Solution: The number being squared is: 286.

      Note that you will find the answer even if you don’t check that the question marks are all different from X (we can use [[ --distinct="" ]] to show this), but without this check the solution is not complete.

      Like

    • GeoffR 1:33 pm on 8 September 2022 Permalink | Reply

      
      from itertools import permutations
      
      for p1 in permutations('1234567890', 3):
          a, X, b = p1
          aXb = int(a + X + b)
          # check leading digit of multiplication result
          res = aXb * aXb
          if res //10000 != int(X):continue
          
          # 1st line of multiplication
          line1 = int(a) * aXb * 100
          if X in set(str(line1)):continue
          
          # 2nd line of multiplication
          line2 = int(X) * aXb * 10
          if line2 //100 % 10 != int(X):continue
          if line2//10 % 10 != int(X):continue
          
          # 3rd line of multiplication
          line3 = int(b) * aXb
          if X in set(str(line3)):continue
          print(f"The three-figure number being squared is {aXb}.")
      
      # The three-figure number being squared is 286.
      
      
      

      Like

  • Jim Randell 8:50 am on 6 September 2022 Permalink | Reply
    Tags:   

    Teaser 2752: Best before 

    From The Sunday Times, 21st June 2015 [link] [link]

    Peter likes to note “pandigital” times, such as 15.46, 29/03/78, that use all ten digits. Here the five individual numbers (15, 46, 29, 3 and 78) have a product that is divisible by the perfect square 36, and they also have a sum that is two more than a perfect square.

    He has been watching for pandigital times ever since and remembers one where the product of the five numbers was not divisible by any perfect square (apart from 1): this has never happened since! He is also looking out for a pandigital time where the sum of the five numbers is a perfect square:

    (a) When was that last “non-square” pandigital time?
    (b) When will that first “square-sum” pandigital time be?

    [teaser2752]

     
    • Jim Randell 8:51 am on 6 September 2022 Permalink | Reply

      We generate possible pandigital times, and then check to find the last (before the puzzle was set) with a square-free product, and the first (after the puzzle was set) with a perfect square sum.

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

      Run: [ @replit ]

      from datetime import datetime
      from enigma import (
        irange, choose, implies, nconcat, catch,
        multiply, is_square_free, is_square, printf
      )
      
      # find pandigital (y, m, d, H, M) values
      def generate():
        # possible digits
        digits = set(irange(0, 9))
      
        # selection functions
        fns = [
          # month first digit; is 0-1
          lambda m1: m1 < 2,
          # hour first digit; is 0-2
          lambda m1, H1: H1 < 3,
          # day first digit; is 0-3
          lambda m1, H1, d1: d1 < 4,
          # minute first digit; is 0-5
          lambda m1, H1, d1, M1: M1 < 6,
          # month second digit; m2 = 1 -> 0, 2
          lambda m1, H1, d1, M1, m2: implies(m1 == 1, m2 < 3),
          # hour second digit; H1 = 2 -> 0, 1, 2
          lambda m1, H1, d1, M1, m2, H2: implies(H1 == 2, H2 < 3),
          # day second digit; d1 = 3 -> 0, 1
          lambda m1, H1, d1, M1, m2, H2, d2: implies(d1 == 3, d2 < 2),
          # remaining digits (M2, y1, y2) = any
          None, None, None
        ]
      
        # assign digits
        for (m1, H1, d1, M1, m2, H2, d2, M2, y1, y2) in choose(digits, fns, distinct=1):
          (y, m, d, H, M) = (nconcat(*x) for x in [(y1, y2), (m1, m2), (d1, d2), (H1, H2), (M1, M2)])
          y += (2000 if y < 78 else 1900)
          t = catch(datetime, y, m, d, H, M)
          if t is not None:
            yield (t, (H, M, d, m, y % 100))
      
      # date of the puzzle
      now = datetime(2015, 6, 21)
      
      # record answers
      a = b = None
      
      # look for pandigital times
      for (t, ns) in generate():
        # (a) latest time before now with a square-free product
        if t < now and (a is None or t > a) and is_square_free(multiply(ns)):
          a = t
        # (b) earliest time after now with a perfect square sum
        if t > now and (b is None or t < b) and is_square(sum(ns)):
          b = t
      
      # output solution
      fmt = lambda t: t.strftime("%H:%M, %d/%m/%y")
      printf("(a) {a}", a=fmt(a))
      printf("(b) {b}", b=fmt(b))
      

      Solution: (a) 15:43, 26/07/89; (b) 18:59, 27/06/34.

      Like

    • Frits 6:06 pm on 6 September 2022 Permalink | Reply

      Two programs:

         
      from itertools import permutations
      
      # small numbers which are square free
      nums = [n for n in range(1, 32) if n % 11 and all(n % y for y in [4, 9, 25])]
      days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      mx_dt = 0
      
      # check day/month/hour permutations
      for d, m, h in permutations(nums, 3):
        if m > 12 or h > 24: continue
        
        s = "".join(str(x).zfill(2) for x in (d, m, h))
        if len(set(s)) != 6: continue          # different digits
        
        p1 = d * m * h                         # product
        # not divisible by any perfect square
        if not all(p1 % (n * n) 
          for n in [2] + list(range(3, int(p1**.5) + 1, 2))): continue
        
        # check day number
        if d > 28 and m == 2:
          if year % 4 == 0 and (year % 100 or year % 400 == 0):
            days[1] = 29
          else:
            days[1] = 28
        if d > days[m - 1]:  continue  
        
        rest = [int(x) for x in range(9, -1, -1) if str(x) not in s]
        mdh = str(m).zfill(2) + str(d).zfill(2) + str(h).zfill(2)
        
        # check year/minutes permutations
        for p in permutations(rest):
          if p[2] > 5: continue                # minutes < 60
          
          year = 10 * p[0] + p[1]
          if 15 < year < 78: continue          # year between 1978 and 2015
          mins = 10 * p[2] + p[3]
          
          # built date string
          dt = int(("19" if year > 77 else "20") + str(year) +  mdh + str(mins))
                   
          if dt < mx_dt: continue              # skip if earlier year than maximum
          
          # not divisible by any perfect square for new numbers ...
          p2 = mins * year
          if not all(p2 % (n * n) 
                     for n in [2] + list(range(3, int(p2**.5) + 1, 2))): continue
          # ... and for all five 2-digit numbers 
          p2 *= p1 
          if not all(p2 % (n * n) 
                     for n in [2] + list(range(3, int(p2**.5) + 1, 2))): continue
         
          mx_dt = dt                           # new maximum
          break                                # as p is decreasing
          
      s = str(mx_dt) 
      print(f"last 'non-square' pandigital time: "
            f"{s[6:8]}/{s[4:6]}/{s[:4]} {s[8:10]}:{s[10:]}") 
      

      and

        
      days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      
      # pick one value from each entry of a <k>-dimensional list <ns>
      # so that all digits in the <k> values are different
      def pickOneFromEach(k, ns, s=[], dgts=set()):
        if k == 0:
          yield s
        else:
          for n in ns[k - 1]:
            sn = str(n).zfill(2)
            if all(x not in dgts for x in sn):
              yield from pickOneFromEach(k - 1, ns, s + [sn], dgts | set(sn))
      
      # months, days and hours have to use the digits 0, 1 and 2 
      # months 10 and 12 are invalid as they use two of the digits 0, 1 and 2
      # and leave no room for day and hour so month < 10
      ys = list(n for n in range(34, 100) 
                if n % 11 and all(y not in str(n) for y in "012"))
      ms = list(range(1, 10))
      ds = list(n for n in range(13, 32) if n % 11 and n % 10)
      hs = list(n for n in range(24) if n % 11 and n % 10)
      mis = list(n for n in range(34, 60) 
                 if n % 11 and all(y not in str(n) for y in "012"))
      
      rev_lst = [mis, hs, ds, ms, ys]
      
      for p in pickOneFromEach(5, rev_lst):
        s = "".join(p)
        # sum has to be a perfect square
        if sum([int(x) for x in p]) not in {144, 169, 196}: continue
        
        # check day number
        m, d = (int(p[1]), int(p[2]))
        if d > 28 and m == 2:
          if year % 4 == 0 and (year % 100 or year % 400 == 0):
            days[1] = 29
          else:
            days[1] = 28
        if d > days[m - 1]:  continue  
        
        print(f" first 'square-sum' pandigital time: "
              f"{p[2]}/{p[1]}/{p[0]} {p[3]}:{p[4]}")  
        break       # we have found the first date 
      

      Like

  • Jim Randell 9:23 am on 1 September 2022 Permalink | Reply
    Tags:   

    Teaser 2732: Prime meat 

    From The Sunday Times, 1st February 2015 [link] [link]

    Mark has recently converted from vegetarianism. John sent him a coded message consisting of a list of prime numbers. Mark found that by systematically replacing each digit by a letter the list became the message:

    EAT BEEF AT TIMES
    IT IS A PRIME MEAT

    What number became PRIME?

    [teaser2732]

     
    • Jim Randell 9:29 am on 1 September 2022 Permalink | Reply

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

      The following Python program executes in 58ms. (Internal runtime of the generated program is 1.8ms).

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, sprintf)
      
      words = "EAT BEEF AT TIMES IT IS A PRIME MEAT"
      
      SubstitutedExpression(
        list(sprintf("is_prime({w})") for w in words.split()),
        answer="PRIME",
        template=words,
      ).run()
      

      Solution: PRIME = 80429.

      Like

    • GeoffR 11:53 am on 1 September 2022 Permalink | Reply

      This solution gets the answer, but is a bit slow – approx 1.5 sec.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      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 0..9:E; var 0..9:A; var 0..9:T; var 0..9:B;
      var 0..9:F; var 0..9:I; var 0..9:M; var 0..9:S;
      var 0..9:P; var 0..9:R;
      
      constraint E > 0 /\ B > 0 /\I > 0 /\ P > 0
      /\ T > 0 /\ A > 0 /\ M > 0;
      
      constraint all_different ([E, A, T, B, F, I, M, S, P, R]);
      
      var 100..999:EAT = 100*E + 10*A + T;
      var 1000..9999:BEEF = 1000*B + 110*E + F;
      var 10..99:AT = 10*A + T;
      var 10..99:IS = 10*I + S;
      var 10..99:IT = 10*I + T;
      var 10000..99999:TIMES = 10000*T + 1000*I + 100*M + 10*E + S;
      var 10000..99999:PRIME = 10000*P + 1000*R + 100*I + 10*M + E;
      var 1000..9999:MEAT = 1000*M + 100*E + 10*A + T;
      
      constraint sum([is_prime(A), is_prime(EAT), is_prime(BEEF),
      is_prime(AT), is_prime(TIMES), is_prime(IT), is_prime(IS),
      is_prime(PRIME), is_prime(MEAT)]) == 9;
      
      solve satisfy;
      output ["PRIME = " ++ show(PRIME) ++
      "\n" ++ "[E, A, T, B, F, I, M, S, P, R] = " 
      ++ show( [E, A, T, B, F, I, M, S, P, R] ) ];
      
      % PRIME = 80429
      % ----------
      %  E, A, T, B, F, I, M, S, P, R] = 
      % [9, 5, 3, 6, 1, 4, 2, 7, 8, 0]
      % ==========
      

      Like

    • GeoffR 1:56 pm on 1 September 2022 Permalink | Reply

      from itertools import permutations
      from enigma import is_prime
      
      for p1 in permutations('1234567890', 5):
          A, E, I, T, S = p1
          if '0' in (A, E, I, T):continue
          if not(is_prime(int(A))):continue
          AT = int(A + T)
          if not (is_prime(AT)):continue
          IS = int(I + S)
          if not (is_prime(IS)):continue
          IT = int(I + T)
          if not (is_prime(IT)):continue
          EAT = int(E + A + T)
          if not (is_prime(EAT)):continue
          q = set('1234567890').difference([A, E, I, T, S])
          for p2 in permutations(q):
              B, F, M, P, R = p2
              if '0' in (B, M, P):continue
              BEEF = int(B + E + E + F)
              if not (is_prime(BEEF)):continue
              TIMES = int(T + I + M + E + S)
              if not (is_prime(TIMES)):continue
              MEAT = int(M + E + A + T)
              if not (is_prime(MEAT)):continue
              PRIME = int( P + R + I + M + E)
              if is_prime(PRIME):
                  print(f"PRIME = {PRIME}")
      
      # PRIME = 80429
      
      
      
      

      Like

  • Jim Randell 9:31 am on 30 August 2022 Permalink | Reply
    Tags:   

    Teaser 2743: Line-up 

    From The Sunday Times, 19th April 2015 [link] [link]

    I have arranged the numbers from 1 to 27 in a 3-by-3-by-3 cubical array. I have noticed that the nine numbers making up one of the faces of the cube are all primes.

    Also, I have searched through the array and written down the sum of any three numbers that are in a straight line. I have then calculated the grand total of all those line-sums. It turns out that the grand total is itself a perfect cube!

    What is that grand total?

    [teaser2743]

     
    • Jim Randell 9:32 am on 30 August 2022 Permalink | Reply

      Considering a standard 3×3×3 Rubik’s cube, we can think of is as consisting of 27 cubelets:

      8 corner pieces (with 3 colours)
      12 edge pieces (with 2 colours)
      6 middle pieces (with 1 colour)
      1 core piece (with 0 colours)

      And if we consider the number of lines each type of cubelet appears in we have:

      corner = 7 lines
      edge = 4 lines
      middle = 5 lines
      core = 13 lines

      The grand total then consists of:

      7× (8 corner pieces)
      4× (12 edge pieces)
      5× (6 centre pieces )
      13× (1 core piece)

      And as long as we distribute the numbers amongst the same type of piece any arrangement will have the same grand total. (Although to confirm with the layout specified in the puzzle you have to keep the primes altogether on one face).

      There are only nine primes between 1 and 27 (= 2, 3, 5, 7, 11, 13, 17, 19, 23) and these are arranged on one of the faces, so we have 4 corners, 4 edges, and 1 centre that prime.

      So we just need to split the primes into sets of size (4, 4, 1) with sums (p1, p2, p3), and the remaining 18 non-primes into sets of size (4, 8, 5, 1) with sums (n1, n2, n3, n4), such that:

      T = 7(p1 + n1) + 4(p2 + n2) + 5(p3 + n3) + 13 n4 [= a perfect cube]

      This can be written as:

      T = 4(p1 + p2 + p3 + n1 + n2 + n3 + n4) + 3(p1 + n1) + (p3 + n3) + 9 n4

      and we know that: (p1 + p2 + p3 + n1 + n2 + n3 + n4) = tri(27), so:

      T = 4 tri(27) + 3(p1 + n1) + (p3 + n3) + 9 n4

      So the maximum possible value for T is 2363, giving a maximum possible cube of 13³ = 2197.

      And the minimum possible value for T is 1731, giving a minimum possible cube of 13³ = 2197.

      So the only possibility for a grand total that is a cube is 13³ = 2197.

      Solution: The grand total is 2197.


      But now we need to show there is at least one solution.

      If we do find a solution we can permute the numbers amongst their own groups without changing the grant total to give a class with (4! × 4! × 1!) × (4! × 8! × 5! × 1!) = 66886041600 solutions. (Which we can divide by 8 to account for reflections/rotations).

      This Python program chooses a non-prime core value, and partitions the primes into sets of size (4, 1) and the remaining non-primes into sets of size (4, 5), and in each case the contribution to the grand total is recorded. We then look for pairs of contributions that can make a grand total that is a perfect cube. (There are many of these, so we stop when we have found a single example for a particular cube).

      It runs in 13s and finds there are many possible core values that lead to viable solutions (and each of these has many partitions sums that lead to many solutions).

      Run: [ @replit ]

      from enigma import (irange, primes, subsets, intc, intf, cbrt, cb, printf)
      
      # partition set <s> into subsets with sizes in <ns>
      def partition(s, ns, ss=[]):
        if not ns:
          yield ss
        elif len(ns) == 1:
          for x in subsets(s, size=ns[0]):
            yield ss + [x]
        else:
          for x in subsets(s, size=ns[0]):
            yield from partition(s.difference(x), ns[1:], ss + [x])
      
      # primes
      ps = set(primes.between(1, 27))
      
      # non-primes
      nps = set(irange(1, 27)).difference(ps)
      
      # total of primes and non-primes (= tri(27))
      t = sum(ps) + sum(nps)
      
      # record grand totals
      Ts = set()
      
      # split the primes into sets of size (4, 1) and record the contribution to the total
      pss = set(3 * sum(p1) + sum(p3) for (p1, p3) in partition(ps, (4, 1)))
      printf("[{n} prime partitions]", n=len(pss))
      
      # choose the core value (non-prime)
      for v in nps:
        printf("[core={v}]")
      
        # split the remaining non-primes into sets of size (4, 5) and record the contribution to the total
        nss = set(3 * sum(n1) + sum(n3) for (n1, n3) in partition(nps.difference({v}), (4, 5)))
        printf("[{n} non-prime partitions]", n=len(nss))
      
        # find possible cubes
        t0 = 4 * t + 9 * v
        cubes = set(cb(x) for x in irange(intc(cbrt(t0 + min(pss) + min(nss))), intf(cbrt(t0 + max(pss) + max(nss)))))
        printf("[possible cubes = {cubes}]", cubes=sorted(cubes))
      
        # find pairs from pss and nss that give a cube grand total
        for T in cubes:
          for p in pss:
            n = T - t0 - p
            if n > 0 and n in nss:
              printf("[p={p} n={n} -> T={T}]")
              Ts.add(T)
              break  # we only need one example for this T
      
      # output solution
      printf("grand total = {Ts}")
      

      There are many, many ways to place the numbers on the cube.

      Here is one (this has 8 as the core value – the smallest possible, but it can be any non-prime value between 8 and 27):

      In this solution the different types of cubelet are (prime + non-prime values):

      8 corners = (7, 17, 19, 23) + (24, 25, 26, 27); sum = 168
      12 edges = (2, 3, 5, 11) + (1, 4, 6, 9, 10, 12, 14, 16); sum = 93
      6 middles = (13) + (15, 18, 20, 21, 22); sum = 109
      1 core = () + (8); sum = 8

      And so the grand total is:

      7×168 + 4×93 + 5×109 + 13×8 = 2197 = 13³

      Like

  • Jim Randell 10:45 am on 25 August 2022 Permalink | Reply
    Tags:   

    Teaser 2745: Square cut 

    From The Sunday Times, 3rd May 2015 [link] [link]

    Jorkens, the wily old cricketer, is faced with a new type of square cut. His house has three square bedrooms, all of different sizes. He has just bought a new carpet for the largest bedroom and has cut up its old carpet into four rectangular pieces, the smallest of which has an area of four square metres. He is able to use the four pieces to carpet the other two bedrooms exactly.

    What is the area of the largest bedroom?

    [teaser2745]

     
    • Jim Randell 10:46 am on 25 August 2022 Permalink | Reply

      Suppose the floors of the rooms have sides a, b, c (smallest to largest).

      Then, if the carpet from the largest bedroom can be used to exactly carpet the other 2 bedrooms we have:

      c² = a² + b²

      So, (a, b, c) form a right-angled triangle (but this is not necessarily a Pythagorean triple, as we are not told that the sides of the rooms take on integer values).

      Or:

      b² = c² − a²
      ⇒ b² = (c − a)(c + a)

      Each side of the larger square must be reduced (otherwise we have a rectangle with side c that won’t fit in the smaller bedrooms).

      Assuming the carpet is cut into exactly 4 rectangular regions (which may be square), we must have cuts like this:

      (i.e. one cut must go across the entire length of the square, and the rectangles produced from this cut must both have cuts perpendicular to this).

      I supposed we cut off an a × a square for the small room, which leaves three rectangles remaining to assemble into a square for the middle room.

      This gives us 3 remaining pieces of size (for some value d):

      a × (c − a)
      (c − a) × d
      (c − a) × (c − d)

      And in order to be assembled into a square of side b one of the pieces must have a dimension of b.

      So either: b = (c − a) or b = d.

      If b = (c − a) we infer that b = c, which is not possible, so the 3 remaining pieces are:

      a × (c − a)
      (c − a) × b
      (c − a) × (c − b)

      Which fit together like this:

      From which we see (vertical edges):

      b = a + (c − b)
      ⇒ 2b = a + c
      ⇒ b = (a + c) / 2

      i.e. b is the mean of a and c.

      So we write:

      b = a + x
      c = a + 2x

      To give the following diagram:

      From which we see (horizontal edges):

      a + x = 4x
      ⇒ a = 3x

      So:

      a = 3x
      b = 4x
      c = 5x

      (So (a, b, c) is a Pythagorean triple, if we measure it in units of x).

      And the pieces and their areas are:

      a × a = 9x²
      a × (c − a) = 6x²
      b × (c − a) = 8x²
      (c − b) × (c − a) = 2x²

      And the smallest of these (2x²) has an area of 4 square metres hence x² = 2.

      And we want the area of the largest bedroom (c²).

      c² = (5x)²
      ⇒ c² = 25x²
      ⇒ c² = 50 square metres

      Solution: The floor area of the largest bedroom is 50 square metres.


      And we can calculate the floor areas of the other rooms as well:

      small = 18 square metres (a 3√2 metre square)
      middle = 32 square metres (a 4√2 metre square)
      large = 50 square metres (a 5√2 metre square)

      And 18 + 32 = 50.

      The 50 square metre carpet from the largest room is cut into the following rectangles:

      √2 × 2√2 (≈ 1.41 × 2.83) = 4 square metres (b.1)
      2√2 × 3√2 (≈ 2.83 × 4.24) = 12 square metres (b.2)
      2√2 × 4√2 (≈ 2.83 × 5.66) = 16 square metres (b.3)
      3√2 × 3√2 (≈ 4.24 × 4.24) = 18 square metres (a)

      Or with each square having an area of 2 square metres (i.e. of side √2 metres):

      Like

  • Jim Randell 10:41 am on 23 August 2022 Permalink | Reply
    Tags: ,   

    Teaser 2738: Prime day for the Irish 

    From The Sunday Times, 15th March 2015 [link] [link]

    St Patrick’s Day is March 17 and it is a prime day in many ways:

    What number month? 3;
    What number day? 17;
    How many letters in “March”? 5;
    How many days in March? 31.

    I asked Pat the same questions about his birthday this year — but I simply asked whether the four answers were prime or not. When he had told me he said: “Now, if I told you its day of the week this year, you should be able to work out my birthday”.

    Then, without me actually being told the day, I was indeed able to work out his birthday.

    What is his birthday?

    [teaser2738]

     
    • Jim Randell 10:42 am on 23 August 2022 Permalink | Reply

      This Python program runs in 57ms. (Internal run time is 1.7ms).

      Run: [ @replit ]

      from datetime import (date, timedelta)
      from enigma import (repeat, is_prime, filter_unique, unpack, printf)
      
      # number of letters in each month (English names)
      months = (
        "january", "february", "march", "april", "may", "june", "july",
        "august", "september", "october", "november", "december",
      )
      mlen = dict((k, len(x)) for (k, x) in enumerate(months, start=1))
      
      # number of days in each month
      mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      
      # generate dates in 2015, return (<date>, <answers-to-questions>)
      def generate():
        inc = lambda x, i=timedelta(days=1): x + i
        # consider consecutive days in 2015
        for d in repeat(inc, date(2015, 1, 1)):
          if d.year > 2015: break
          # are the following prime?
          qs = tuple(is_prime(x) for x in (
            # 1. the month number
            d.month,
            # 2. the day number
            d.day,
            # 3. the number of letters in the month
            mlen[d.month],
            # 4. the number of days in the month
            mdays[d.month],
          ))
          yield (d, qs)
      
      # candidate dates
      rs = generate()
      # selection functions
      fn_d = unpack(lambda d, qs: d)
      fn_qs = unpack(lambda d, qs: qs)
      fn_qs_dow = unpack(lambda d, qs: (qs, d.isoweekday()))
      
      # if we knew the answers to the questions _and_ the day of the week
      # then we could work out the date
      rs = filter_unique(rs, fn_qs_dow, fn_d).unique
      
      # now we can work out the date only knowing the answers to the questions
      rs = filter_unique(rs, fn_qs, fn_d).unique
      
      # output solutions
      for (d, qs) in rs:
        printf("date = {d} [qs={qs}]", d=d.strftime('%A, %d %B %Y'))
      

      Solution: Pat’s birthday is on 29th November.


      There are 9 dates that can be identified as unique knowing the answers to the questions and the day of the week (in 2015).

      If the answers to the questions are: (not prime, prime, prime, not prime) we have:

      Mon ⇒ 13th April 2015
      Tue ⇒ 7th April 2015
      Wed ⇒ 29th April 2015
      Sat ⇒ 11th April 2015

      If the answers to the questions are: (prime, prime, not prime, prime) we have:

      Mon ⇒ 13th July 2015
      Tue ⇒ 7th July 2015
      Wed ⇒ 29th July 2015
      Sat ⇒ 11th July 2015

      And if the answers to the questions are: (prime, prime, not prime, not prime) we have:

      Sun ⇒ 29th November 2015

      As the setter does not need to be told the day of the week to find the birthday from these 9 he must have been told (prime, prime, not prime, not prime) so the candidate dates are narrowed down to a single possibility.

      Like

    • Frits 2:48 pm on 23 August 2022 Permalink | Reply

      partition_unique has recently been updated.

         
      from calendar import month_name, monthrange, day_name, weekday
      
      # https://brg.me.uk/?page_id=4800
      from partition_unique import partition_unique
      
      # primes up to 365
      P = {3, 5, 7, 11, 13, 17, 19}
      P |= {2} | {x for x in range(23, 366, 2) if all(x % p for p in P)}
      
      year = 2015
      sols = []
      # generate dates in 2015
      # store (<answers-to-questions>, <day name>, <(month, day)>)
      for month in range(1, 13):
        month_length = monthrange(year, month)[1]
        len_month_name = len(month_name[month])
        for d in range(1, month_length + 1):
          # find the day of the week
          d_name = day_name[weekday(year, month, d)]
          
          sols.append(((# 1. the month number
                       month in P, 
                       # 2. the day number
                       d in P, 
                       # 3. the number of letters in the month
                       len_month_name in P,
                       # 4. the number of days in the month
                       month_length in P),
                       d_name, (month, d) 
                     ))   
          
      f_answs = lambda answs, nm, md: answs
      f_answs_nm = lambda answs, nm, md: (answs, nm)
      f_md = lambda answs, nm, md: md
      
      # find those solutions for which the answers plus day of the week
      # lead to the date
      sols = partition_unique(sols, f_answs_nm, f_md)[0]
      
      # find those solutions for which the answers lead to the date
      sols = partition_unique(sols, f_answs, f_md)[0]
      
      for _, _, md in sols:
        print(f"answer: {month_name[md[0]]} {md[1]}")
      

      Like

    • NigelR 5:19 pm on 25 August 2022 Permalink | Reply

      Not, I suspect, very ‘pythonic’ but seems to run ok. enigma timer gives 3.2ms:

      prim=[2,3,5,7,11,13,17,19,23,29,31]
      mths=["january", "february", "march", "april", "may", "june", "july",
        "august", "september", "october", "november", "december"]
      days=[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      dow=['We','Th','Fr','Sa','Su','Mo','Tu']  # 1 Jan 2015 fell on a Thursday
      #i = month (0-11),j = day of month.  Returns string of day of week (Mo-Su)
      day = lambda i,j: dow[(j+sum([days[k] for k in range(i)]))%7]
      #generate answers to questions + day of week for each day in format 'n/p n/p n/p n/p dd'
      ans=[[''.join(['p' if x+1 in prim else 'n','p' if y in prim else 'n', 'p' if len(mths[x]) in prim else 'n',
             'p'if days[x] in prim else 'n',day(x,y)]),x,y] for x in range(12) for y in range (1,days[x]+1)]
      #create dictionary with count of each occurrence of 'n/p n/p n/p n/p dd'
      cand={x[0]:[y[0] for y in ans].count(x[0]) for x in ans }
      #remove non-unique entries
      lst=[x for x in cand.keys() if cand[x] == 1] 
      #identify whether there is a unique day of week entry:
      result = [x for x in lst if [y[4:] for y in lst].count(x[4:])==1]
      if len(result)>1:
          print('No unique solution found')
          exit()
      print([['Birthday is' + ' '+str( x[2]) + ' '+ str(mths[x[1]+1].capitalize())] for x in ans if x[0]==result[0]][0][0])
      

      Like

  • Jim Randell 9:07 am on 18 August 2022 Permalink | Reply
    Tags:   

    Teaser 2729: Factorial fact 

    From The Sunday Times, 11th January 2015 [link] [link]

    The “factorials” of numbers (denoted by !) are defined by:

    1! = 1
    2! = 2 × 1
    3! = 3 × 2 × 1
    4! = 4 × 3 × 2 × 1
    etc.

    It is possible to take eleven of the twelve factorials 1!, 2!, 3!, 4!, 5!, 6!, 7!, 8!, 9!, 10!, 11!, 12! and to split them into groups of three, four and four, so that in each group the product of the factorials in that group is a perfect square.

    What are the factorials in the group whose product is the smallest?

    [teaser2729]

     
    • Jim Randell 9:07 am on 18 August 2022 Permalink | Reply

      This Python program runs in 56ms. (Internal run time is 1.68ms).

      Run: [ @replit ]

      from enigma import (
        irange, subsets, factorial, multiply, is_square_p,
        seq_all_different as all_different, printf
      )
      
      # measure function
      fn = lambda ns: multiply(factorial(n) for n in ns)
      
      # find groups of k-tuples with a measure that is a square
      def generate(k):
        for ns in subsets(irange(1, 12), size=k):
          if is_square_p(fn(ns)):
            yield ns
      
      # collect 3-, 4-tuples
      n3s = list(generate(3))
      n4s = list(generate(4))
      
      # choose two disjoint 4-tuples
      for (g1, g2) in subsets(n4s, size=2):
        if not all_different(g1 + g2): continue
        # and find a disjoint 3-tuple
        for g3 in n3s:
          if not all_different(g1 + g2 + g3): continue
          # find the group with the minimal measure
          g = min(g1, g2, g3, key=fn)
          printf("min = {g} [{g3} {g1} {g2}]")
      

      Solution: The factorials in the group with the smallest product are: 1!, 8!, 9!.

      There are 2 ways to form the groups:

      (1, 8, 9) (2, 3, 11, 12) (4, 5, 7, 10)
      (1, 8, 9) (2, 4, 11, 12) (3, 5, 7, 10)

      Like

    • GeoffR 2:38 pm on 18 August 2022 Permalink | Reply

      A brute force solution produced products of 11, 14 and 18 digits long for groups of 3, 4 and 4 factorials. The solution ran in approximately 20 sec.

      The smallest solution was repeated if the break statement is excluded.

      from itertools import permutations
      from enigma import factorial as fact
      from enigma import is_square as is_sq
      
      # Precalculate the twelve factorials
      F1,F2, F3 = fact(1), fact(2), fact(3)
      F4, F5, F6 = fact(4), fact(5), fact(6)
      F7, F8, F9 = fact(7), fact(8), fact(9)
      F10, F11, F12 = fact(10), fact(11), fact(12)
      
      # Factorial dictionary for output
      ND = {F1: 'F1', F2:'F2', F3:'F3', F4:'F4', F5:'F5',F6:'F6',
            F7:'F7', F8:'F8', F9:'F9', F10:'F10', F11:'F11', F12:'F12' }
            
      for p1 in permutations((F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12),11):
        a, b, c, d, e, f, g, h, i, j, k = p1
        # split factorials into groups of three, four and four
        if not (is_sq(a * b * c)): continue
        if not (is_sq(d * e * f * g)): continue
        if not( is_sq(h * i * j * k)):continue
        # sort factorial products
        T = sorted((a * b * c , d * e * f * g, h * i * j * k))
        if a * b * c < d * e * f * g and a * b * c < h * i * j * k:
          print(f"Factorials in smallest product are {ND[a]}, {ND[b]} and {ND[c]}.")
          print(f"Smallest factorial product = {a * b * c}")
          break
      
      # Factorials in smallest product are F1, F8 and F9.
      # Smallest factorial product = 14631321600
      
      
      
      

      Like

      • GeoffR 9:17 am on 19 August 2022 Permalink | Reply

        A three stage permutation seemed more compatible with a (3,4,4) group of numbers. This solution was considerably faster than my previous posting, running in 4.64ms.

        from itertools import permutations
        from enigma import factorial as fact, is_square as is_sq, timer
        
        timer.start()
        # Precalculate the twelve factorials
        F1,F2, F3 = fact(1), fact(2), fact(3)
        F4, F5, F6 = fact(4), fact(5), fact(6)
        F7, F8, F9 = fact(7), fact(8), fact(9)
        F10, F11, F12 = fact(10), fact(11), fact(12)
        
        factorials = {F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12} 
        
        # Factorial dictionary for output
        ND = {F1: 'F1', F2:'F2', F3:'F3', F4:'F4', F5:'F5',F6:'F6',
              F7:'F7', F8:'F8', F9:'F9', F10:'F10', F11:'F11', F12:'F12' }
        
        # first stage permutation
        # split factorials into groups of three, four and four
        for p1 in permutations(factorials, 3):
          a, b, c = p1
          if not (is_sq(a * b * c)):
            continue
        
          # second stage permutation
          q1 = factorials.difference({a, b, c})
          for p2 in permutations(q1, 4):
            d, e, f, g = p2
            if not (is_sq(d * e * f * g)):
              continue
        
            # third stage permutation
            q2 = q1.difference({d, e, f, g})
            for p3 in permutations(q2, 4):
              h, i, j, k = p3
              if not( is_sq(h * i * j * k)):
                continue
              
              # sort factorial products
              T = sorted((a * b * c , d * e * f * g, h * i * j * k))
              if (a * b * c) < (d * e * f * g) and (a * b * c) < (h * i * j * k):
                print(f"Factorials in smallest product are {ND[a]}, {ND[b]} and {ND[c]}.")
                print(f"Smallest factorial product = {a * b * c}")
                timer.stop()
                timer.report()
                # only one solution needed
                exit()
        
        # Factorials in smallest product are F8, F1 and F9.
        # Smallest factorial product = 14631321600
        # [timing] total time: 0.0046427s (4.64ms)
        
        
        
        
        

        Like

    • NigelR 11:33 pm on 18 August 2022 Permalink | Reply

      [timing] total time: 0.0030880s (3.09ms)

      from itertools import combinations as comb
      from enigma import timer
      timer.start()
      
      def fac(num): #returns num!
          return num*fac(num-1) if num > 1 else 1
      
      def is_square(num): #returns True if num is square
              return round(num ** 0.5) ** 2 == num
      
      def lprod(lst) : #returns product of elements in list
          res = 1
          for x in lst:
               res = res * x
          return res
      
      facs = {i:fac(i) for i in range(1,13)}
      # identify group of i numbers between 1 and 12 where product of their factorial is a square
      group = lambda i:[x for x in comb(facs.keys(),i) if is_square(lprod([facs[y] for y in x]))]
      threes,fours = group(3),group(4)
      #identify group of 3 and two groups of 4 where all 11 elements are different
      valid = [[x,y[0],y[1]] for x in threes for y in comb(fours,2) if len(set(list(x)+[z for sub in y for z in sub]))==11]
      valid = [y for x in valid for y in x] # flatten valid
      prods =[lprod(list(x)) for x in valid] # generate products of factorials in valid group
      res=valid[prods.index(min(prods))] #identify factorials with minimum product
      print('Factorials in group with smallest product are:',*res)
      timer.stop()
      timer.report()
      

      Factorials in group with smallest product are: 1 8 9

      Like

  • Jim Randell 7:35 am on 9 August 2022 Permalink | Reply
    Tags:   

    Teaser 2733: Letter-writing 

    From The Sunday Times, 8th February 2015 [link] [link]

    Last year I went to calligraphy lessons. They were held weekly, on the same day each week, for nine consecutive months. Actually I only went to 15 of the lessons, and after the course was over I listed the dates of those lessons that I had attended. In order to practise my new skills I wrote the dates in words (in the format “First of January” etc.) and I found to my surprise that each date used a different number of letters.

    What were the dates of the first and last lessons that I attended?

    [teaser2733]

     
    • Jim Randell 7:36 am on 9 August 2022 Permalink | Reply

      See also: Teaser 2832.

      The lessons were for “9 months”, so let’s suppose there were 39 weekly lessons, of which the setter attended 15.

      This Python program runs in 68ms.

      Run: [ @replit ]

      from datetime import (date, timedelta)
      from enigma import (repeat, trim, int2words, group, subsets, cache, sprintf as f)
      
      # map month numbers to English names
      month = dict(enumerate([
          'january', 'february', 'march', 'april', 'may', 'june',
          'july', 'august', 'september', 'october', 'november', 'december',
        ], start=1)
      )
      
      # ordinals that aren't cardinal + "th", or cardinal - "y" + "ieth"
      _ordinal = {
        1: 'first', 2: 'second', 3: 'third', 5: 'fifth',
        8: 'eighth', 9: 'ninth', 12: 'twelfth',
      }
      
      # return the ordinal of a number
      @cache
      def ordinal(n):
        if n in _ordinal: return _ordinal[n]
        if n < 20: return int2words(n) + 'th'
        (t, r) = divmod(n, 10)
        if r == 0: return trim(int2words(n), tail=1) + 'ieth'
        return int2words(n - r) + ' ' + ordinal(r)
      
      # return the length of the inscription for a date
      @cache
      def inscribe(d):
        t = f("{d} of {m}", d=ordinal(d.day), m=month[d.month])
        return sum(x.isalpha() for x in t)
      
      # generate length <k> sequences of possible dates
      def generate(k):
        (i1, i7) = (timedelta(days=1), timedelta(days=7))
        d = date(2014, 1, 1)
        while True:
          # generate 39 weeks of dates
          ds = list(repeat((lambda d: d + i7), d, k - 1))
          # are we done?
          if ds[-1].year > 2014: break
          yield ds
          # increase start date
          d += i1
      
      # record possible date spans
      rs = set()
      
      # consider possible dates for 39 weekly lessons
      for ds in generate(39):
        # group dates by letter count
        g = group(ds, by=(lambda d: inscribe(d)))
        # choose 15 letter counts
        for ks in subsets(g.keys(), size=15):
          # find earliest and latest possible dates
          dmin = min(min(g[k] for k in ks))
          dmax = max(max(g[k] for k in ks))
          rs.add((dmin, dmax))
      
      # output solution(s)
      for (dmin, dmax) in rs:
        print(f("{dmin} -> {dmax}"))
      

      Solution: The first lesson attended was on the 5th April. The final lesson attended was on 27th December.


      In fact there is only one sequence of 39 weekly dates in 2014 that gives at least 15 different lengths:

      10 letters: “Third of May”; “Tenth of May”
      11 letters: “Fifth of July”
      12 letters: “Fifth of April”
      13 letters: “Seventh of June”; “Twelfth of July”; “Ninth of August”
      14 letters: “Twelfth of April”; “Second of August”
      15 letters: “Fourth of October”; “First of November”; “Sixth of December”
      16 letters: “Seventeenth of May”; “Thirty first of May”; “Fourteenth of June”; “Nineteenth of July”; “Sixth of September”; “Eighth of November”
      17 letters: “Nineteenth of April”; “Twenty fourth of May”; “Twenty first of June”; “Twenty sixth of July”; “Sixteenth of August”; “Thirtieth of August”; “Eleventh of October”
      18 letters: “Twenty eighth of June”
      19 letters: “Twenty third of August”; “Eighteenth of October”; “Fifteenth of November”; “Twentieth of December”
      20 letters: “Twentieth of September”; “Twenty fifth of October”; “Thirteenth of December”
      21 letters: “Thirteenth of September”; “Twenty ninth of November”
      22 letters: “Twenty second of November”
      23 letters: “Twenty seventh of December”
      24 letters: “Twenty seventh of September”

      And this gives exactly 15 different lengths of inscription. So, each length must be represented by a single date that the setter attended.

      The period covered is 5th April – 27th December, and as 5th April is the only date with an inscription of 12 letters, and 27th December is the only date with an inscription of 23 letters, the setter must have attended the actual first and last lessons, and these give the answer to the puzzle.

      Like

  • Jim Randell 8:26 am on 4 August 2022 Permalink | Reply
    Tags:   

    Teaser 2725: Darts match 

    From The Sunday Times, 14th December 2014 [link] [link]

    Andrew, Alexander, Austin, Anthony, Benjamin, Charles, Christopher, Elijah, Jacob, Jayden, Jackson, James, Jason, Mason, Michael, Nathan, Newman, Robert, Samuel and William entered a darts competition, arranged into five teams of four players. It turned out that, for any pair of members of any team, there were just two letters of the alphabet that occurred (once or more) in both their names. The names in each team were arranged alphabetically, the first name being the captain and the last name the reserve. Then the teams were numbered 1 to 5 in alphabetical order of the captains.

    In the order 1 to 5, who were the reserves?

    [teaser2725]

     
    • Jim Randell 8:27 am on 4 August 2022 Permalink | Reply

      This puzzle is slightly different from the usual kind of grouping puzzle, but we can still use some of the [[ grouping ]] functions from the enigma.py library.

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

      Run: [ @replit ]

      from enigma import (grouping, join, printf)
      
      # available players
      names = [
        'Andrew', 'Alexander', 'Austin', 'Anthony', 'Benjamin', 'Charles',
        'Christopher', 'Elijah', 'Jacob', 'Jayden', 'Jackson', 'James', 'Jason',
        'Mason', 'Michael', 'Nathan', 'Newman', 'Robert', 'Samuel', 'William',
      ]
      
      # grouping function
      fn = grouping.share_letters(2)
      
      # form the names <ns> into teams of size <k>
      def teams(ns, k, ts=[]):
        # are we done?
        if not ns:
          yield ts
        else:
          # find the next captain
          cap = min(ns)
          # and 3 team members
          for team in grouping.gang(k - 1, cap, ns.difference([cap]), fn):
            team = [cap] + sorted(team)
            # solve for the remaining names
            yield from teams(ns.difference(team), k, ts + [team])
      
      for ts in teams(set(names), 4):
        for (i, team) in enumerate(ts, start=1):
          printf("Team {i}: {team}", team=join(team, sep=", "))
        printf()
      

      Solution: The reserves are (Team 1-5): William, Samuel, Robert, Newman, Michael.

      The full teams are:

      Team 1: Alexander, Austin, James, William
      Team 2: Andrew, Christopher, Jason, Samuel
      Team 3: Anthony, Benjamin, Charles, Robert
      Team 4: Elijah, Jackson, Nathan, Newman
      Team 5: Jacob, Jayden, Mason, Michael

      Like

    • Frits 8:04 pm on 5 August 2022 Permalink | Reply

      A lot more work but already solved before recursion (ie parameter teams will have 5 teams) .

         
      from itertools import combinations
      
      # check that each string shares exactly <k> letters
      shr_lttrs = lambda s1, s2, k: len(set(s1.lower()) & set(s2.lower())) == k
      
      # form the names <ns> into teams of size <k>
      def form_teams(ns, k, ts=[]):
        # are we done?
        if not ns:
          yield ts
        else:
          # find the name with the least matches
          key = min({k: v for k, v in d.items() if k in ns}.items(), 
                     key=lambda x: len(x[1]))
          # and 3 team members
          for rest in combinations([x for x in key[1] if x in ns], 3):
            # these 3 names should have exactly 2 letters in common
            if any(not y in d[x] for x, y in zip(rest, rest[1:])): continue
            
            team = sorted((key[0], ) + rest)
            # solve for the remaining names
            yield from form_teams(ns.difference(team), k, ts + [team])
            
      
      # available players
      vs = [
        'Andrew', 'Alexander', 'Austin', 'Anthony', 'Benjamin', 'Charles',
        'Christopher', 'Elijah', 'Jacob', 'Jayden', 'Jackson', 'James', 'Jason',
        'Mason', 'Michael', 'Nathan', 'Newman', 'Robert', 'Samuel', 'William',
      ]
      
      vs = sorted(vs)
      
      # create dictionary: name --> matches
      d = {vs[i]: {x for j, x in enumerate(vs[:i] + vs[i + 1:]) 
                       if shr_lttrs(vs[i], x, 2)} for i in range(len(vs))}
      
      teams = []
      dsize = -1
      # process until no more changes to dictionary
      while dsize != sum(len(v) for v in d.values()):
        dsize = sum(len(v) for v in d.values())
        
        # if a match doesn't share exactly 2 letters with at least 2 other matches
        # it can be removed from the dictionary as we we need 4 members in a team
        d = {k: {x for x in v if sum(x != y and y in d[x] for y in v) > 1} 
                 for k, v in d.items()}
       
        # if only three matches left we have finalized a team 
        match3 = [x for k, v in list(d.items()) 
                  for x in [k] + list(v) if len(v) == 3]
        
        # add these persons to teams (removing duplicates while preserving order)
        teams += list(dict.fromkeys(match3))
       
        # maintain dictionary for persons in teams
        d = {k: [x for x in v if x not in teams] 
                 for k, v in d.items() if k not in teams}
               
      # reduce values for persons in teams
      vs = [x for x in vs if x not in teams]
      
      # solve for remaining people
      for ts in form_teams(set(vs), 4):
        i = 0
        for (i, team) in enumerate(ts, start=1):
          print(f"Team {i}: {', '.join(team)}")
        for j in range(len(teams) // 4):
          i += 1
          print(f"Team {i}: {', '.join(sorted(teams[j * 4:j * 4 + 4]))}")
        print()
      

      Like

  • Jim Randell 9:10 am on 28 July 2022 Permalink | Reply
    Tags:   

    Teaser 2726: Christmas star 

    From The Sunday Times, 21st December 2014 [link] [link]

    I asked Peter to place the numbers 1 to 10 at the ten intersection points of the Christmas star so that in each of the five lines the four numbers added to the same total. He found that this was impossible so instead he did it with the numbers 1 to 9 together with just one of those digits repeated. In his answer there was just one line in which that digit did not appear.

    [In ascending order] what were the four numbers in that line?

    [teaser2726]

     
    • Jim Randell 9:12 am on 28 July 2022 Permalink | Reply

      (See also: Enigma 1063).

      I assume were are talking about a layout like this:

      There are 5 lines, and each of the numbers appears on two of the lines.

      If we add the lines we will be counting each number twice, and the total will be 5 times the common sum, T.

      So, if X is the repeated digit we have:

      2 × (1 + 2 + … + 9 + X) = 5 × T
      T = 18 + (2/5)X

      Hence: X = 5, and T = 20.

      To remove duplicate solutions we can suppose the line that doesn’t include X is (B, C, D, E) and that B < E.

      The following run file executes in 98ms.

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      # suppose the line with no repeated digit is BCDE
      
      SubstitutedExpression
      
      --digits="1-9"
      --distinct="BCDE"
      
      # the 5 lines have the same sum (= 20)
      "A + C + F + I = 20"
      "A + D + G + J = 20"
      "B + C + D + E = 20"
      "B + F + H + J = 20"
      "E + G + H + I = 20"
      
      # all 9 digits are used
      "len({A, B, C, D, E, F, G, H, I, J}) = 9"
      
      # 5 is in all the lines except BCDE
      "5 not in {B, C, D, E}"
      "5 in {A, C, F, I}"
      "5 in {A, D, G, J}"
      "5 in {B, F, H, J}"
      "5 in {E, G, H, I}"
      
      # remove duplicate solutions
      "B < E"
      
      --answer="ordered(B, C, D, E)"
      --template=""
      

      Solution: The four numbers on the line without the repeated digit are: 1, 3, 7, 9.

      There are 12 essentially different layouts, here is one:

      Like

  • Jim Randell 6:31 am on 19 July 2022 Permalink | Reply
    Tags:   

    Teaser 2721: Milliner’s hat-trick 

    From The Sunday Times, 16th November 2014 [link] [link]

    A football tournament has four groups each of four teams, with the teams in the same group playing each other once. So far the teams have each played two games and in each group the distribution of points is different. Also, in each group just one pair of teams are level on points and their positions have been determined by their “goals scored”. Milliner has scored four (including a hat-trick), Orlando two, and nine other players have scored one goal each. Despite his success, Orlando’s team is not the top of their group.

    What are the results of the two games that Milliner’s team have played? And the two games that Orlando’s team have played?

    [teaser2721]

     
    • Jim Randell 6:33 am on 19 July 2022 Permalink | Reply

      The puzzle is not explicit on the scoring system used (i.e. 2 points for a win, or 3 points for a win), although it turns out it doesn’t matter which is used, the answer to the puzzle is the same in both cases.

      In each group each team has played 2 of their 3 matches. So there have been 4 matches played (and there are 2 matches left to play). So among the 4 groups there have been 16 matches played in total.

      The minimum possible number of goals scored among the 4 matches in a group is 2. So the maximum possible number of goals scored is 15 − 3×2 = 9. (Although it may be possible to reduce this theoretical maximum value, which would speed up the program).

      For each group, this Python program considers the scores for teams A (1st), B (2nd), C (3rd), D (4th), such that each team has played 2 matches, and two of the teams are tied on points, but have different “goals for” values.

      It then chooses 4 different points distributions (one for each of the groups), and from the previously examined scores it chooses 4 sets of scores that have 15 goals scored in total, and ensures there are possible teams for both Milliner and Orlando.

      The program runs in 361ms.

      Run: [ @replit ]

      from collections import defaultdict
      from enigma import (
        Football, irange, subsets, tuples, cproduct, peek, delete,
        update, find, unzip, reverse, map2str, join, printf
      )
      
      # scoring regime (2 or 3 points for a win)
      football = Football(games='wdlx', points=dict(w=2, d=1))
      
      # labels for the teams
      teams = (A, B, C, D) = tuple("ABCD")
      
      # keys for the matches
      ks = list(x + y for (x, y) in subsets(teams, size=2))
      
      # allocate <t> goals among the matches <ms>, return scores <ss>
      def allocate(t, ms, ss=dict()):
        # are we done?
        if not ms:
          if t == 0:
            yield ss
        else:
          # calculate the number of surplus goals
          n = t - sum(v == 'w' for v in ms.values())
          if n < 0: return
          # choose the next match to allocate goals to
          (k, v) = peek(ms.items())
          if v == 'x':
            # not played
            yield from allocate(t, delete(ms, [k]), update(ss, [(k, None)]))
          elif v == 'w' or v == 'l':
            # a win (or loss), calculate scores (x, y)
            for x in irange(1, n + 1):
              for y in irange(0, min(x - 1, n - x + 1)):
                s = ((x, y) if v == 'w' else (y, x))
                yield from allocate(t - x - y, delete(ms, [k]), update(ss, [(k, s)]))
          elif v == 'd':
            # a draw, calculate scores (x, x)
            for x in irange(0, n // 2):
              yield from allocate(t - x - x, delete(ms, [k]), update(ss, [(k, (x, x))]))
      
      # check goals scored by team <t> in a match from scores <ss> is <n> or more
      def scored(t, ss, n):
        for (k, v) in ss.items():
          if v is not None:
            i = find(k, t)
            if i != -1 and v[i] >= n:
              return True
      
      # map: <pts> -> <total goals> -> (<match scores>, <milliner>, <orlando>) values
      d = defaultdict(lambda: defaultdict(list))
      
      # consider possible match outcomes
      for ms in football.games(repeat=len(ks)):
        ms = dict(zip(ks, ms))
        # compute the table for each team
        table = dict((t, football.extract_table(ms, t)) for t in teams)
        # each team has played twice
        if not all(table[t].played == 2 for t in teams): continue
        # points are increasing, with exactly 2 teams having the same points
        pts = tuple(table[t].points for t in teams)
        if len(set(pts)) != 3: continue
        if not all(x >= y for (x, y) in tuples(pts, 2)): continue
        # allocate goals to the matches
        for t in irange(2, 9):
          for ss in allocate(t, ms):
            # extract the goals (for, against) for each team
            goals = dict((t, football.extract_goals(ss, t)) for t in teams)
            # check teams with same points have different "goals for" values
            if any(p1 == p2 and not (goals[t1][0] > goals[t2][0])
              for ((t1, p1), (t2, p2)) in tuples(zip(teams, pts), 2)
            ): continue
            # find teams for Milliner: team must have a match with >= 3 goals scored
            # and have >= 4 goals scored in total
            tM = list(t for (t, (f, _)) in goals.items() if f > 3 and scored(t, ss, 3))
            # find teams for Orlando: must have >= 2 goals, and not be top
            tO = list(t for (t, (f, _)) in goals.items() if t != 'A' and f > 1)
            # record this set of scores
            d[pts][t].append((ss, tM, tO))
      
      # collect scores from <ss> for teams in <ts> (from the team's perspective)
      def collect(ss, ts):
        for t in ts:
          rs = list()
          for (s, f) in unzip(football.extract(ss, t)):
            if s is None: continue
            rs.append(reverse(s) if f else s)
          yield tuple(sorted(rs, reverse=1))
      
      # choose 4 different points distributions
      for ps in subsets(d.keys(), size=4):
        # choose the number of goals scored in each group
        for ts in cproduct(d[p].keys() for p in ps):
          # there should be 15 goals in total
          if sum(ts) != 15: continue
          # choose the actual scores in each match
          for ss in cproduct(d[p][t] for (p, t) in zip(ps, ts)):
            # check for possibilities for Milliner and Orlando
            if not any(tM for (_, tM, _) in ss): continue
            if not any(tO for (_, _, tO) in ss): continue
            # output a solution (groups = scores, pts, M, O; M's team; O's team)
            (Ms, Os) = (set(), set())
            for (i, ((s, tM, tO), p)) in enumerate(zip(ss, ps), start=1):
              printf("Group {i}: {s} {p} {tM} {tO}", s=map2str(s, enc=""), tM=join(tM, enc="[]"), tO=join(tO, enc="[]"))
              Ms.update(collect(s, tM))
              Os.update(collect(s, tO))
            for M in sorted(Ms): printf("M's team: {M}", M=join(M, sep=", "))
            for O in sorted(Os): printf("O's team: {O}", O=join(O, sep=", "))
            printf()
      

      Solution: The results for Milliner’s team are: a 3-1 win, a 1-0 win. The results for Orlando’s team are: a 2-0 win, a 0-1 loss.

      M’s team is a group leader. O’s team is second in their group.

      With “2 points for a win” (and 1 for a draw) there are three possible sets of scores:

      Group 1: A v B = 1-0; A v C = 1-0; B v D = 2-0; C v D = 1-0; points = [4, 2, 2, 0], Orlando is team B
      Group 2: A v C = 3-1; A v D = 1-0; B v C = 0-0; B v D = 0-0; points = [4, 2, 1, 1], Milliner is team A
      Group 3: A v B = 1-0; A v C = 0-0; B v D = 1-0; C v D = 0-0; points = [3, 2, 2, 1]
      Group 4: A v C = 0-0; A v D = 2-0; B v C = 0-0; B v D = 1-0; points = [3, 3, 2, 0]

      Group 1: A v B = 1-0; A v D = 1-0; B v C = 2-0; C v D = 1-0; points = [4, 2, 2, 0], Orlando is team B
      Group 2: A v C = 3-1; A v D = 1-0; B v C = 0-0; B v D = 0-0; points = [4, 2, 1, 1], Milliner is team A
      Group 3: A v B = 1-0; A v C = 0-0; B v D = 1-0; C v D = 0-0; points = [3, 2, 2, 1]
      Group 4: A v C = 0-0; A v D = 2-0; B v C = 0-0; B v D = 1-0; points = [3, 3, 2, 0]

      Group 1: A v C = 1-0; A v D = 1-0; B v C = 0-1; B v D = 2-0; points = [4, 2, 2, 0], Orlando is team B
      Group 2: A v C = 3-1; A v D = 1-0; B v C = 0-0; B v D = 0-0; points = [4, 2, 1, 1], Milliner is team A
      Group 3: A v B = 1-0; A v C = 0-0; B v D = 1-0; C v D = 0-0; points = [3, 2, 2, 1]
      Group 4: A v C = 0-0; A v D = 2-0; B v C = 0-0; B v D = 1-0; points = [3, 3, 2, 0]

      We can examine what happens with “3 points for a win” by changing the initialisation at line 8. And we find in this case there are four possible sets of scores:

      Group 1: A v B = 1-0; A v D = 0-0; B v C = 2-0; C v D = 1-0; points = [4, 3, 3, 1], Orlando is team B
      Group 2: A v B = 1-1; A v D = 1-0; B v C = 0-0; C v D = 0-0; points = [4, 2, 2, 1]
      Group 3: A v C = 3-1; A v D = 1-0; B v C = 0-0; B v D = 0-0; points = [6, 2, 1, 1], Milliner is team A
      Group 4: A v C = 0-0; A v D = 2-0; B v C = 0-0; B v D = 1-0; points = [4, 4, 2, 0]

      Group 1: A v B = 1-0; A v D = 0-0; B v C = 2-0; C v D = 1-0; points = [4, 3, 3, 1], Orlando is team B
      Group 2: A v C = 0-0; A v D = 1-0; B v C = 0-0; B v D = 1-1; points = [4, 2, 2, 1]
      Group 3: A v C = 3-1; A v D = 1-0; B v C = 0-0; B v D = 0-0; points = [6, 2, 1, 1], Milliner is team A
      Group 4: A v C = 0-0; A v D = 2-0; B v C = 0-0; B v D = 1-0; points = [4, 4, 2, 0]

      Group 1: A v C = 1-0; A v D = 0-0; B v C = 0-1; B v D = 2-0; points = [4, 3, 3, 1], Orlando is team B
      Group 2: A v B = 1-1; A v D = 1-0; B v C = 0-0; C v D = 0-0; points = [4, 2, 2, 1]
      Group 3: A v C = 3-1; A v D = 1-0; B v C = 0-0; B v D = 0-0; points = [6, 2, 1, 1], Milliner is team A
      Group 4: A v C = 0-0; A v D = 2-0; B v C = 0-0; B v D = 1-0; points = [4, 4, 2, 0]

      Group 1: A v C = 1-0; A v D = 0-0; B v C = 0-1; B v D = 2-0; points = [4, 3, 3, 1], Orlando is team B
      Group 2: A v C = 0-0; A v D = 1-0; B v C = 0-0; B v D = 1-1; points = [4, 2, 2, 1]
      Group 3: A v C = 3-1; A v D = 1-0; B v C = 0-0; B v D = 0-0; points = [6, 2, 1, 1], Milliner is team A
      Group 4: A v C = 0-0; A v D = 2-0; B v C = 0-0; B v D = 1-0; points = [4, 4, 2, 0]

      Like

  • Jim Randell 7:29 am on 12 July 2022 Permalink | Reply
    Tags:   

    Teaser 2724: Headcount 

    From The Sunday Times, 7th December 2014 [link] [link]

    My grandson and I play a coin game. First we toss seven coins and I have to predict in advance the number of heads whilst he has to predict the number of tails. I then get a number of points equal to the number of heads, he gets a number of points equal to the number of tails, and anyone whose prediction was correct gets a fixed bonus number of points (less than 40). We repeat this with six coins in the second round, then five, and so on down to two. In a recent game we noticed that, after each round, the total of all the points so far awarded was equal to a prime number.

    What is the “fixed bonus” number of points? And what was the total of all the points at the end of the game?

    [teaser2724]

     
    • Jim Randell 7:30 am on 12 July 2022 Permalink | Reply

      (See also: Teaser 3009).

      In a round with n coins the points awarded are, the number of heads (+ bonus if guessed correctly) and the number of tails (+ bonus if guessed correctly). So n points are always awarded, along with 0, 1, or 2 bonuses.

      We don’t need to worry about the points won by each player, just the total number of points gained in each round.

      This Python program keeps a set of (total, bonus) pairs, and then progresses through the rounds keeping viable values.

      It runs in 57ms. (Internal runtime is 664µs).

      Run: [ @replit ]

      from enigma import (irange, is_prime, printf)
      
      # start with a total of 0, and all possible bonus values
      ss = set((0, x) for x in irange(0, 40))
      
      # start with <k> coins
      k = 7
      
      # consider subsequent rounds
      while k > 1:
        ss_ = set()
        # consider (total, bonus) in the previous round
        for (t, b) in ss:
          # consider number of bonuses awarded in this round
          for n in (0, 1, 2):
            t_ = t + k + n * b
            if is_prime(t_):
              ss_.add((t_, b))
        # move on to the next round
        (ss, k) = (ss_, k - 1)
      
      # output solutions
      for (t, b) in ss:
        printf("total = {t}, bonus={b}")
      

      Solution: The number of bonus points is 19. The total number of points at the end of the game is 103.

      The progression of the game is:

      7 coins: total = 7 (0 bonus points)
      6 coins: total = 13 (0 bonus points)
      5 coins: total = 37 (19 bonus points)
      4 coins: total = 79 (38 bonus points)
      3 coins: total = 101 (19 bonus points)
      2 coins: total = 103 (0 bonus points)

      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
%d bloggers like this: