Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 4:46 pm on 24 November 2023 Permalink | Reply
    Tags:   

    Teaser 3192: In formation technology 

    From The Sunday Times, 26th November 2023 [link] [link]

    Football formations are generally described by three or four nonzero whole numbers summing to 10 (the goalkeeper isn’t counted), representing, from defence to attack, the number of players in approximate lines across the pitch.

    Last season we played a different formation every week, always using four lines, each with at most four players; the difference between one week and the next was that from one line two players moved, one to an adjacent line and the other to the line beyond that (e.g. 3-4-1-2 could only be followed by 3-2-2-3). Our number of fixtures was the largest it could have been, given these conditions. The first number in our formations was more often 3 than any other number; 3-1-3-3 gave us our worst result.

    How many games did we play, and what were our first three formations?

    [teaser3192]

     
    • Jim Randell's avatar

      Jim Randell 5:38 pm on 24 November 2023 Permalink | Reply

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

      Run: [ @replit ]

      from enigma import (Accumulator, decompose, update, multiset, join, printf)
      
      # find possible formations
      fs = list(decompose(10, 4, increasing=0, sep=0, min_v=1, max_v=4))
      
      # find possible transitions
      trans = dict()
      for s in fs:
        ts = set()
        # choose an index to donate 2 players
        for (i, n) in enumerate(s):
          if n < 3: continue
          # i -> j, k
          for d in [-1, +1]:
            j = i + d
            if j < 0 or j > 3 or s[j] > 3: continue
            k = j + d
            if k < 0 or k > 3 or s[k] > 3: continue
            ts.add(update(s, [(i, n - 2), (j, s[j] + 1), (k, s[k] + 1)]))
        trans[s] = ts
      
      # find paths in graph <adj>
      def paths(adj, ss):
        yield ss
        # try to extend the path
        s = ss[-1]
        for t in adj[s]:
          if t not in ss:
            yield from paths(adj, ss + [t])
      
      # find maximal length paths
      r = Accumulator(fn=max, collect=1)
      for s in fs:
        for ss in paths(trans, [s]):
          r.accumulate_data(len(ss), ss)
      printf("max len = {r.value}")
      
      # consider maximal length paths
      for ss in r.data:
        # 3-1-3-3 must be present
        if (3, 1, 3, 3) not in ss: continue
        # 3 occurs most frequently as the first number
        m = multiset.from_seq(s[0] for s in ss)
        n3 = m.count(3)
        if not all(n3 > nk for (k, nk) in m.items() if k != 3): continue
        # output solution
        fmt = lambda x: join(x, sep='-')
        printf("{ss}", ss=join((fmt(x) for x in ss), sep=", ", enc="()"))
      

      Solution: There were 13 games. The first three formations were: 3-2-4-1, 4-3-2-1, 4-1-3-2.

      The full list of formations is:

      1: 3-2-4-1
      2: 4-3-2-1
      3: 4-1-3-2
      4: 2-2-4-2
      5: 3-3-2-2
      6: 3-1-3-3
      7: 4-2-1-3
      8: 2-3-2-3
      9: 2-1-3-4
      10: 3-2-1-4
      11: 1-3-2-4
      12: 1-4-3-2
      13: 1-2-4-3

      There are 4 weeks that begin with 3-, and 3 weeks that each start 1-, 2-, 4-.

      Like

      • Frits's avatar

        Frits 5:05 pm on 1 December 2023 Permalink | Reply

        @Jim,

        I noticed that you don’t see (among others ) 4-1-3-2 to 2-2-3-3 as a valid transition. I don’t consider “to the line beyond that” as the immediate adjacent line.

        Like

        • Jim Randell's avatar

          Jim Randell 5:12 pm on 1 December 2023 Permalink | Reply

          @Frits: Yes, it has to be adjacent (it is “the line beyond” not “a line beyond”). It would probably have been better expressed as “the next line beyond”.

          I think there are many solutions to the puzzle where you allow transfers to non-adjacent lines. (I originally tried it with this interpretation).

          And welcome back, it has been a while.

          Like

  • Unknown's avatar

    Jim Randell 9:36 am on 23 November 2023 Permalink | Reply
    Tags:   

    Teaser 2654: Square cut 

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

    Given a rectangular piece of paper that is not actually square it is possible with one straight cut to divide it into a square and a rectangle (which might or might not itself be square). I call this process a “square cut”. Recently I started with a rectangle of paper one metre long with the width being more than half a metre. I performed a square cut and put the square to one side. On the remaining rectangle I performed a square cut and put the square to one side. I kept repeating this process until the remaining rectangle was itself a square. The result was that I had cut the original piece of paper into six squares all of whose sides were whole numbers of centimetres.

    How many centimetres wide was the original piece of paper?

    [teaser2654]

     
    • Jim Randell's avatar

      Jim Randell 9:37 am on 23 November 2023 Permalink | Reply

      This Python program considers possible widths between 51 and 99 cm, and looks for those which cut the rectangle into 6 squares.

      It runs in 69ms. (Internal runtime is 452µs).

      Run: [ @replit ]

      from enigma import (irange, ordered, printf)
      
      # perform cuts on an a x b rectangle, where a <= b
      def cut(a, b):
        assert not (a > b)
        ss = list()
        while True:
          ss.append(a)
          if a == b: break
          (a, b) = ordered(a, b - a)
        return ss
      
      # consider the width of the rectangle
      for n in irange(51, 99):
        # cut into squares
        ss = cut(n, 100)
        if len(ss) == 6:
          # output solution
          printf("n={n}: {ss}")
      

      Solution: The original piece of paper was 70 cm wide.

      The sizes of the 6 squares made are: 70, 30, 30, 10, 10, 10.

      Like

  • Unknown's avatar

    Jim Randell 4:23 pm on 20 November 2023 Permalink | Reply
    Tags:   

    Teaser 2670: Answers on a postcard 

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

    On a postcard I have written four two-digit numbers, none of which is divisible by three. In three of the numbers the two digits used are consecutive (in some order) and in fact overall the four numbers use eight consecutive digits. I have calculated the sum and product of the four numbers. Then I have consistently replaced each of the digits 0 to 9 by a different letter of the alphabet. It turns out that the sum of my four numbers is SUM and their product is PRODUCT.

    What number is represented by POSTCARD?

    [teaser2670]

     
    • Jim Randell's avatar

      Jim Randell 4:24 pm on 20 November 2023 Permalink | Reply

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

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --distinct="abcdefgh,ACDMOPRSTU"
      
      # check integers <ns> are consecutive (when considered in order)
      --code="check = lambda ns: ns[-1] + 1 == ns[0] + len(ns)"
      --code="is_consecutive = lambda *ns: check(sorted(ns))"
      
      # sum is SUM, product is PRODUCT
      "{ab} + {cd} + {ef} + {gh} = {SUM}"
      "{ab} * {cd} * {ef} * {gh} = {PRODUCT}"
      
      # suppose {ab}, {cd}, {ef} (in order) are the 3 with consecutive digits
      "is_consecutive({a}, {b})"
      "is_consecutive({c}, {d})"
      "is_consecutive({e}, {f})"
      "{a} < {c} < {e}"
      # [optional] the other one does not consist of consecutive digits
      "not is_consecutive({g}, {h})"
      
      # 8 consecutive digits are used overall
      "is_consecutive({a}, {b}, {c}, {d}, {e}, {f}, {g}, {h})"
      
      --answer="POSTCARD"
      --template="({ab} {cd} {ef} {gh}) ({SUM}) ({PRODUCT}) ({POSTCARD})"
      --solution=""
      

      Solution: POSTCARD = 94267830.

      The numbers are:

      (23, 56, 74, 98)
      SUM = 251
      PRODUCT = 9340576

      Like

    • GeoffR's avatar

      GeoffR 9:43 am on 21 November 2023 Permalink | Reply

      from enigma import all_different,nsplit,nconcat
      
      def consec_dig(L):
          return sorted(L) == list(range(min(L), max(L)+1))
      
      # Find three 2-digit numbers (ab, cd, ef) with consecutive digits
      for ab in range(12, 99):
        if ab % 3 == 0:continue
        for cd in range(ab+1, 99):
          if cd % 3 == 0:continue
          for ef in range(cd+1, 99):
            if ef % 3 == 0:continue
            a, b, c, d = ab // 10, ab % 10, cd // 10, cd % 10
            e, f = ef // 10, ef % 10
            if not a < c < e:continue
            L_6d = [a, b, c, d, e, f]
            if consec_dig(L_6d):
                
              # last 2-digit number does not have consecutive digits
              for gh in range(ef+1, 99):
                if gh % 3 == 0:continue
                g, h = gh // 10, gh % 10
                if g < h:continue
                
                # check overall the four numbers use eight consecutive digits 
                L_8d = [a, b, c, d, e, f, g, h]
                if consec_dig(L_8d):
                    
                  # Find SUM and PRODUCT
                  SUM = ab + cd + ef + gh
                  S, U, M = SUM //100, SUM // 10 % 10, SUM % 10
                  if not all_different(S, U, M): continue
                  
                  PRODUCT = ab * cd * ef * gh
                  # Check PRODUCT has 7 digits
                  if len(str(PRODUCT)) != 7:continue
                  P, R, O, D, U, C, T = nsplit(PRODUCT)
                  if not all_different (P,R,O,D,U,C,T ):continue
                  
                  # Check 'U' is same value in SUM and PRODUCT
                  if SUM // 10 % 10 == PRODUCT // 100 % 10:
                    # Find missing letter A for POSTCARD value
                    digits = set(range(10))
                    A = digits.difference({S,U,M,P,R,O,D,C,T}).pop()
                    POSTCARD = nconcat(P,O,S,T,C,A,R,D)
                    print(f"SUM = {SUM},PRODUCT = {PRODUCT}")
                    print(f"POSTCARD = {POSTCARD}")
                    print(f"Four Numbers are {ab},{cd},{ef} and {gh}.")
      
      # SUM = 251,PRODUCT = 9340576
      # POSTCARD = 94267830
      # Four Numbers are 23,56,74,98.
         
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:46 pm on 17 November 2023 Permalink | Reply
    Tags:   

    Teaser 3191: The budgie’s extra ration 

    From The Sunday Times, 19th November 2023 [link] [link]

    The budgie’s circular toy hung on a hook. Two equal legs suspended his budgerigar-seed dispensing chord from that hook. Parallel to the chord, a diameter crossed the middle. When budgie knocked his seed dispenser below the  diameter, a triangle lit up, as shown, and he got an extra seed ration. In the design of the toy, the ratio of the length of the chord to the length of the smallest side of the lit triangle has been adjusted so that a right-angled triangle results, with the right angle on the diameter.

    What is the square of that ratio?

    [teaser3191]

     
    • Jim Randell's avatar

      Jim Randell 7:05 pm on 17 November 2023 Permalink | Reply

      You can have a pretty good guess at the answer by measuring the relevant lines in the diagram.

      Here’s a numerical solution:

      This Python program runs in 56ms. (Internal runtime is 382µs).

      Run: [ @replit ]

      from enigma import (find_value, rcs, line_intersect, sq, fdiv, printf)
      
      # distance between A and B
      def dist(A, B):
        ((x1, y1), (x2, y2)) = (A, B)
        return rcs(abs(x1 - x2), abs(y1 - y2))
      
      # return the cosine of the angle for a chord of length 2x (on a unit circle)
      def solve(x, verbose=0):
        y = rcs(1, -x)
        # vertices of the isosceles triangle = A, B, C
        (A, B, C) = ((0, -1), (x, y), (-x, y))
        # chord length = c
        c = dist(B, C)
        # find where AB intersects the diameter = X
        X = line_intersect(A, B, (1, 0), (-1, 0)).pt
        # non-hypotenuse sides of the lit triangle = f, g
        (f, g) = (dist(A, X), dist(X, C))
      
        # print the answer R = (c/f)^2
        if verbose: printf("R = sq(c/f) = {r:.6g} [x={x:.6f} y={y:.6f}]", r=fdiv(sq(c), sq(min(f, g))))
      
        # return the cosine of the angle AXC (cosine rule)
        h = dist(A, C)
        return fdiv(sq(f) + sq(g) - sq(h), 2 * f * g)
      
      # find x that gives an angle of pi/2 (cosine = 0)
      r = find_value(solve, 0, 0, 1)
      # output solution
      solve(r.v, verbose=1)
      

      Solution: The square of the ratio is 2.

      i.e. the ratio itself is √2.


      We can determine the answer analytically using a similar approach as follows:

      With a unit circle, and a chord of length 2x the co-ordinates of the isosceles triangle can be represented as:

      A = (0, −1)
      B = (x, y)
      C = (−x, y)
      where:
      x, y ∈ (0, 1) and x² + y² = 1

      The line AB intersects the diameter at:

      X = (x/(y + 1), 0)

      And so we can determine the 3 sides of the lit triangle f, g, h in terms of y:

      f² = |AX|² = x²/(y + 1)² + 1
      f² = 2/(y + 1)

      g² = |CX|² = x²(y + 2)²/(y + 1)² + y²
      g² = (4 − 2y²)/(y + 1)

      h² = |AC|² = x² + (y+1)²
      h² = 2y + 2

      And this is a right-angled triangle, so f² + g² = h²:

      2/(y + 1) + (4 − 2y²)/(y + 1) = 2y + 2
      y² + y − 1 = 0

      y = (√5 − 1)/2 ≈ 0.618 (and x = √y ≈ 0.786)

      If φ is the Golden Ratio, then: y = φ − 1 = 1/φ, but we don’t need to deal with the actual value of y, instead we note:

      y = 1 − y²
      y(y + 1) = y² + y = 1

      We can then determine the required answer (R) as c²/f² where the length of the chord c = 2x:

      R = 4x² / 2/(y + 1)
      R = 2(1 − y²)(y + 1)
      R = 2y(y + 1)
      R = 2

      Like

  • Unknown's avatar

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

    Brain-Teaser 422: Telephone number 

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

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

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

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

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

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

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

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

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

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

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

    What was Fred’s telephone number?

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

    [teaser422]

     
    • Jim Randell's avatar

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

      This puzzle is straightforward to solve programatically.

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

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

      Run: [ @replit ]

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

      Solution: Fred’s telephone number is 35742.

      And we have:

      35 + 7 = 42
      35742 = 138 × 259

      Like

    • GeoffR's avatar

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

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

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

      Like

  • Unknown's avatar

    Jim Randell 7:55 am on 14 November 2023 Permalink | Reply
    Tags:   

    Teaser 2652: Square meals 

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

    A company produces five different types of muesli. The ingredients are bought from a wholesaler who numbers his items from 1 to 10. In each type of muesli there is a mixture of a square number of different ingredients and the weight in grams of each ingredient is the square of its item number: also the total weight of its ingredients is a perfect square number of grams.

    Last month one of the ingredients was unavailable and so only the “basic” and “fruity” varieties could be produced. This week a different ingredient is unavailable and so only the “luxury” variety can be made.

    What are the item numbers of the ingredients in the luxury muesli?

    [teaser2652]

     
    • Jim Randell's avatar

      Jim Randell 7:55 am on 14 November 2023 Permalink | Reply

      I assumed each mixture has multiple ingredients, and as there are only 10 possible ingredients this means each must have 4 or 9.

      This Python program looks for sets of 4 or 9 numbers from 1 to 10 whose squares sum to a square number. We then choose 5 of these sets to form the different types of muesli made, and look for the 2 (different) unavailable ingredients. The first unavailable ingredient only allows 2 of the types to be made (these are “basic” and “fruity”) and the second only allows 1 to be made (and this is “luxury”).

      It runs in 84ms. (Internal runtime is 879µs).

      Run: [ @replit ]

      from enigma import (irange, subsets, sq, is_square, union, intersect, printf)
      
      # collect possible collections of ingredients
      ms = list()
      
      # choose 4 or 9 ingredients
      for k in [4, 9]:
        for ns in subsets(irange(1, 10), size=k):
          # calculate the total weight
          t = sum(sq(n) for n in ns)
          if not is_square(t): continue
          printf("[k={k}: ns={ns} t={t}]")
          ms.append(ns)
      
      # choose 5 varieties
      for vs in subsets(ms, size=5):
        ns = union(vs)
        # choose the first unavailable ingredient
        for u1 in ns:
          # find how many can be made without u1
          vs1 = list(v for v in vs if u1 not in v)
          # there are 2 that cannot be made ("basic" and "fruity")
          if len(vs1) != 2: continue
      
          # choose the second unavailable ingredient
          for u2 in ns:
            if u2 == u1: continue
            # find how many can be made without u2
            vs2 = list(v for v in vs if u2 not in v)
            # there is only 1 that cannot be made ("luxury")
            if len(vs2) != 1: continue
            if intersect([vs1, vs2]): continue
      
            # output solution
            printf("u1={u1} -> basic/fruity={vs1}; u2={u2} -> luxury={vs2[0]}")
      

      Solution: Luxury muesli uses ingredients: 2, 4, 5, 6.

      There are only 5 possible sets of ingredients, so these correspond to each of the 5 types of museli:

      (2, 4, 5, 6) → 81
      (1, 2, 4, 10) → 121
      (1, 2, 8, 10) → 169
      (2, 4, 7, 10) → 169
      (5, 6, 8, 10) → 225

      The unavailable ingredient last month was 4, meaning only: (1, 2, 8, 10) and (5, 6, 8, 10) could be made. These are (in some order) “basic” and “fruity”.

      The unavailable ingredient this week is 10, meaning only (2, 4, 5, 6) can be made, and this is “luxury”.

      Like

  • Unknown's avatar

    Jim Randell 2:48 pm on 12 November 2023 Permalink | Reply
    Tags:   

    Brainteaser 1500: Lying about his age 

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

    It’s Uncle Joe’s birthday today, but he’s reluctant to tell me how old he is. Here are some clues, although some of them aren’t true:

    1. Every number of a true statement is a factor of Joe’s age.

    2. Every number of a false statement is a factor of Joe’s age.

    3. This is neither the first true nor the last false statement.

    4. Of the two statements immediately before and after this one, just one is true and the other is false.

    5. Of the two statements immediately before and after this one, just one is true and the other is false.

    6. Each digit of Joe’s age is different and is the number of a statement which is false.

    7. This is either the last true or the last false statement.

    8. Every prime factor of Joe’s age is the number of a true statement.

    How old is Joe?

    [teaser1500]

     
    • Jim Randell's avatar

      Jim Randell 2:49 pm on 12 November 2023 Permalink | Reply

      The following Python program considers possible ages, and truth values for the statements.

      The [[ Delay() ]] class from the enigma.py library is used to calculate the digits and prime factors of the age only as necessary. (Although in the event this doesn’t save a great deal of processing).

      The program runs in 147ms. (Internal runtime is 73ms).

      Run: [ @replit ]

      from enigma import (Delay, irange, subsets, nsplit, prime_factor, map2str, printf)
      
      # the prime factors of <n>
      factors = lambda n: list(f for (f, _) in prime_factor(n))
      
      # choose an age
      for n in irange(1, 121):
        # delay the evaluation of digits and factors
        _ds = Delay(nsplit, n)
        _fs = Delay(factors, n)
      
        # choose truth values for the statements
        for vs in subsets((False, True), size=8, select="M"):
          if all(vs): continue
          ss = dict(enumerate(vs, start=1))
      
          # check the values:
          # 1. "every number of a true statement is a factor of the age"
          if ss[1] != all(n % k == 0 for (k, v) in ss.items() if v): continue
      
          # 2. "every number of a false statement is a factor of the age"
          if ss[2] != all(n % k == 0 for (k, v) in ss.items() if not v): continue
      
          # 3. "this is neither the first true nor the last false statement"
          if ss[3] != (not ((not any(vs[:2])) if ss[3] else all(vs[3:]))): continue
      
          # 4. "one of (3), (5) is true and one is false"
          if ss[4] != (ss[3] != ss[5]): continue
      
          # 5. "one of (4), (6) is true and one is false"
          if ss[5] != (ss[4] != ss[6]): continue
      
          # 6. "each digit of the age is different, and the number of a false statement"
          ds = _ds.value
          if ss[6] != (len(ds) == len(set(ds)) and all(not ss.get(d, 1) for d in ds)): continue
      
          # 7. "this is either the last true or the last false statement"
          if ss[7] != ((not ss[8]) if ss[7] else ss[8]): continue
      
          # 8. "every prime factor of the age is the number of a true statement"
          fs = _fs.value
          if ss[8] != all(ss.get(f, 0) for f in fs): continue
      
          # output solution
          printf("n={n} {ss}", ss=map2str(ss, enc="[]"))
      

      Solution: Joe is 72.

      And the values of the statements are:

      1. true [the true statements are 1, 3, 4, 6, and these are all divisors of 72]
      2. false [5 and 7 are false statements, but there are not divisors of 72]
      3. true [this is not the first true statement (1), nor the last false statement (8)]
      4. true [3 is true, 5 is false]
      5. false [both 4 and 6 are true]
      6. true [7 and 2 are different, and both number false statements]
      7. false [this is not the last true statement (6), nor the last false statement (8)]
      8. false [2 is a prime factor of 72, and statement 2 is false]

      Like

  • Unknown's avatar

    Jim Randell 4:37 pm on 10 November 2023 Permalink | Reply
    Tags:   

    Teaser 3190: Mods-In-Suits 

    From The Sunday Times, 12th November 2023 [link] [link]

    In single-pack card game Mods-In-Suits, players are dealt two cards. Each card’s face value (Ace=1 to K=13) is modified by its suit, thus:

    “Spade” squares it;
    “Heart” halves it;
    “Club” changes its sign;
    “Diamond” divides it by the other card’s face value.

    Players score their modified values’ sum (or zero if there is a matching pair of face values). Players may exchange their second dealt card for a fresh card from the pack.

    Stuck in the jam in the Smoke at rush hour, John’s children were missing Andy’s party. Playing Mods-In-Suits for amusement led to one unusual game. Jo’s initial score was a positive whole number, Bo’s its negative. Four different suits and face values were dealt. Both exchanged their second card, but each score was unchanged. Four different suits were still showing.

    Give Jo’s final hand.

    [teaser3190]

     
    • Jim Randell's avatar

      Jim Randell 5:21 pm on 10 November 2023 Permalink | Reply

      This Python program runs in 80ms. (Internal runtime is 12ms).

      Run: [ @replit ]

      from enigma import (
        irange, cproduct, sq, rdiv, group, subsets, unpack,
        as_int, intersect, singleton, diff, join, printf
      )
      
      # a pack of cards (<suit>, <value>)
      pack = list(cproduct(['SHCD', irange(1, 13)]))
      
      # calculate modified value for card <X> paired with <Y>
      def value(X, Y):
        ((sx, vx), (sy, vy)) = (X, Y)
        if sx == 'S': return sq(vx)
        if sx == 'H': return rdiv(vx, 2)
        if sx == 'C': return -vx
        if sx == 'D': return rdiv(vx, vy)
      
      # score a pair of cards (we are only interested in non-zero integer
      # values, from pairs of cards with different suits)
      def score(X, Y):
        ((sx, vx), (sy, vy)) = (X, Y)
        if sx == sy or vx == vy: return None
        return as_int(value(X, Y) + value(Y, X), include='+-', default=None)
      
      # deal 3 cards from the pairs <ps>
      # return cards <x1> and <x2> which is replaced by <x3>
      def deal(ps):
        for (p1, p2) in subsets(ps, size=2, select='P'):
          x1 = singleton(intersect([p1, p2]))
          if x1 is None: continue
          x2 = singleton(diff(p1, p2))
          x3 = singleton(diff(p2, p1))
          yield (x1, x2, x3)
      
      # group pairs by integer value
      g = group(subsets(pack, size=2), by=unpack(score))
      del g[None]  # remove scores we are not interested in
      
      # consider possible scores for J
      for (k, psJ) in g.items():
        if k < 0 or len(psJ) < 2: continue
        # B's score is -k
        psB = g.get(-k)
        if psB is None or len(psB) < 2: continue
      
        # consider cards for J (J1, J2 -> J3)
        for (J1, J2, J3) in deal(psJ):
          # consider cards for B (B1, B2 -> B3)
          for (B1, B2, B3) in deal(psB):
            # check deals are possible
            if len({J1, J2, J3, B1, B2, B3}) < 6: continue
            # check 4 different suits and values initially
            if any(len({J1[i], J2[i], B1[i], B2[i]}) < 4 for i in [0, 1]): continue
            # check 4 different suits finally
            if len({J1[0], J3[0], B1[0], B3[0]}) < 4: continue
            # output solution
            (J1, J2, J3, B1, B2, B3) = map(join, (J1, J2, J3, B1, B2, B3))
            printf("J: {J1}, {J2} -> {J3} [+{k}]; B: {B1}, {B2} -> {B3} [-{k}]")
      

      Solution: Jo’s final hand is: Ace of Spades, 6 of Hearts.

      The initial cards dealt were:

      Jo: Ace of Spades, 3 of Diamonds; score = (+1, +3) = +4
      Bo: 6 of Clubs, 4 of Hearts; score = (−6, +2) = −4

      The suits are: S, D, C, H and the values: 1, 3, 4, 6.

      Jo then chooses to discard 3 of Diamonds, and is dealt 6 of Hearts:

      Jo: Ace of Spades, 6 of Hearts; score = (+1, +3) = +4

      Bo then chooses to discard 4 of Hearts, and is dealt Queen (12) of Diamonds:

      Bo: 6 of Clubs, 12 of Diamonds; score = (−6, +2) = −4

      The suits are: S, H, C, D and the values: 1, 6, 6, 12.


      If Jo’s discarded card is returned to the pack, and then it can be Bo’s exchange card (rather than a “fresh” card from the pack) and this leads to a further solution, where the final values are also all different (I initially was solving the puzzle with this extra condition). So presumably that does not happen.

      The initial cards dealt were:

      Jo: Ace of Spades, 4 of Hearts; score = (+1, +2) = 3
      Bo: 5 of Clubs, 10 of Diamonds; score = (−5, +2) = −3

      The suits are: S, H, C, D and the values 1, 4, 5, 10.

      Jo then chooses to discard the 4 of Hearts (which is returned to the pack), and is dealt 2 of Diamonds:

      Jo: Ace of Spades, 2 of Diamonds; score = (+1, +2) = 3

      Bo then chooses to discard the 10 of Diamonds, and is dealt 4 of Hearts (the card discarded by Jo):

      Bo: 5 of Clubs, 4 of Hearts; score = (−5, +2) = −3

      The suits are: S, D, C, H and the values: 1, 2, 4, 5.

      Like

  • Unknown's avatar

    Jim Randell 8:13 am on 5 November 2023 Permalink | Reply
    Tags:   

    Brain-Teaser 935: Ears, noses & throats 

    From The Sunday Times, 22nd June 1980 [link]

    Our local hospital is a busy place, with a large department, luckily, to keep records of what is wrong with whom. Those who come to the ENT specialist to complain about their ears, sometimes complain about their nose or their throat.

    Of his 60 patients, the number complaining of ears and nose only is three times as great as the number complaining of ears and throat only.

    Another group consists of those who say that their ears are the only body part of the three where they are in good health; and this group is three times as large as the group which declares that the throat alone is healthy.

    It’s all very confusing, I know; but I can tell you that there are 110 complaints in all — if you count a complaint about two ears as one complaint.

    Everyone complains about at least one thing.

    How many patients come with complaints about one part of their anatomy (ears or nose or throat) only?

    This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).

    [teaser935]

     
    • Jim Randell's avatar

      Jim Randell 8:14 am on 5 November 2023 Permalink | Reply

      We can label the areas of the Venn diagram we are interested in as: E, N, T, EN, ET, NT, ENT (this accounts for all patients, as each has at least one complaint).

      We have:

      E + N + T + EN + ET + NT + ENT = 60
      EN = 3 ET
      NT = 3 EN
      E + N + T + 2(EN + ET + NT) + 3 ENT = 110

      And we want to determine the value of E + N + T.

      So:

      EN = 3 ET
      NT = 3 EN = 9 ET

      Hence:

      EN + ET + NT = 3 ET + ET + 9 ET = 13 ET

      Writing: X = E + N + T, we get:

      X + 13 ET + ENT = 60
      X + 26 ET + 3 ENT = 110

      X = ENT + 10

      And so:

      2 ENT = 50 − 13 ET

      There are 2 possibilities:

      ET = 0 ⇒ ENT = 25, E+N+T = 35
      ET = 2 ⇒ ENT = 12, E+N+T = 22

      If we suppose the phrase: “three times as great/large” in the puzzle text precludes zero values, then only a single solution remains.

      Solution: 22 of the patients have a single complaint.

      Like

  • Unknown's avatar

    Jim Randell 4:33 pm on 3 November 2023 Permalink | Reply
    Tags:   

    Teaser 3189: Telling tiles 

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

    I have four tiles with a digit written on each of them: I shall refer to these as A, B, C and D. I have rearranged the tiles in various ways to make two 2-digit numbers and I have then multiplied those two numbers together (e.g., CB times AD). In this way I have found as many answers as possible with this particular set of tiles and I have discovered that:

    1. The number of different answers is AB.
    2. Of those answers B consist of the four digits A, B, C, D in some order.
    3. There are C other 4-digit answers.

    What are A, B, C and D respectively?

    [teaser3189]

     
    • Jim Randell's avatar

      Jim Randell 5:02 pm on 3 November 2023 Permalink | Reply

      It wasn’t clear to me if 0 digits were allowed or not, or if repeated digits were allowed, so I wrote code to solve the most permissive interpretation. But it turns out there is the same unique answer whatever variation is used.

      This Python program runs in 131ms. (Internal runtime is 52ms).

      Run: [ @replit ]

      from enigma import (irange, subsets, multiset, nconcat, nsplit, singleton, printf)
      
      # choose the 4 digits
      for ds in subsets(irange(0, 9), size=4, select='R'):
        m = multiset.from_seq(ds)
      
        # find the products of 2 digit pairs (ignoring duplicates)
        ps = set(
          nconcat(a, b) * nconcat(c, d)
            for (a, b, c, d) in subsets(ds, size=4, select='mP')
        )
      
        # AB = number of different products
        n = len(ps)
        (A, B) = nsplit(n, 2)
        if not m.issuperset((A, B)): continue
      
        # count total 4-digit products = t
        # those that are rearrangements of <ds> = r
        t = r = 0
        for p in ps:
          ds_ = nsplit(p)
          if len(ds_) != 4: continue
          t += 1
          if multiset.from_seq(ds_) == m: r += 1
      
        # B = number of rearrangements
        if r != B: continue
      
        # C = number of remaining 4-digit products
        C = t - r
        # and D is the remaining digit
        D = singleton(m.difference((A, B, C)))
        if D is None: continue
      
        # output solution
        printf("(A, B, C, D) = {ds} [n={n} t={t} r={r}]", ds=(A, B, C, D))
      

      Solution: (A, B, C, D) = (1, 2, 7, 8).

      There are 12 different products:

      17 × 28 = 476
      18 × 27 = 486
      12 × 78 = 936
      12 × 87 = 1044
      18 × 72 = 1296
      17 × 82 = 1394
      21 × 78 = 1638
      21 × 87 = 1827 [*]
      28 × 71 = 1988
      27 × 81 = 2187 [*]
      71 × 82 = 5822
      72 × 81 = 5832

      2 of them [*] are rearrangements of the digits A, B, C, D, and there are 7 other 4-digit products.


      There are 55 sets of digits that satisfy condition 1, and the numbers of distinct products are: 1, 2, 4, 6, 10, 12.

      12 is the largest number of distinct products we can have, because there are 24 rearrangements of the 4 digits, but in this list each product will appear twice (as pq × rs and rs × pq).

      When we include condition 2 only 3 sets of digits survive:

      (1, 0, 4, 6) → 10 products {46, 64, 84, 244, 246, 460, 640, 840, 2440, 2460}, 0 rearrangements [2 others]
      (1, 0, 5, 9) → 10 products {59, 95, 135, 455, 459, 590, 950, 1350, 4550, 4590}, 0 rearrangements [3 others]
      (1, 2, 7, 8) → 12 products {476, 486, 936, 1044, 1296, 1394, 1638, 1827, 1988, 2187, 5822, 5832}, 2 rearrangements [7 others]

      And of these only the last set satisfies condition 3, and provides the required answer.

      Like

  • Unknown's avatar

    Jim Randell 11:20 am on 2 November 2023 Permalink | Reply
    Tags:   

    Brainteaser 1555: Riddle-me-ree 

    From The Sunday Times, 28th June 1992 [link]

    My first is a digit
    That is equal to “y”.
    My second’s not zero,
    But smaller than pi.

    My third, fourth and fifth
    You’ll find if you try,
    For my whole is the cube
    Of “x” squared plus “y”.

    “x” is an integer
    And so is “y”.
    I’ve only five digits,
    What number am I?

    [teaser1555]

     
    • Jim Randell's avatar

      Jim Randell 11:21 am on 2 November 2023 Permalink | Reply

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

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      # we are looking for: YABCD = (X^2 + Y)^3
      
      SubstitutedExpression
      
      --distinct=""
      --invalid="0,AY"
      --invalid="4|5|6|7|8|9,A"  # 2nd digit is 1, 2, 3
      
      # the 5-digit number is the cube of X^2 + Y
      "is_square(is_cube(YABCD) - Y)"
      
      --answer="YABCD"
      

      Or we can use a simple Python program:

      from enigma import (irange, nsplit, is_square, printf)
      
      # consider 5-digit cubes
      for i in irange(22, 46):
        n = i**3
        (y, a, _, _, _) = nsplit(n)
        if a == 0 or a > 3: continue
        x = is_square(i - y)
        if x is None: continue
        # output solution
        printf("{n} = ({x}^2 + {y})^3")
      

      Solution: The number is 91125.

      We have x = 6, y = 9, and:

      (6² + 9)³ = 91125

      Like

    • Ender Aktulga's avatar

      Ender Aktulga 11:12 am on 7 November 2023 Permalink | Reply

      Answer: 91125

      from math import sqrt
      
      i=0
      n=0
      while True:
          i+=1
          n=i**3
              
          if n=100000:
              break
          
          n2=int(str(n)[1])  #:[1]
          if n2==0 or n2>3:
              continue
      
          y=n//10000
          x=round(sqrt(i-y))
          if n!=(x**2+y)**3:
              continue
      
          print(f"{i=}, i^3={n=}, {x=}, i={x**2+y=}")
      
      """
      i=45, i^3=n=91125, x=6, i=x**2+y=45
      """
      

      Like

      • Jim Randell's avatar

        Jim Randell 8:21 am on 9 November 2023 Permalink | Reply

        Thanks for the comment, but if this is meant to be a Python program there seem to be some problems with it, because it doesn’t run.

        If you want to post a new comment with a revised version I can remove this version.

        When posting code it is best to wrap it in [code]...[/code] tags to make sure it appears correctly.

        Like

    • GeoffR's avatar

      GeoffR 9:53 am on 11 November 2023 Permalink | Reply

      cb5d = [x * x * x for x in range(22, 47)]
      
      for x in range(1, 10):
          for y in range(1, 10):
              n = (x * x + y) ** 3
              if n not in cb5d:continue
              # y is my first digit
              if str(n)[0] != str(y):continue
              # My second digit is not zero, but smaller than pi
              if str(n)[1] not in ('1', '2', '3'):continue
              print(f"My 5-digit number is {n}.")
      

      My 5-digit number is 91125.

      Like

    • GeoffR's avatar

      GeoffR 10:42 am on 12 November 2023 Permalink | Reply

      I updated the original 5-digit cube list to include a check on the 2nd digit of the 5-digit number. This reduced the original cube list from 25 potential candidates to 6 cubes to check.

      
      # list of 5-digit cubes with 2nd digit in range 1..3
      cb_5d = [x * x * x for x in range(22, 47)
              if str(x * x * x)[1] in ('1','2','3')]
      
      # find x and y values
      for x in range(1, 10):
          for y in range(1, 10):
              n = (x * x + y) ** 3
              # 5-digit number required
              if n < 10000:
                  continue
              if n in cb_5d:
                  # y is my first digit
                  if str(n)[0] == str(y):
                      print(f"My 5-digit number is {n}.")
      
      print(f"Candidate cube list was {cb_5d}")
      
      # My 5-digit number is 91125.
      # Candidate cube list was [12167, 13824, 21952, 32768, 42875, 91125]
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 7:39 am on 31 October 2023 Permalink | Reply
    Tags:   

    Teaser 2647: Happy medium 

    From The Sunday Times, 16th June 2013 [link] [link]

    At a recent séance Adam, Alan, Andy, Eden, Eric, Fred, Gary, Glen, John, Mike, Pete and Tony sat around a circular table of radius one metre. Around its edge there were 12 equally-spaced points, each representing a different letter of the alphabet.

    A light started at the centre, moved straight to one of the points, moved straight to another, then straight to another, and so on, before returning directly to the centre. In this way it spelt out the name of one of the people present. It then started again and in a similar fashion spelt out a day of the week. Then it started again and spelt out a month. Every straight line path that it took was a whole number of centimetres long.

    Which three words did it spell out?

    [teaser2647]

     
    • Jim Randell's avatar

      Jim Randell 7:41 am on 31 October 2023 Permalink | Reply

      See also: Enigma 595a, Enigma 146, Enigma 1369.

      The 12 points fall into two regular hexagonal orbits, and once the initial letter is selected we must stay in that particular orbit, making transitions of ±1 or 3 to move between letters. Therefore any word spelled out must not have more than 6 different letters (which eliminates 4 of the days of the week, and 3 months of the year).

      Also, if we label the letters in any particular orbit alternately as “odd” and “even”, then we can only transition from an “odd” position to an “even” position and vice versa. (So we can eliminate 3 more months which have a letter with both parities).

      So each arrangement consists of two distinct orbits, and each orbit consists of two parities, each of which consists of 3 letters. And there are 12 different letters used in an arrangement.

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

      Run: [ @replit ]

      from enigma import (update, union, disjoint_union, join, unpack, printf)
      
      # an orbit is an (<even>, <odd>) pair:
      empty = (set(), set())  # the empty orbit
      
      # possible index orders for a pair
      def orders(vs):
        yield (0, 1)
        if vs[0] != vs[1]: yield (1, 0)
      
      # add a word into an orbit
      def add(word, orbit):
        # collect the letters by even/odd parity
        ps = (set(word[0::2]), set(word[1::2]))
        # consider possible orderings to add the letters
        for (i0, i1) in orders(orbit):
          # construct new orbit
          orb = (union([orbit[0], ps[i0]]), union([orbit[1], ps[i1]]))
          # check orbit is valid
          if len(orb[0]) < 4 and len(orb[1]) < 4 and disjoint_union(orb):
            # return updated orbit
            yield orb
      
      # add a word into a pair of orbits
      def add_word(word, pair):
        # choose an orbit to add the word to
        for (i0, i1) in orders(pair):
          # attempt to add the word to the orbit
          for orb in add(word, pair[i0]):
            # check orbits are disjoint
            if disjoint_union(orb + pair[i1]):
              yield update(pair, [(i0, orb)])
      
      # find arrangements that spell out a word from each of the word lists <wss>
      def solve(wss, ws=[], orbs=(empty, empty)):
        # are we done?
        if not wss:
          yield (ws, orbs)
        else:
          # choose the next word
          for w in wss[0]:
            for orbs_ in add_word(w, orbs):
              yield from solve(wss[1:], ws + [w], orbs_)
      
      # word sets (with impossible words removed)
      words1 = "ADAM ALAN ANDY EDEN ERIC FRED GARY GLEN JOHN MIKE PETE TONY".split()
      words2 = "MONDAY FRIDAY SUNDAY".split()
      words3 = "MARCH APRIL MAY JUNE JULY AUGUST".split()
      
      # format an orbit (x, y)
      fmt = unpack(lambda x, y: join(sorted(x)) + "+" + join(sorted(y)))
      
      # solve the puzzle
      for (ws, orbs) in solve([words1, words2, words3]):
        # output solution
        printf("{ws} <- {orbs}", ws=join(ws, sep=" "), orbs=join(map(fmt, orbs), sep=", "))
      

      Solution: The words are: GLEN, FRIDAY, JUNE.

      The two orbits are:

      EGU + JLN → GLEN, JUNE
      AFI + DRY → FRIDAY

      From these we can construct many viable arrangements of letters. For example: E A J D G F L R U I N Y.

      Like

  • Unknown's avatar

    Jim Randell 3:06 pm on 27 October 2023 Permalink | Reply
    Tags:   

    Teaser 3188: Royal Mail slims down 

    From The Sunday Times, 29th October 2023 [link] [link]

    The Royal Mail, facing stiff competition, was looking for ways to streamline and simplify its operations. One dotty idea circulating in 2022 was to sell only two face values of postage stamps. Customers would then need to be able to make up 68p for a second-class letter and all values above 68p, to be ready for subsequent price rises. An obvious solution would be to sell only 1p and 68p stamps. But this would mean sticking 28 stamps on a first-class letter (costing 95p), leaving little room for the address!

    Which two stamp values would minimise the total number of stamps required to post two letters, one at 68p and one at 95p, and still allow any value above 68p to be made up?

    [teaser3188]

     
    • Jim Randell's avatar

      Jim Randell 3:23 pm on 27 October 2023 Permalink | Reply

      We have encountered similar puzzles before. (See: Enigma 1154, Enigma 1194, Enigma 228, Enigma 221, Enigma 122, Enigma 1635).

      For a pair of coprime denominations, the largest amount that cannot be expressed is known as the Frobenius number [@wikipedia], and can be calculated as:

      F(x, y) = x.y − (x + y)

      This Python program runs in 74ms. (Internal runtime is 10ms).

      Run: [ @replit ]

      from enigma import (Accumulator, coprime_pairs, express, printf)
      
      # calculate Frobenius number for coprime <x>, <y>
      F = lambda x, y: x * y - x - y
      
      # find minimal stamps to make total <t> with denominations <ds>
      stamps = lambda t, ds: min(express(t, ds), key=sum)
      
      # find minimal total
      r = Accumulator(fn=min, collect=1)
      
      # consider possible denominations
      for ds in coprime_pairs(68):
        # can we make 68p and all larger amounts?
        if F(*ds) > 67: continue
      
        # find minimal number of stamps used
        (q1, q2) = (stamps(68, ds), stamps(95, ds))
        r.accumulate_data(sum(q1) + sum(q2), ds)
      
      # output solution
      printf("denominations = {r.data} [{r.value} stamps]")
      

      Solution: The minimal number of stamps required is with denominations of 2p and 31p.

      With these denominations we can make the required amounts as:

      68p = 2× 31p + 3× 2p = 5 stamps
      95p = 3× 31p + 1× 2p = 4 stamps
      total = 9 stamps

      These denominations can make all values from 30p upwards.

      Like

  • Unknown's avatar

    Jim Randell 5:18 pm on 26 October 2023 Permalink | Reply
    Tags:   

    Teaser 2128: Goodbye, Norma Jean(e) 

    From The Sunday Times, 29th June 2003 [link]

     

    She was originally Norma Jean Baker, then she called herself Norma Jeane, before becoming the much-loved Marilyn Monroe. It is more than 40 years since she died and yet people still remember her fondly. Today we celebrate her life once again in the above addition sum, where each letter stands consistently for a different non-zero digit and O is prime.

    What is the numerical value of JAMBOREE?

    [teaser2128]

     
    • Jim Randell's avatar

      Jim Randell 5:18 pm on 26 October 2023 Permalink | Reply

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

      The following run file executes in 75ms. (Internal runtime of the generated code is 144µs).

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression.split_sum
      
      # digits are non-zero
      --invalid="0,ABEJKMNOR"
      # O is a single-digit prime = {2, 3, 5, 7}
      --invalid="1|4|6|8|9,O"
      
      "NORMA + JEAN = BAKER"
      
      --answer="JAMBOREE"
      

      Solution: JAMBOREE = 94735611.

      And the unencoded sum is:

      25674 + 9142 = 34816

      Like

  • Unknown's avatar

    Jim Randell 9:04 am on 24 October 2023 Permalink | Reply
    Tags:   

    Teaser 2664: Time ‘n’ again 

    From The Sunday Times, 13th October 2013 [link] [link]

    Time and again at school we would be given the exercise of changing a fraction into a decimal.

    This time the given fraction is in its simplest form and it equals a recurring decimal. In some places the digits have been consistently replaced by letters, with different letters used for different digits, but in four places the digits have merely been replaced by asterisks:

    TIME / **N** = .AGAINAGAIN

    Numerically, what is the TIME?

    [teaser2664]

     
    • Jim Randell's avatar

      Jim Randell 9:06 am on 24 October 2023 Permalink | Reply

      The fraction:

      TIME / **N**

      is in its lowest form, but may also be written:

      AGAIN / 99999

      So it follows that the numerator and denominator of the first fraction can be scaled up by an integer value (k) to give the numerator and denominator of second fraction.

      And k must be a 1-digit divisor of 99999.

      The only possible values are k=3 or k=9, meaning

      **N** = 33333
      **N** = 11111

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --invalid="0,NT"
      --invalid="2|4|5|6|7|8|9,N"  # N is 1 or 3
      
      # TIME / NNNNN is in lowest terms
      "gcd(TIME, NNNNN) = 1"
      
      # TIME scales up to give AGAIN
      "div(N * AGAIN, 9) = TIME"
      
      --answer="TIME"
      

      Solution: TIME = 5269.

      And we have:

      TIME / **N** = .(AGAIN)… → 5269 / 11111 = 0.(47421)…

      Like

      • Hugo's avatar

        Hugo 1:36 pm on 24 October 2023 Permalink | Reply

        99999 = 3² × 41 × 271. 11111 = 41 × 271.
        41 is the smallest value of n such that multiples and submultiples of 1/n recur with period five.

        5269 = 11 × 479, and AGAIN = 47421 is 9 times as much.

        Like

  • Unknown's avatar

    Jim Randell 4:17 pm on 20 October 2023 Permalink | Reply
    Tags: ,   

    Teaser 3187: Wired squares 

    From The Sunday Times, 22nd October 2023 [link] [link]

    Sitting at his study desk one morning, Ted noticed a paperclip poking out of his notebook, unbent to a straight thin wire. Opening it up he found that his granddaughter Jessica had drawn a square grid (less than 14 cm side width) on one of the pages, with vertical and horizontal grid lines at 1 cm intervals.

    Musing this, Ted numbered each cell consecutively from 1, working left to right along each row from top to bottom in turn. Moving the wire over the page, he rested the wire over (or touching the corner of) some cells containing a square number. Placing the wire carefully, he was able to connect as many square-numbered cells as possible in this way. No square grid of less than 14 cm side width could have allowed the connection of a larger number of squares.

    What squares did he connect?

    When originally posted on The Sunday Times website the upper limit was “less than 15 cm”, but this was revised down to “less than 14 cm” when the paper was published.

    [teaser3187]

     
    • Jim Randell's avatar

      Jim Randell 8:06 am on 21 October 2023 Permalink | Reply

      Initially I drew out the various grids and looked for straight lines that could connect cells. (I am assuming the wire forms a sufficiently long straight line).

      This program examines lines that pass through the corners of square-numbered cells (so it may not find all possible lines), but it does better than I did on my manual attempt.

      It runs in 664ms.

      Run: [ @replit ]

      from enigma import (fdiv, inf)
      
      # find the slope and intercept of a line defined by points p1, p2
      def line_slope_intercept(p1, p2, div=fdiv):
        ((x1, y1), (x2, y2)) = (p1, p2)
        if x1 == x2: return (inf, x1)
        m = div(y2 - y1, x2 - x1)
        c = y1 - m * x1
        return (m, c)
      
      
      from enigma import (
        Accumulator, irange, subsets, tuples, line_intersect, catch, uniq,
        unpack, rdiv, seq2str, printf
      )
      
      # return the corners of a square
      corners = lambda x, y: [(x, y), (x + 1, y), (x + 1, y + 1), (x, y + 1)]
      
      # solve for an n x n grid
      # return max hits
      def solve(n, verbose=1):
        # calculate the corners of each square cell
        sqs = dict()
        pts = set()
        for i in irange(1, n):
          k = i * i
          (y, x) = divmod(k - 1, n)
          sqs[k] = (x, y)
          pts.update(corners(x, y))
      
        # find maximal hits
        ss = Accumulator(fn=max, collect=1)
      
        # consider lines that pass through two of the points
        fn = unpack(lambda p1, p2: line_slope_intercept(p1, p2, div=rdiv))
        for (p1, p2) in uniq(subsets(pts, size=2), fn=fn):
          # which squares are hit
          hit = set()
          for (k, (x, y)) in sqs.items():
            for (c1, c2) in tuples(corners(x, y), 2, circular=1):
              r = catch(line_intersect, p1, p2, c1, c2, internal=2, div=rdiv)
              if r is None: continue
              hit.add(k)
              break
      
          ss.accumulate_data(len(hit), tuple(sorted(hit)))
      
        # output information
        if verbose:
          printf("[n={n}]")
          printf("max hits = {ss.value}")
          for hit in uniq(ss.data):
            printf("-> {hit}", hit=seq2str(hit, enc="[]"))
          printf()
      
        # return max hits
        return ss.value
      
      # consider grids up to size 13
      r = Accumulator(fn=max, collect=1)
      for n in irange(1, 13):
        h = solve(n)
        r.accumulate_data(h, n)
      printf("max hits = {r.value} on grids {r.data}")
      

      Solution: The connected cells are: 1, 4, 16, 36, 49, 64.

      My answer to the originally posted puzzle (with an upper limit of “less than 15 cm”) was the same, and is as follows:

      The program finds two grids (up to 14×14) that allow a maximal 6 square-numbered cells to be connected, shown below:

      (Although the program looks for “reasonable” solutions, it is possible there may be a line with more than 6 connections that is not found by the program).

      However the puzzle is looking for a single unique solution.

      The first (13×13) grid needs a wire of around 11.3 cm in length to link the cells, and the second (14×14) grid needs a wire of around 14 cm to link the cells.

      So if the length of wire is between these values, we can link the cells in the 13×13 grid, but not the 14×14 grid.

      I unbent a couple of paperclips, and found the smaller one used around 10 cm of wire, and the big one about 16 cm. So it is plausible that a paperclip could fall between the two required values, leaving the 13×13 grid as the unique answer.

      In the revised puzzle, only the 13×13 grid is considered, so this must provide the answer. (Which makes me wonder why the puzzle has all the stuff about straightening the paperclip at all).


      The [[ line_slope_intercept() ]] function is included in the latest version of enigma.py.

      Like

  • Unknown's avatar

    Jim Randell 9:21 am on 19 October 2023 Permalink | Reply
    Tags:   

    Teaser 2643: Pack points 

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

    Five cubs were awarded points for effort. Enid’s son and Master Smith had 17 points between them, Masters Jones and Robinson had a total of 16, Ivan and Master Robinson together had 14, and the two youngest of the cubs had a total of 13. John and Master Brown had a total of 12 points, Brenda’s son and Mike had 11 points between them, Ken and his best friend had a total of 10, Ann’s son and Debbie’s son together had 9, Christine’s son and Nigel had a total of 8, and Master Perkins and Debbie’s son together had 6.

    In alphabetical order of their mothers’ Christian names, what were the names of the five cubs (Christian name and surname)?

    [teaser2643]

     
    • Jim Randell's avatar

      Jim Randell 9:21 am on 19 October 2023 Permalink | Reply

      If we use variables A, B, C, D, E to correspond to the points allocated (according to mothers name), then we can allocate the first names and surnames of the cubs to the same set of variables to give us a set of simultaneous linear equations, which we can attempt to solve to give the points values.

      I used the [[ Matrix.linear() ]] solver from the enigma.py library to find assignments that give non-negative points values. (Although there are no further solutions if negative values are permitted).

      I also ensure that the pairings mentioned in the text all consist of distinct cubs. (Although, again, there are no further solutions if a pairing can refer to the same cub twice).

      The following Python program runs in 375ms.

      Run: [ @replit ]

      from enigma import (Matrix, subsets, catch, printf)
      
      # suppose the points are: A, B, C, D, E (according to mothers name)
      # then we can map the first and surnames to these values
      
      # variables
      vs = "ABCDE"
      
      # a function to construct equations
      def eq(vs, k, fn=Matrix.equation(vs)):
        r = fn(vs, k)
        assert {0, 1}.issuperset(r[0])  # reject coefficients that aren't 0 or 1
        return r
      
      # validate point values
      def valid(x):
        assert not x < 0
        return x
      
      # for firstnames I, J, K, M, N
      for (I, J, K, M, N) in subsets(vs, size=len, select='P'):
      
        # for surnames P, R, S, T (= Brown), U = (Jones)
        for (P, R, S, T, U) in subsets(vs, size=len, select='P'):
      
          # try to solve the equations
          r = None
          try:
            # construct equations
            eqs = [
              eq(['E', S], 17),  # Enid + Smith = 17
              eq([U, R], 16),  # Jones + Robinson = 16
              eq([I, R], 14),  # Ivan + Robinson = 14
              eq([J, T], 12),  # John + Brown = 12
              eq(['B', M], 11),  # Brenda + Mike = 11
              eq(['A', 'D'], 9),  # Ann + Debbie = 9
              eq(['C', N], 8),  # Christine + Nigel = 8
              eq([P, 'D'], 6),  # Perkins + Debbie = 6
            ]
      
            # solve the equations
            r = Matrix.linear(eqs, valid=valid)
          except (ValueError, AssertionError):
            pass
          if r is None: continue
      
          # check there is a pair = 13 (two youngest), and a pair = 10 (Ken + friend)
          v = dict(zip(vs, r))
          if not any(v[x] + v[K] == 10 for x in vs if x != K): continue
          if not any(v[x] + v[y] == 13 for (x, y) in subsets(vs, size=2)): continue
      
          # output solution
          mn = { 'A': 'Anne', 'B': 'Brenda', 'C': 'Christine', 'D': 'Debbie', 'E': 'Enid' }
          fn = { I: 'Ivan', J: 'John', K: 'Ken', M: 'Mike', N: 'Nigel' }
          sn = { P: 'Perkins', R: 'Robinson', S: 'Smith', T: 'Brown', U: 'Jones' }
          for k in vs:
            printf("{fn} {sn} = {v} points [{mn}]", mn=mn[k], fn=fn[k], sn=sn[k], v=v[k])
          printf()
      

      Solution: The names of the cubs are:

      Mike Smith = 7 points [Anne]
      Ivan Perkins = 4 points [Brenda]
      Ken Jones = 6 points [Christine]
      Nigel Brown = 2 points [Debbie]
      John Robinson = 10 points [Enid]

      So the youngest two are Mike and Ken (= 13 points in total), and Ivan must be Ken’s best friend (= 10 points in total).

      Like

  • Unknown's avatar

    Jim Randell 7:46 am on 15 October 2023 Permalink | Reply
    Tags:   

    Teaser 2644: Route canal 

    From The Sunday Times, 26th May 2013 [link] [link]

    Stephen is planning a 70-mile canal trip from the lock at Aytown to the lock at Beeswick, stopping at a pub at a lock over half way along the route. He has listed the number of miles from each lock to the next, the largest being the stretch from the pub to the next lock. He has also noted the distances from Aytown to the pub and from the pub to Beeswick. All the numbers that he has written down are different primes.

    Stephen can use his figures to work out the number of miles from any lock to any other. He’s found that, whenever that number of miles between locks is odd, then it is also a prime.

    What (in order) are the numbers of miles between consecutive locks?

    [teaser2644]

     
    • Jim Randell's avatar

      Jim Randell 7:46 am on 15 October 2023 Permalink | Reply

      This Python program runs in 55ms. (Internal runtime is 806µs).

      Run: [ @replit ]

      from enigma import (irange, primes, express, diff, cproduct, subsets, flatten, printf)
      
      # decompose total <t> into different values from <ns>
      def decompose(t, ns):
        if not ns: return
        for qs in express(t, ns, qs=(0, 1)):
          yield list(n for (n, q) in zip(ns, qs) if q > 0)
      
      # check all odd distances are prime
      def check(ds):
        for (i, j) in subsets(irange(len(ds)), size=2):
          d = sum(ds[i:j + 1])
          if d % 2 == 1 and d not in primes: return False
        return True
      
      # consider possible distances from P to B (prime, < 35)
      for d2 in primes.between(2, 34):
        # distance from A to P (prime, > 35)
        d1 = 70 - d2
        if d1 not in primes: continue
      
        # decompose the distances into individual sections
        for d2s in decompose(d2, list(primes.between(2, d2 - 1))):
          max_d = d2s[-1]
          for d1s in decompose(d1, list(diff(primes.between(2, max_d - 1), d2s))):
      
            # construct possible ordered sequences
            for (s1, s2) in cproduct(subsets(s, size=len, select='P') for s in (d1s, d2s[:-1])):
              ds = flatten([s1, [max_d], s2])
              if check(ds):
                # output solution
                printf("{ds}")
      

      Solution: The distances between consecutive locks (from A to B) are (in miles): 17, 13, 11, 19, 7, 3.

      Like

  • Unknown's avatar

    Jim Randell 4:25 pm on 13 October 2023 Permalink | Reply
    Tags:   

    Teaser 3186: Contemporary classmates 

    From The Sunday Times, 15th October 2023 [link] [link]

    In our local school, the year begins on September 1 and ends on August 31, and class one contains all the children who reach the age of five during the school year. I was looking back at the school records and discovered that one year during the 1980s, it so happened that the birthdays of a group of children in class one fell on the same-numbered day of different months and also on the same day of the week. The size of this group was the largest possible for these circumstances, and by coincidence the number of classmates in the group was also the number of the day in the month for their birthday.

    The oldest member of that group, whose birthday was in January, was born in the 1980s.

    What was the full date of birth of the youngest member of that group?

    [teaser3186]

     
    • Jim Randell's avatar

      Jim Randell 4:54 pm on 13 October 2023 Permalink | Reply

      This Python program finds maximal groups of birthdays sharing day-of-month and day-of-week for academic years starting in the 1980s, and then looks for maximal groups satisfying the remaining conditions.

      It runs in 76ms. (Internal runtime is 4.6ms).

      Run: [ @replit ]

      from datetime import (date, timedelta)
      from enigma import (defaultdict, Accumulator, irange, repeat, inc, seq2str, printf)
      
      # collect all maximal groups
      gs = Accumulator(fn=max, collect=1)
      
      # consider school years that start in the 80s
      for y in irange(1980, 1989):
      
        # record (day-of-month, day-of-week) -> [date, ...]
        g = defaultdict(list)
        for d in repeat(inc(timedelta(days=1)), date(y, 9, 1)):
          if d.year > y and d.month > 8: break
          g[(d.day, d.isoweekday())].append(d)
      
        # look for the longest lists
        r = Accumulator(fn=max, collect=1)
        for (k, vs) in g.items():
          r.accumulate_data(len(vs), k)
      
        # record them
        for k in r.data:
          gs.accumulate_data(r.value, g[k])
      
      # maximal group size
      n = gs.value
      printf("max group = {n}")
      
      # look for maximal groups where day-of-month = n
      # and the oldest member of the group has a birthday in January
      for ds in gs.data:
        d = ds[0]
        if not (d.day == n and d.month == 1): continue
        # calculate the dates of birth
        bs = list(d.replace(year=d.year - 5) for d in ds)
        # check the eldest is born in the 80s
        if not bs[0].year // 10 == 198: continue
        # output solution
        printf("birthdates = {bs}", bs=seq2str(bs))
      

      Solution: The youngest member of the group was born on: 3rd July 1983.

      The largest group of dates in a year that share the same day of the week and day of the month is 3.

      The relevant school year starts on 1st September 1987, and the 3 birthdays are:

      Sunday, 3rd January 1988 (DOB: Monday, 3rd January 1983)
      Sunday, 3rd April 1988 (DOB: Sunday, 3rd April 1983)
      Sunday, 3rd July 1988 (DOB: Sunday, 3rd July 1983)

      Like

  • Unknown's avatar

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

    Teaser 2656: Wrong adding 

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

    Here is an addition sum in which digits have consistently been replaced by letters, with different letters used for different digits. The six-figure total is even:

    Unfortunately, once again I have made a mistake. In just one place in the display I have written an incorrect letter.

    What is the correct numerical value of the six-figure total?

    [teaser2656]

     
    • Jim Randell's avatar

      Jim Randell 12:31 pm on 12 October 2023 Permalink | Reply

      I’ve added the [[ bungled_sum() ]] solver (from Puzzle 56 etc.) to the enigma.py library as a class method on [[ SubstitutedSum ]], and also allowed it to be called from the command line.

      Using this solver we find there are two possibilities for as single letter mistake in the translation.

      The following command line executes in 270ms.

      Run: [ @replit ]

      % python3 -m enigma -r SubstitutedSum.bungled_sum "AGAIN + WRONG = ADDING"
      
                  G
      AGAIN + WRONI = ADDING / @[1,4] G -> I
      14195 + 86759 = 100954 / A=1 D=0 G=4 I=9 N=5 O=7 R=6 W=8
      
                           G
      AGAIN + WRONG = ADDINA / @[2,5] G -> A
      16195 + 84756 = 100951 / A=1 D=0 G=6 I=9 N=5 O=7 R=4 W=8
      

      But we are told that in the original numerical sum the result is even, so we are looking at the first possibility.

      Solution: The result of the sum is 100954.

      The original sum was: 14195 + 86759 = 100954.

      And it should have been encoded as: AGAIN + WRONI = ADDING.

      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