Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 8:24 am on 25 March 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 717: Wrong hands 

    From The Sunday Times, 13th April 1975 [link]

    Waking in the night I glanced at my bed-side clock and thought it indicated just after 2:20. Putting on my spectacles and looking more carefully I saw that it was actually just after 4:10. I had, of course, interchanged the hands at my first glance.

    I began to wonder what time around then that my observation would have occurred, to the exact fraction of a minute, if the hands could have been exactly interchanged.

    What do you think?

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

    [teaser717]

     
    • Jim Randell's avatar

      Jim Randell 8:26 am on 25 March 2021 Permalink | Reply

      I we divide the circle of the clock face into 60 sectors, then the minute has travels at 1 sector/minute, and the hour hand at 1/12 sector/minute.

      If we suppose the difference (in minutes) between the times is d, then the hour hand has moved a distance d / 12 sectors (which is roughly 1/6 of the circle), and the minute hand has moved distance d sectors (and roughly twice – 1/6 times around the circle):

      d = 120 − (d / 12)
      d = 1440 / 13
      d = 110 + 10/13

      So the difference between the times is nearly 111 minutes, about what we would expect.

      If we suppose the actual time is m minutes after 4 o’clock (i.e. 240 + m minutes), then the minute hand will be showing m minutes, which is m sectors around the circle.

      And at the mistaken time = (240 + m − d) the hour hand would be showing the same point:

      (240 + m − d) / 12 = m
      240 − d = 11m
      m = (240 − d) / 11
      m = 1680 / 143
      m = 11 + 107/143

      Solution: The exact time would be 11 + 107/143 minutes after 4 o’clock.

      Like

    • Hugh Casement's avatar

      Hugh Casement 1:45 pm on 25 March 2021 Permalink | Reply

      I make that 11 minutes, 44 and 128/143 seconds,
      mistaken for 20 minutes, 58 and 106/143 seconds (20 and 140/143 minutes) after 2.

      There’s a lot to be said for digital clocks, especially in the middle of the night!

      Like

  • Unknown's avatar

    Jim Randell 9:07 am on 23 March 2021 Permalink | Reply
    Tags:   

    Teaser 2781: Number time 

    From The Sunday Times, 10th January 2016 [link] [link]

    I have written down some numbers and then consistently replaced digits by capital letters, with different letters used for different digits. In this way my numbers have become:

    TRIPLE (which is a multiple of three)
    EIGHT (which is a cube)
    NINE (which is divisible by nine)
    PRIME (which is a prime)

    What is the number TIME?

    [teaser2781]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 23 March 2021 Permalink | Reply

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

      The following run file executes in 122ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      "TRIPLE % 3 = 0"
      "is_cube(EIGHT)"
      "NINE % 9 = 0"
      "is_prime(PRIME)"
      
      --answer="TIME"
      

      Solution: TIME = 3901.

      There are 4 solutions to the alphametic expressions, but the value for TIME is the same in all of them.

      Like

    • Frits's avatar

      Frits 12:39 pm on 23 March 2021 Permalink | Reply

      With a complicated formula for N.

      from enigma import is_prime
      
      alldiff = lambda seq: len(seq) == len(set(seq))
      
      # EIGHT is a cube
      for i in range(22, 47):
        s3 = str(i ** 3)
        E = int(s3[0])
        I = int(s3[1])
        T = int(s3[-1])
        if T == 0: continue             
        if E % 2 == 0: continue  # E may not be even (due to PRIME)
        if not alldiff(s3): continue
        
        # NINE is a multiple of 9
        # sumIE plus 2*N must be equal to k*9   (k is 1,2 or 3)
        sumIE = I + E
        N = (9 + 18 * (sumIE > 9) - sumIE) // 2 if sumIE % 2 else (18 - sumIE) // 2  
        if N == 0 or not alldiff(s3 + str(N)): continue   
        
        # TRIPLE is a multiple of 3
        LMPR = [x for x in range(10) if str(x) not in s3 + str(N)]
        candsM = [x for x in LMPR if (sumIE + T + sum(LMPR) - x) % 3 == 0]
         
        for M in candsM:
          LPR = [x for x in LMPR if x != M]
          for L in LPR:
            # PRIME may not be a multiple of 3
            if (sumIE + M + sum(LPR) - L) % 3 == 0: continue
            PR = [x for x in LPR if x != L]
            # consider permutations of PR
            for (P, R) in zip(PR, PR[::-1]):
              if P == 0: continue
              PRIME = 10000*P + 1000*R + 100*I + 10*M + E
              if not is_prime(PRIME): continue
              print(f"TIME={T}{I}{M}{E}")
      

      Like

  • Unknown's avatar

    Jim Randell 10:41 pm on 21 March 2021 Permalink | Reply
    Tags: by: Mrs. K. Y. Gunson   

    Brain-Teaser 27: Foggy birthday 

    From The Sunday Times, 24th September 1961 [link]

    Mr and Mrs Herbert Fogg, in celebration of Herbert’s forty-third birthday, invited three friends, Ann, Bill, and Cuthbert, to dinner.

    “Of course, Herbert is quite a bit older than any one of you”, Mrs Fogg declared to the three guests. Herbert frowned.

    “It’s odd”, Ann quickly interposed, “if you multiply Bill’s and Cuthbert’s ages together, then divide by Herbert’s age the remainder is just my age”.

    “Whereas if you multiply together Ann’s and Cuthbert’s ages, again divide by Herbert’s age, then the remainder is just how old I was a year ago”, added Bill.

    “Not so simple for me”, said Cuthbert, the youngest present. “If you multiply together Ann’s and Bill’s ages, divide by Herbert’s, then the remainder is just one and a half times my age”.

    “Very interesting”, Herbert reflected. “In fact, from what you have told me it may be possible to find all three of your ages, if I knew how to start!”

    Show that Herbert was almost right and find the exact ages of Ann, Bill, and Cuthbert.

    [teaser27]

     
    • Jim Randell's avatar

      Jim Randell 10:42 pm on 21 March 2021 Permalink | Reply

      This Python program runs in 49ms.

      Run: [ @replit ]

      from enigma import irange, printf
      
      # H's age
      H = 43
      
      # choose C's age (the youngest)
      for C in irange(10, H - 3, step=2):
        # choose B's age
        for B in irange(C + 1, H - 1):
          # calculate A's age (using A's statement)
          A = (B * C) % H
          if not(A > C and A != B): continue
          # check B's and C's statements
          if not(B - 1 == (A * C) % H): continue
          if not(3 * C == 2 * ((A * B) % H)): continue
      
          # output solution
          printf("A={A} B={B} C={C}")
      

      Solution: Ann is 27. Bill is 25. Cuthbert is 20.

      Like

    • Frits's avatar

      Frits 10:02 am on 22 March 2021 Permalink | Reply

      % A Solution in MiniZinc 
       
      % ages
      var 11..42:A; var 11..42:B; var 10..38:C; var 43..43:H;
       
      constraint C < A /\ C < B /\ A + 2 < H /\ B + 2 < H;
       
      constraint (B * C) mod H = A;
      constraint (A * C) mod H + 1 = B;
      constraint 2 * ((A * B) mod H) = 3 * C;
       
      solve satisfy;
       
      output [ "Ann is " ++ show(A)]
      ++ [ "\nBill is " ++ show(B)]
      ++ [ "\nCuthbert is " ++ show(C)];
      

      Like

    • John Crabtree's avatar

      John Crabtree 4:16 pm on 23 March 2021 Permalink | Reply

      1. BC = A mod 43
      2. AC = B – 1 mod 43
      3. AB = 3C / 2 mod 43 with C even and C < 30
      Combining Equations 1 and 3 leads to B^2 = 23 mod 43
      Equations 1, 2 and 3 give ABC = A^2 = B^2 – B = 23 – B = 3C^2 / 2
      One can also show that (A ^2 – 23)^2 = 23 mod 43 and (C^2 – 1)^2 = 15 mod 43

      The easiest way forward is to start with (C^2 – 1)^2 = 15 mod 43
      (C^2 – 1) = 12 mod 43 (C = 20 or 23), or (C^2 – 1) = 31 mod 43 with no value of C
      And so C = 20
      B = 23 – 3C^2 / 2 mod 43 = 25
      A = BC mod 43 = 27
      This would make for a simple and fast program. It can also be done by hand.

      Starting with B^2 = 23 mod 43 gives:
      B = 18, A^2 = 5 mod 43, with no possible values of A < 22
      Also B = 43 – 18 = 25, when A^2 = -2 = 41 mod 43
      ..A = 16 gives 3C / 2 = 13 which is invalid (and C = 23 mod 43)
      ..A = 43 – 16 = 27 gives 3C / 2 = 30, ie C = 20.

      Ann is 27, Bill is 25 and Cuthbert is 20.

      The information that C is the youngest is redundant, although it does reduce the solution space for a brute force search.
      This is an interesting teaser.

      Like

  • Unknown's avatar

    Jim Randell 4:40 pm on 19 March 2021 Permalink | Reply
    Tags:   

    Teaser 3052: Porcus facit griphus 

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

    “Argent bend sinister abased sable in dexter chief a hog enraged proper” blazons our shield (shaped as a square atop a semi-circle, with a 45° diagonal black band meeting the top corner). We’ve three shields. For the first, in centimetres, the top width (L) is an odd perfect cube and the vertical edge height of the band (V) is an odd two-figure product of two different primes. The others have, in inches, whole-number L (under two feet) and V values (all different). For each shield, the two white zones have almost identical areas. All three V/L values, in percent, round to the same prime number.

    Give the shortest top width, in inches.

    [teaser3052]

     
    • Jim Randell's avatar

      Jim Randell 6:00 pm on 19 March 2021 Permalink | Reply

      The puzzle is set by Stephen Hogg.

      This Python routine calculates the V/L ratio where the areas are equal numerically, and then looks for permissible L values for the shield measured in centimetres.

      We then look for integer V values within 10% of the “ideal” value, that have 2 different prime factors, and give a rounded percentage that is a prime.

      We then look for multiple shields that have an L value less than 24 inches, and a corresponding V value that give the same rounded percentage.

      This Python program runs in 50ms.

      Run: [ @replit ]

      from math import (pi, sqrt, atan2)
      from enigma import (find_value, intf, intc, Accumulator, irange, divf, divc, fdiv, group, item_from, is_prime, factor, iroot, powers, printf)
      
      # calculate the difference between the upper and lower areas
      def areas(V, L):
        # lower area: triangular bit
        t = L - V
        T = 0.5 * t * t
        # lower area: semi-circular bit
        a = 0.5 * L
        b = a - V
        a2 = a * a
        b2 = b * b
        # solve: x^2 + y^2 = a^2, y = x - b for x, y
        r = sqrt(2 * a2 - b2)
        x = 0.5 * (r + b)
        y = 0.5 * (r - b)
        theta = pi - atan2(y, x)
        # calculate the areas
        upper = a * L
        lower = T + 0.5 * (a2 * theta + b * y)
        # return the difference in areas
        return fdiv(2 * abs(upper - lower), upper + lower)
      
      # find the V/L ratio when the white areas are equal
      r0 = find_value((lambda v: areas(v, 1)), 0, 0.25, 0.50).v
      printf("[r0 = {r0}]")
      
      # generate (V, L, rounded V/L percentage) values
      def generate(Ls):
        for L in Ls:
          # calculate exact V
          v = r0 * L
          # allow a tolerance of 10%
          for V in irange(intc(0.9 * v), intf(1.1 * v)):
            # calculate the rounded V/L percentage
            p = divf(200 * V + L, 2 * L)
            if not is_prime(p): continue
            # return values
            yield (V, L, p)
      
      # group (V, L, p) values by the percentage, for the shields measured in inches
      d = group(generate(irange(2, 23)), by=item_from("p", "V, L, p"))
      
      # find the shield measured in centimetres
      M = iroot(divc(100, 0.9 * r0), 3)
      for (V, L, p) in generate(powers(3, M, 3, step=2)):
        # V is an odd, 2-digit number
        if V % 2 == 0 or V < 10 or V > 99: continue
        # V should have 2 prime factors
        fs = factor(V)
        if not (len(fs) == 2 and len(set(fs)) == 2): continue
      
        # look for shields measured in inches
        vs = d.get(p)
        if (not vs) or len(vs) < 2: continue
      
        # output solution
        r = Accumulator(fn=min)
        inch = lambda x: fdiv(x, 2.54)
        printf("V/L ~ {p}%")
        printf("-> 1: L = {L}cm ({Li:.2f}in), V = {V}cm ({Vi:.2f}in) [error = {r:.6f}]", Li=inch(L), Vi=inch(V), r=areas(V, L))
        r.accumulate(inch(L))
        for (Vi, Li, _) in vs:
          printf("-> 2: L = {Li}in, V = {Vi}in [error = {r:.6f}]", r=areas(Vi, Li))
          r.accumulate(Li)
        printf("=> min L = {r:.2g}in", r=r.value)
        printf()
      

      Solution: The shortest L value is: 17 inches.

      The measurements for each of the three shields are:

      L = 125cm (49.2in), V = 51cm (20.1in)
      L = 17in, V = 7in
      L = 22in, V = 9in
      V/L ≈ 41%

      The program calculates a more precise ratio for equal areas: V/L = 0.408083900721218.


      In order to calculate the area of the lower white area on the shield, I started with a polynomial approximation based on the three values at V=0, V=L/2, V=L.

      This is sufficient to find the answer (as, I suspect, are many other rough estimates).

      But for the program above I came up with a a way of calculating the area exactly, based on the following diagram:

      The shield is turned upside down and we place x,y axes through the centre of the semicircle.

      Then using: a = L/2, b = L/2 − V

      The semicircle is part of the circle: x² + y² = a²

      The (now) upper line of the stripe is the line: y = x − b

      And these meet at: (x, y) = ((r + b)/2, (r − b)/2), where: r = √(2a² − b²)

      So we can calculate the angle θ = arctan(y, x)

      And the area of the yellow triangle = (1/2)ab sin(θ) = by/2

      The orange region is then the area of the sector of the circle that subtends the angle θ, less the area of the yellow triangle.

      And once we know the area of the orange region the area of the (formerly) lower region of the shield can be easily determined.

      Like

      • Tony Brooke-Taylor's avatar

        Tony Brooke-Taylor 10:13 am on 22 March 2021 Permalink | Reply

        I found you can get away with approximating away the arctan term and I eliminated the need to set a tolerance by finding the valid V/L ratios closest to the target prime. However, my code takes 40 times longer to run than yours Jim so I have some other work to do!

        Like

        • Tony Brooke-Taylor's avatar

          Tony Brooke-Taylor 10:56 am on 22 March 2021 Permalink | Reply

          … so it takes 1/4s to import numpy and 5ms for the rest of my program to run. Now I know why people bother with the math library.

          Like

    • Robert Brown's avatar

      Robert Brown 8:25 pm on 20 March 2021 Permalink | Reply

      The information about equal white zones is redundant. There is only one solution to the metric shield, which defines the prime. Then there are only two possible imperial solutions where v/a rounds to give the same prime. That solves the teaser. It’s good of him to tell us that all 3 solutions have white zones with ‘almost’ identical areas, but it adds nothing to the teaser solution.

      Like

      • Jim Randell's avatar

        Jim Randell 9:04 pm on 20 March 2021 Permalink | Reply

        @Robert: If we didn’t have the “equal area” constraint what would stop a solution like this:

        V/L ≈ 31%
        L = 125cm, V = 39cm
        L = 13in, V = 4in
        L = 16in, V = 5in

        I think we need the additional constraint to eliminate such solutions (in this one the areas differ by about 16%).

        But you don’t need to be completely accurate. A rough estimate will get you to the answer.

        Like

    • Hugh Casement's avatar

      Hugh Casement 7:04 am on 21 March 2021 Permalink | Reply

      I thought I knew a bit about heraldry, but had never come across the term ‘abased’. However, it’s clear that the bend has slipped down so that its upper edge, rather than its centre line, meets the corner of the shield. ‘Enraged’ is surely an invention, n’est-ce pas?

      The area of white space must mean before the piglet has flown in. Then it’s a quick back-of-the-envelope calculation — no computer needed. Teasers are seldom so easy.

      Like

    • Hugh Casement's avatar

      Hugh Casement 11:17 am on 21 March 2021 Permalink | Reply

      Whatever S. Hogg’s ability to blazon may be, his Latin is shocking!
      As every schoolboy knows, a direct object goes in the accusative: porcus imitat gryphem (for example).
      Better might be porci volent, though I’m not sure how far we can trust the googly translator.

      Like

  • Unknown's avatar

    Jim Randell 8:28 am on 18 March 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 702: That’s torn it … again 

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

    Last time it was vertical. But no one could accuse Uncle Bungle of being consistent and this time it was horizontal. The way, I mean, in which he tore the piece of paper on which were written the details of the matches between 4 local football teams, ABC, and D, who are to play each other once.

    All that was left was:

    It is not known whether all the matches have been played. And not more than 7 goals were scored in any game.

    With the information that it is possible to discover the score in each match, you should be able to discover them.

    What was the score in each match?

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

    [teaser702]

     
    • Jim Randell's avatar

      Jim Randell 8:29 am on 18 March 2021 Permalink | Reply

      I wasn’t entirely happy about this puzzle, but after some thought I was able to convince myself that there is a way to arrive at a single solution.

      We are told that there is enough information to determine the scores in each match (even though on the face of it it seems that we have not), so that fact it is possible to determine the scores means that there must be some ambiguous situations that cannot arise.

      We have no information at all about C and D, so any solution that we find will be equally applicable if C and D’s labels are swapped over. But as we are told we can determine the scores in the matches, this must mean swapping the labels of C and D would have no effect on the scores, so C and D must have performed identically.

      From this we can see that the C vs D match cannot be won by either of the teams, so it must be a draw, or not yet played. If it were a draw, then it could be 0-0, 1-1, 2-2, 3-3 and we don’t know which. So the C vs D match must be not yet played.

      We know A and B have both played each of the other teams (as they have played 3 matches each), so all 6 matches involving A and B must have been played. And so the A vs B and A vs C matches must have identical scores, as must the B vs C and B vs D matches.

      So we only need to calculate three scorelines: A vs B (win), A vs (C and D) (draw), B vs (C and D) (win).

      This Python program runs in 48ms.

      Run: [ @replit ]

      from itertools import product
      from enigma import Football, printf
      
      # possible scores (no more than 7 goals scored per match)
      win = [
        (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0),
        (2, 1), (3, 1), (4, 1), (5, 1), (6, 1),
        (3, 2), (4, 2), (5, 2),
        (4, 3),
      ]
      draw = [(0, 0), (1, 1), (2, 2), (3, 3)]
      
      # for computing goals for/against
      football = Football()
      
      # CD is not yet played
      CD = None
      
      # A has 2 draws (which must be AC = AD) and a win (which must be AB)
      # B has 2 wins (which must be BC = BD)
      for (AB, AX, BX) in product(win, draw, win):
        # check goals for/against
        if not(football.goals([AB, AX, AX], [0, 0, 0]) == (7, 5)): continue
        if not(football.goals([AB, BX, BX], [1, 0, 0]) == (5, 5)): continue
        # output solution
        printf("AB={AB} AC={AX} AD={AX} BC={BX} BD={BX} CD={CD}")
      

      Solution: A vs B = 3-1; A vs C = 2-2; A vs D = 2-2; B vs C = 2-1; B vs D = 2-1; C vs D = not yet played.

      Like

    • John Crabtree's avatar

      John Crabtree 7:33 pm on 18 March 2021 Permalink | Reply

      At the point where you can say “So we only need to calculate three scorelines”, you are almost home.
      A must beat B by 2 goals, and score an odd number of goals. That fixes the score in A vs B, and the remaining scores follow.

      Like

  • Unknown's avatar

    Jim Randell 8:14 am on 16 March 2021 Permalink | Reply
    Tags: ,   

    Teaser 2529: [Cost estimates] 

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

    A letter to The Times concerning the inflated costs of projects read:

    When I was a financial controller, I found that multiplying original cost estimates by 𝛑 used to give an excellent indication of the final outcome.

    Interestingly, I used the same process (using 22/7 as a good approximation for 𝛑). On one occasion, the original estimate was a whole number of pounds (less than £100,000), and this method for the probable final outcome gave a number of pounds consisting of exactly the same digits, but in the reverse order.

    What was the original estimate?

    When originally published the amount was given as “less than £10,000”, which was raised to “less than £100,000” in the following issue. But even with this change the puzzle is still open to interpretation.

    This puzzle was originally published with no title.

    [teaser2529]

     
    • Jim Randell's avatar

      Jim Randell 8:15 am on 16 March 2021 Permalink | Reply

      What the puzzle isn’t clear on is what happens to any fractional part of the new estimate. (Which there would always be if you were to multiply by 𝛑).

      If we round down (simply ignoring any fractional part) in the result, then we can get a solution to the original puzzle:

      £185 × 22/7 = £581.43 → £581

      And we also get the same result if we round to the nearest pound.

      And a different unique solution if we round up:

      £29 × 22/7 = £91.14 → £92

      Raising the limit to £100,000 then we get the following additional solutions:

      £22407 × 22/7 = £70422

      Rounding to nearest:

      £30769 × 22/7 = £96702.57 → £96703

      Which we also get if we round up, along with:

      £29429 × 22/7 = £92491.14 → £92492

      So the only way we can get a unique solution with the revised upper limit is to assume that, before rounding, the new estimate was a whole number of pounds, and so no rounding was necessary. In this case only the pair £22407 → £70422 remain as a candidate solution, and this was the required answer.

      This makes a manual solution easier (and a programmed solution a little faster), as we know the original estimate must be a multiple of 7.

      The following Python program lets you play with various rounding methods.

      from enigma import (irange, divf, divc, divr, div, nreverse, printf)
      
      # the new estimate is the original estimate multiplied by 22/7
      # choose an appropriate function from the following
      # - fn=divf: rounded down
      # - fn=divc: rounded up
      # - fn=divr: rounded to nearest integer
      # - fn=div: exact division
      estimate = lambda x, fn=div: fn(x * 22, 7)
      
      # consider the original estimate x
      for x in irange(1, 99999):
        # multiply by 22/7 for new estimate n
        n = estimate(x)
        if n is None or n % 10 == 0: continue
        # new estimate is reverse of the original
        if nreverse(n) == x:
          printf("original {x} -> {n}")
      

      Solution: The original estimate was: £22,407.


      If the estimates had been constrained to be between £100,000 and £1,000,000 then there is a single solution whichever rounding method is chosen:

      £246,477 × 22/7 = £774,642

      Like

    • Frits's avatar

      Frits 1:04 pm on 17 March 2021 Permalink | Reply

      # consider original cost abcde w    
      # 22 * abcde = 7 * edcba 
      
      # 0 < a < 4 as a * 22 / 7 < 10
      # a must be also be even as 22 * abcde is even
      # so a = 2 and 5 < e < 10
      # 22 * 2bcde = 7 * edcb2 --> e is 7 or 2 --> e is 7 
      
      # equation 22 * 2bcd7 = 7 * 7dcb2
      
      # cost 2-digit number: 
      # -- 27 is not divisible by 7
      # cost 3-digit number: 4914 <= 22 * 2b7 <= 5544 so 2 < b < 5 
      # -- no 2b7 is divisible by 7 for 2 < b < 5 
      # cost 4-digit number: 49014 <= 22 * 2bc7 <= 55944 so 1 < b < 6 
      # cost 5-digit number: 490014 <= 22 * 2bcd7 <= 559944 so 1 < b < 6 
      
      # ...d7 * 22 ends on 54 + d * 20 --> ends on o4 where o is an odd digit
      # b = 2 --> ...22 * 7 = ...54, 54 + d * 20 ends on 54 so d = 0 or 5
      # b = 3 --> ...32 * 7 = ...24, wrong
      # b = 4 --> ...42 * 7 = ...94, 54 + d * 20 ends on 94 so d = 2 or 7
      # b = 5 --> ...52 * 7 = ...64, wrong
      
      # b = 2 --> d = 0 or 5,
      # b = 4 --> d = 2 or 7
      
      # 4-digit numbers 2207, 2257, 2427 and 2477 are not not divisible by 7
      
      # divisibility by 11 requires that the difference between 
      # the sum of the the digits in odd positions and 
      # the sum of all the digits in even positions is 0 or divisible by 11
      
      # 22x07, sum even positions = 2  --> (9 + x - 2) % 11 = 0 -- > x = 4
      # 22407 is divisible by 7
      
      # 22x57, sum even positions = 7  --> (9 + x - 7) % 11 = 0 -- > x = 9
      # 22957 is not divisible by 7
      
      # 24x27, sum even positions = 6  --> (9 + x - 6) % 11 = 0 -- > x = 8
      # 24827 is not divisible by 7
      
      # 24x77, sum even positions = 11 --> (9 + x - 11) % 11 = 0 -- > x = 2
      # 24277 is not divisible by 7
           
      # so 22407 * 22/7 = 70422  
      

      Like

    • John Crabtree's avatar

      John Crabtree 1:40 pm on 18 March 2021 Permalink | Reply

      X = abcde, Y = edcba where X * 22 = Y * 7
      and so 2e = 7a mod 10, and so a = 2 and e = 7

      By digital roots, the sum of the digits in X = 0 mod 3. X = Y = 0 mod 3
      X = 0 mod 7. Y = 0 mod 11, and so X = 0 mod 11
      And so X = 231*M, Y = 726*M, and M = 7 mod 10
      Or X = 1617 + 2310*N and Y = 5082 + 7260*N

      By inspection X cannot have 2, 3 or 4 digits
      70,000 < Y < 80,000 and so N = 9 or 10, which is invalid by inspection.
      N = 9 gives X = 22407 and Y = 70422, which is valid.

      If X 6 digits, there are only 14 possible values of N, ie 96 to 109, to check.

      Like

      • Frits's avatar

        Frits 12:12 pm on 19 March 2021 Permalink | Reply

        @John,

        Maybe you used the congruent sign in your analysis and it was replaced by equal signs.
        If so you might try the enclosure of text as mentioned in Teaser 2756.
        If not the analysis is wrong as 0 mod 11 equals 0.

        Please elaborate as I am not an expert in modular arithmetic (I gather X must be a multiple of 3, 7 and 11?).

        Like

        • John Crabtree's avatar

          John Crabtree 4:16 pm on 19 March 2021 Permalink | Reply

          @Frits
          I do not recall the congruence sign from when I was first introduced to modulo arithmetic nearly 50 years ago. I have always used the equals sign (perhaps incorrectly) followed by mod n.
          X = 0 mod 3 means that X is divisible by 3. And so, yes, X is divisible by 3, 7 and 11. As it also ends in 7 the solution space becomes very small. HTH

          Like

    • Hugh Casement's avatar

      Hugh Casement 12:25 pm on 19 March 2021 Permalink | Reply

      I meant to post this on St Patrick’s day, before most of the other comments appeared.

      I’m sure that an integer solution is intended, so no rounding is necessary.
      The original estimate must therefore be an integer multiple of 7.
      The result is even, so the original estimate (with its digits in reverse order) must start with 2: any larger value would yield a result with more digits.
      The result is a multiple of 11, so the original estimate (using the same digits) must be too.
      Admittedly, that still means a lot of trial & error if we don’t use a computer.

      22/7 is a lousy approximation to pi! Much better is 355/113, but I think that can work only if the original estimate is allowed to have a leading zero.

      Like

  • Unknown's avatar

    Jim Randell 10:34 am on 14 March 2021 Permalink | Reply
    Tags: by: R. R. Brand   

    Brain-Teaser 26: Farmer’s overdraft 

    From The Sunday Times, 17th September 1961 [link]

    Farmer Green was in the red. On studying his statement (which was entirely in complete £s) he found that the aggregate of the figures in his deposits total was only one-fourth of the aggregate of the figures in his withdrawals total. He also noted that, coincidentally, the latter aggregate equals the balance, in red, that would be shown when he has paid in the £749 cheque which he, fortunately, had received that morning.

    What was the overdraft shown on the statement?

    [teaser26]

     
    • Jim Randell's avatar

      Jim Randell 10:35 am on 14 March 2021 Permalink | Reply

      This Python program run in 89ms.

      Run: [ @replit ]

      from collections import defaultdict
      from enigma import irange, dsum, div, printf
      
      # record number by digit sum
      ns = defaultdict(list)
      
      # consider increasing numbers
      for n in irange(1, 10000):
        ds = dsum(n)
        ns[ds].append(n)
      
        # if n is the total amount withdrawn
        # then the total amount deposited is less than this
        # and has a digit sum that is ds/4
        dds = div(ds, 4)
        if dds is None: continue
        for t in ns[dds]:
          # balance (is in red, so is negative), after the cheque is paid in
          red = -(t - n + 749)
          # is the same as the digit sum of the withdrawals
          if red == ds:
            # output solution
            od = ds + 749
            printf("withdrawn = {n}, deposited = {t}, red = {red}; overdraft = {od}")
      

      Solution: The overdraft on the statement is £777.

      There are 27 different (widthrawn, deposited) totals, but they all lead to an overdraft on the statement of £777.

      Like

    • Hugh Casement's avatar

      Hugh Casement 1:52 pm on 14 March 2021 Permalink | Reply

      I don’t follow this one. We are not told the opening balance on the bank statement, so we can’t tell what the difference between deposits and withdrawals should be.

      And one needs to be a mind-reader to deduce that “aggregate of the figures” means sum of the digits! Why can’t the setters of puzzles say what they mean?

      Like

    • John Crabtree's avatar

      John Crabtree 7:43 pm on 15 March 2021 Permalink | Reply

      The overdraft on the statement = W – D = Sum(W) + 749
      And so Sum(D) = – 2 mod 9 = 7 and Sum(W) = 4 * 7 = 28
      The overdraft on the statement = 28 + 749 = £777

      Like

  • Unknown's avatar

    Jim Randell 4:57 pm on 12 March 2021 Permalink | Reply
    Tags:   

    Teaser 3051: Shipping forecast 

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

    “Here is the shipping forecast for the regions surrounding our island.

    First, the coastal regions:

    Hegroom: E 6, rough, drizzle, moderate.
    Forkpoynt: E 7, rough, rain, good.
    Angler: NE 7, rough, drizzle, moderate.
    Dace: NE 7, smooth, drizzle, moderate.

    Now, the offshore regions:

    Back: E gale 8, rough, rain, moderate.
    Greigh: E 7, smooth, drizzle, poor.
    Intarsia: SE 7, rough, drizzle, moderate.
    Catter: E gale 8, high, drizzle, moderate.
    Eighties: E 7, rough, fair, good.”

    In this forecast, no element jumps from one extreme to another between adjacent regions.

    The extremes are:

    wind direction: SE & NE;
    wind strength: 6 & gale 8;
    sea state: smooth & high;
    weather: fair & rain;
    visibility: good & poor.

    Each region adjoins four others (meeting at just one point doesn’t count).

    Which of the regions does Angler touch?

    [teaser3051]

     
    • Jim Randell's avatar

      Jim Randell 10:37 pm on 12 March 2021 Permalink | Reply

      (You might like to work out which sea areas this puzzle is spoofing [ @wikipedia ]).

      It took me a bit of doodling to come up with a map of the sea areas that satisfied the description. (I’ll put up a diagram of it later – [link]). But once that is done the program is relatively straightforward.

      I don’t know that it is the only possible map though, but I assume the setter must have checked that any viable map will give the same solution.

      This Python program runs in 58ms.

      from enigma import (subsets, update, peek, join, map2str, printf)
      
      # suppose the coastal regions are: 0, 1, 2, 3
      # and the offshore regions are: 4, 5, 6, 7, 8
      
      # the adjacency map
      adj = [
        (1, 3, 6, 8), # 0
        (0, 2, 4, 6), # 1
        (1, 3, 4, 5), # 2
        (0, 2, 5, 8), # 3
        (1, 2, 6, 7), # 4
        (2, 3, 7, 8), # 5
        (0, 1, 4, 7), # 6
        (4, 5, 6, 8), # 7
        (0, 3, 5, 7), # 8
      ]
      
      # the forecast, region -> (dir, str, sea, wea, vis)
      forecast = dict(
        # coastal
        H=('E', 6, 'rough', 'drizzle', 'moderate'),
        F=('E', 7, 'rough', 'rain', 'good'),
        A=('NE', 7, 'rough', 'drizzle', 'moderate'),
        D=('NE', 7, 'smooth', 'drizzle', 'moderate'),
        # offshore
        B=('E', 8, 'rough', 'rain', 'moderate'),
        G=('E', 7, 'smooth', 'drizzle', 'poor'),
        I=('SE', 7, 'rough', 'drizzle', 'moderate'),
        C=('E', 8, 'high', 'drizzle', 'moderate'),
        E=('E', 7, 'rough', 'fair', 'good'),
      )
      
      # extremes (in the same order)
      extreme = ({'SE', 'NE'}, {6, 8}, {'smooth', 'high'}, {'fair', 'rain'}, {'good', 'poor'})
      
      # check forecast for <k> can be placed at region <r> without giving an extreme pair
      # return an updated map, or None
      def check(k, r, m):
        for (i, (v0, xs)) in enumerate(zip(forecast[k], extreme)):
          # is there an extreme in the forecast?
          if v0 in xs:
            # check the other extreme is not in any adjacent areas
            for r1 in adj[r]:
              s = m.get(r1, None)
              if s:
                v1 = forecast[s][i]
                if v1 != v0 and v1 in xs: return None
        # looks good, map r -> k
        return update(m, [(r, k)])
      
      # attempt to map regions <rs> to keys <ks>
      def assign(rs, ks, m):
        for (r, k) in zip(rs, ks):
          m = check(k, r, m)
          if m is None: break
        return m
      
      # assign the coastal regions (0, 1, 2, 3) -> "ADFH"
      for ks in subsets("ADFH", size=len, select="P"):
        m0 = assign((0, 1, 2, 3), ks, dict())
        if m0 is None: continue
      
        # assign the offshore regions (4, 5, 6, 7, 8) -> "CBEGI"
        for ks in subsets("CBEGI", size=len, select="P"):
          m = assign((4, 5, 6, 7, 8), ks, m0)
          if m is None: continue
      
          # which region is A?
          r = peek(k for (k, v) in m.items() if v == 'A')
      
          # output the regions adjacent to A
          adjA = sorted(m[x] for x in adj[r])
          printf("adj[A] = {adjA} {m}", adjA=join(adjA, sep=",", enc="()"), m=map2str(m, arr="->", enc="[]"))
      

      Solution: Angler adjoins Catter, Eighties, Forkpoynt, Hegroom.

      The program finds 4 ways to assign the areas to regions of the map. But the map can be drawn to have two axes of symmetry, so there is essentially only one solution (and the other 3 found are just reflections of this).


      My initial stetch, with the required connectivity looked like this:

      Which I redrew using straight lines to get this map:

      Which I think looks more like a map of sea areas. (The point where areas 2, 4, 5, 7 meet could be small outcrop with a lighthouse perhaps).

      But it is topologically equivalent to the following diagram, which has horizontal and vertical symmetry:

      The four solutions found by the program correspond to the reflections of a single solution:

      Like

      • Jim Randell's avatar

        Jim Randell 10:18 am on 21 March 2021 Permalink | Reply

        I think the derivations of the sea areas are:

        Angler → Fischer
        Back → Forth
        Catter → Dogger
        Dace → Sole
        Eighties → Forties
        Forkpoynt → Tyne
        Greigh → Wight
        Hegroom → Hebrides
        Intarsia → Fair Isle [knitting]

        Like

      • Tony Brooke-Taylor's avatar

        Tony Brooke-Taylor 1:19 pm on 17 April 2021 Permalink | Reply

        I was curious about the possibility of other viable maps giving different solutions (and cross that I did not come up with the map that seems to work). Several weeks and a crash-course in graph theory later, I developed a routine that generates all possible combinations of 18 allowed borders and then applies constraints until it produces a unique solution. My conclusions are as follow:

        Combinations of 18 from the set of 26 allowed borders gives a long-list of 1562275 possibilities.

        Only 564 of these contain all 9 regions with 4 borders each.

        Only 9 of these are planar. I used Pyladder, which is a Python implementation of the graph planarity algorithm sourced from ‘Efficient Planarity Testing’ by John Hopcroft and Robert Tarjan. See https://github.com/haraldujc/pyladder

        As far as I can see, there is no reason why any of these 9 might not, in principle, be valid. They do not all give the same result for Angler. However, if we apply a couple more constraints to the coastal regions, we do get Jim’s solution uniquely:
        1 – one of the 9 only has 2 coastal-coastal borders, so the ‘island’ would be two islands.
        2 – only one of the 9 has exactly 2 coastal borders per coastal region. All of the others have at least one coastal region with either 1 or 3 coastal borders. If we assume that the coastal regions represent quadrants of the island (which makes sense) then they will indeed have two coastal neighbours each. Hence the unique solution has that form.

        Like

        • Jim Randell's avatar

          Jim Randell 10:24 am on 21 April 2021 Permalink | Reply

          @Tony:

          Interesting. I did wonder if there was a programmatic way to generate viable graphs in order to show uniqueness (or otherwise).

          I had a brief tussle with planar graphs when solving Enigma 1035.

          Like

        • Jim Randell's avatar

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

          Having been looking at packages to manipulate graphs for Teaser 3083, I thought I’d revisit this problem.

          I wrote a Python program to generate all possible potential graphs, and output them in “graph6” format. I saved this to a file, so I could use the “nauty” tools on it. [ link ]

          The graphs generated have nodes 0-3 as the coastal regions, and nodes 4-8 are the off-shore regions. We generate all possible graphs with 4 connections between each of the nodes to give us a viable adjacency graph for the regions, but then add in an additional region to represent the island (9), which is adjacent to all 4 of the coastal regions.

          This program takes 7m22s to generate all possible graphs.

          from enigma import (subsets, ordered)
          
          # generate graphs with each node connected to K others
          def generate(K, ns, g=set()):
            # are we done?
            if not ns:
              yield g
            else:
              # deal with the next node
              n = ns[0]
              k = sum(n in x for x in g)
              if k <= K:
                # choose K - k edges to add
                ns_ = ns[1:]
                for xs in subsets(ns_, size=K - k):
                  yield from generate(K, ns_, g.union(ordered(n, x) for x in xs))
          
          # "graph6" format for a graph (edge list) 
          import networkx as nx
          def graph6(g):
            g6 = nx.to_graph6_bytes(nx.Graph(g), header=0)
            return g6.decode(encoding="ascii").rstrip()
          
          # generate graphs:
          #   0, 1, 2, 3: are the 4 coastal regions
          #   4, 5, 6, 7, 8: are the off-shore regions
          for g in generate(4, [0, 1, 2, 3, 4, 5, 6, 7, 8]):
            # add in edges from the coastal regions to the island (9)
            g.update([(0, 9), (1, 9), (2, 9), (3, 9)])
            # output in graph6 format
            print(graph6(g))
          

          And we can process the list of generated graphs as follows:

          # generate the graphs, and save those that are isomorphically distinct
          % python3 teaser3051g.py | shortg > graphs3051.g6 
          1024380 graphs read from stdin
          494 graphs written to stdout
          
          # find which of them are planar
          % planarg graphs3051.g6 | showg
          494 graphs read from graphs3051.g6, 1 written to stdout; 0.01 sec.
          
          Graph 1, order 10.
            0 : 1 2 3 4;
            1 : 0 2 6 7;
            2 : 0 1 6 8;
            3 : 0 4 7 9;
            4 : 0 3 8 9;
            5 : 6 7 8 9;
            6 : 1 2 5 7 8;
            7 : 1 3 5 6 9;
            8 : 2 4 5 6 9;
            9 : 3 4 5 7 8;
          

          Or without the intermediate file, just run:

          % python3 teaser3051g.py | shortg | planarg | showg
          

          The initial 1024380 generated graphs are reduced to a set of 494 isomorphically different graphs, and only 1 of these is planar.

          From the output we see that nodes 6-9 are the coastal regions, and 5 is the island itself, so 0-4 are the off-shore regions.

          And if we take my diagram and relabel it as follows:

          We see that the graph corresponds to the adjacency matrix that I started with, and this is the only possible layout.

          Like

    • Frits's avatar

      Frits 1:39 pm on 14 March 2021 Permalink | Reply

      Without diagram or explanation of analysis (no permission was given).
      Same results as Jim (using same adjacency map).

      from itertools import permutations
      
      regions = "HFADBGICE"
      d = dict()
      
      # coastal regions       low/mid/high = 1/2/3
      d["H"] = [2, 1, 2, 2, 2]
      d["F"] = [2, 2, 2, 3, 1]
      d["A"] = [3, 2, 2, 2, 2]
      d["D"] = [3, 2, 1, 2, 2]
      
      # offshore regions      low/mid/high = 1/2/3
      d["B"] = [2, 3, 2, 3, 2]
      d["G"] = [2, 2, 1, 2, 3]
      d["I"] = [1, 2, 2, 2, 2]
      d["C"] = [2, 3, 3, 2, 2]
      d["E"] = [2, 2, 2, 1, 1]     
      
      # the adjacency map             credit: Jim Randell
      adj = [
        (1, 3, 6, 8), # 0
        (0, 2, 4, 6), # 1
        (1, 3, 4, 5), # 2
        (0, 2, 5, 8), # 3
        (1, 2, 6, 7), # 4
        (2, 3, 7, 8), # 5
        (0, 1, 4, 7), # 6
        (4, 5, 6, 8), # 7
        (0, 3, 5, 7), # 8
      ]
      
      # generate "not bordering" dictionary
      nb = {r: [] for r in regions}             # init dictionary      
      for i, r1 in enumerate(regions): 
        for j, r2 in enumerate(regions): 
          if j == i: continue
          # check for opposite extremes
          if any(x * y == 3 for (x, y) in zip(d[r1], d[r2])):
            nb[r1] += [r2]
            
      # check which region can be the outer region
      outer = [r for r in regions[4:] 
                if not any(x for x in nb[r] if x in regions[4:])] 
                
      if len(outer) == 1:
        outer = outer[0]
        cdict = {outer: 7}
      else:
        outer = ""
        cdict = {}
      
      # check offshore regions (except outer)
      for p in permutations("BCEGI".replace(outer, "")):
        o2no = dict(zip(p, (4, 5, 6, 8)))
        
        # regions 4, 6 share a border
        if p[0] in nb[p[2]]: continue     
        
        # regions 5, 8 share a border
        if p[1] in nb[p[3]]: continue
        
        # check coastal regions
        for q in permutations("ADFH"):
          c2no = dict(zip(q, (0, 1, 2, 3)))
        
          # F shares a border with A, also D and H have to share a border
          if q[0] + q[2] in {"AF", "FA", "DH", "HD"} : continue
          
          # A and D don't share a border
          if abs(c2no["A"] - c2no["D"]) != 2: continue
          
          # C shares a border with A
          if c2no["A"] not in adj[o2no["C"]]: continue
       
          # B doesn't share a border with A 
          if c2no["A"] in adj[o2no["B"]]: continue
          
          # B doesn't share a border with H
          if c2no["H"] in adj[o2no["B"]]: continue
          
          # C doesn't share a border with H
          if c2no["H"] in adj[o2no["C"]]: continue
          
          # merge dictionaries (not using | as PyPy doesn't support it yet)
          comb = {**o2no, **c2no, **cdict}
          
          print("".join(k for k, v in comb.items() if v in adj[c2no["A"]]), 
                end = ": ")
          print(", ".join(str(v) + "->" + k 
                for k, v in sorted(comb.items(), key=lambda x: x[1])))
      

      Like

    • Tony Brooke-Taylor's avatar

      Tony Brooke-Taylor 11:32 am on 19 March 2021 Permalink | Reply

      I’ve spent hours trying to find a unique solution: manually (found a solution but could not prove unique), and with two entirely different approaches to looping and eliminating possibilities. However, I did not try a map that allowed one of the offshore regions to surround the entire space. Without allowing that, the only map I found topologically possible yielded three solutions. Oh well, I can console myself that my objective is to learn Python, not to second-guess problem-setters 🙂

      Like

  • Unknown's avatar

    Jim Randell 8:35 am on 11 March 2021 Permalink | Reply
    Tags:   

    Teaser 2780: Calendar dice 

    From The Sunday Times, 3rd January 2016 [link] [link]

    I have tried to make a calendar using some dice. To display the month I want to use three dice, with a capital letter on each of the faces to display:

    [J A N] or [F E B] or [M A R] etc.

    I chose the capital letters on the dice to enable me to go as far as possible through the year. Furthermore, it turned out that one particular die contained four vowels.

    (a) What was the last month that I was able to display?
    (b) What were the two other letters on that particular die?

    [teaser2780]

     
    • Jim Randell's avatar

      Jim Randell 8:36 am on 11 March 2021 Permalink | Reply

      In upper case the only symbol that can be used for more than one letter is M/W, but W does not appear in the words we are interested in, so we can consider all the symbols to be unique. (In a standard font anyway).

      This Python program finds sets of dice that can make the maximum number of months, and then looks for sets that have a die with 4 vowels on. It runs in 1.18s.

      Run: [ @repl.it ]

      from enigma import (subsets, Accumulator, first, diff, join, printf)
      
      vowels = set('AEIOU')
      months = "JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC".split()
      
      # merge the symbols <ss> to the dice <ds>
      def merge(ds, ss):
        rs = list()
        for (d, s) in zip(ds, ss):
          if s in d:
            rs.append(d)
          elif len(d) == 6:
            return
          else:
            rs.append(d + [s])
        return rs
      
      # add words from <ws> to dice in <ds>
      # return (<dice>, <number-of-words-remaining>)
      def solve(ds, ws):
        # result so far
        yield (ds, len(ws))
        if ws:
          # put letters for the next word on the dice
          for ss in subsets(ws[0], size=len, select="mP"):
            ds_ = merge(ds, ss)
            if ds_:
              yield from solve(ds_, ws[1:])
      
      # start with the first month
      ds0 = list([x] for x in months[0])
      
      # find dice with the fewest remaining words
      r = Accumulator(fn=min, collect=1)
      for (ds, n) in solve(ds0, months[1:]):
        r.accumulate_data(n, ds)
      
      # output result
      printf("months = {ms} [{n} sets of dice]",
        ms=join(first(months, len(months) - r.value), sep=" ", enc="[]"),
        n=len(r.data),
      )
      
      # look for solutions with 4 vowels on one die
      fmt = lambda d: join(d, sep=" ", enc="[]") # format a die
      for ds in r.data:
        ds = sorted(sorted(d) for d in ds)
        # find dice with (exactly) 4 vowels
        vs = list(d for d in ds if len(vowels.intersection(d)) == 4)
        if not vs: continue
        printf("dice = {ds}", ds=join((fmt(d) for d in ds), sep=" "))
        for d in vs:
          printf("-> {d} has 4 vowels; consonants = {c}", d=fmt(d), c=fmt(diff(d, vowels)))
      

      Solution: (a) The last month you would be able to display is OCT. (b) The consonants on the die with 4 vowels are R and Y.

      There are 108 sets of dice that can make JANOCT, but only 4 of them have a die with 4 vowels on:

      [A B L N S T] [A E O R U Y] [C F G J M P]
      [A B C L N S] [A E O R U Y] [F G J M P T]
      [A E O R U Y] [A F L N S T] [B C G J M P]
      [A C F L N S] [A E O R U Y] [B G J M P T]

      In each case the die with 4 vowels is: [A E O U] + [R Y]

      We can see that the vowel I does not occur in any of the words, so the four vowels must be [A E O U], and setting the middle die in our initial set of dice (line 31) to have these 4 symbols on to start with finds just the four sets of dice given above, and runs in 401ms.


      However, if we were to use lower case letters, then there are useful symbols that can be used for more than one letter:

      b / q
      d / p
      n / u

      And by replacing lines 3-4 with:

      vowels = set('aeion')
      months = ['jan', 'feb', 'mar', 'adr', 'may', 'jnn', 'jnl', 'ang', 'sed', 'oct', 'nov', 'dec']
      

      We find there are 10 sets of dice that can make all twelve months. (But none of them have a die containing 4 vowel symbols, so remove line 50 if you want to see them).

      Like

    • Hugh Casement's avatar

      Hugh Casement 1:38 pm on 11 March 2021 Permalink | Reply

      If we cheat by using upper-case U turned sideways to make C, will that allow us to make DEC?

      Like

      • Jim Randell's avatar

        Jim Randell 1:51 pm on 11 March 2021 Permalink | Reply

        @Hugh: It doesn’t let us get to DEC (we can’t fit a D in), but it means we don’t need a C to make OCT, so we free up a letter, which can be V, so we can get as far as NOV.

        If we can also use an upside down A to make a V, then we can fit the D in and get to DEC.

        I think there is scope for using specialised fonts that would allow some symbols to serve as multiple letters.

        Like

    • Hugh Casement's avatar

      Hugh Casement 5:40 pm on 11 March 2021 Permalink | Reply

      Thanks, Jim. I was going to suggest turning the A upside down, though it’s possibly a worse cheat!

      I’m sure I’ve come across a variant of this puzzle, possibly by Martin Gardner. I have a vague recollection that all the months could be done except AUG which wasn’t needed because it was during the university long vacation.

      Like

      • Jim Randell's avatar

        Jim Randell 8:45 pm on 14 March 2021 Permalink | Reply

        @Hugh: Removing AUG from the list in my program finds 6 sets of dice that can make the remaining 11 months.

        Also removing FEB has 30 sets of dice that can make the remaining 11 months, and 2 of the sets also have 4 vowels on one die.

        Like

    • Frits's avatar

      Frits 1:54 pm on 12 March 2021 Permalink | Reply

      # place the 4 vowels A, E, O and U on die number 2
      #
      # As either A or U also has to be on die 1 or 3 (in order to form AUG)
      # there are only 18 -5 = 13 spots left for consonants
      
      months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 
                'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
      
      s = set()
      for j in range(12):
        lets = "".join([x for x in months[j] if x not in "AEOU"])
        s = s | set(lets)
        print (f"{months[j]}: {len(list(s))} consonants {''.join(s)} ")
      
      # JAN: 2 consonants JN
      # FEB: 4 consonants JNFB
      # MAR: 6 consonants JNFBMR
      # APR: 7 consonants JNFBMRP
      # MAY: 8 consonants JNFBMRPY
      # JUN: 8 consonants JNFBMRPY
      # JUL: 9 consonants JNFBMRPYL
      # AUG: 10 consonants JNFBMRPYLG
      # SEP: 11 consonants JNFBMRPYLGS
      # OCT: 13 consonants JNFBMRPYLGSCT
      # NOV: 14 consonants JNFBMRPYLGSCTV
      # DEC: 15 consonants JNFBMRPYLGSCTVD
      #
      # so NOV and DEC can't be the last month to be displayed
      #
      # Assume last month to be displayed is OCT
      #
      # letter pairs on dice 1 and 3 but not on the same die (no order):
      # v,G + F,B + C,T + S,P (E only on die 2) + J,N (because JUN and JAN)
      # where v is A or U (extra vowel)
      #
      # suppose v = U -> in same group: P and M (APR and MAR), R and Y (MAR and MAY)
      # this is impossible as group 1 and 3 already have 5 candidates
      #
      # suppose v = A 
      # candidates for 2 empty spots on die 2: 
      # NOT: F,B + C,T + S,P + J,N + G + L (as U only on die 2)
      # leaving R, M, Y
      #  -- suppose M and R on die 2: --> we can't form MAR anymore
      #  -- suppose M and Y on die 2: --> we can't form MAY anymore
      #  -- suppose R and Y on die 2: --> no inconsistency
      
      # place A on die 1
      
      # die 1: A  .  .  .  .  .
      # die 2: A  E  O  U  R  Y
      # die 3: .  .  .  .  .  .
      
      # P must be on die 3 (APR) --> S on die 1
      # M must be on die 3 (MAR)
      # G must be on die 3 (AUG)
      # L must be on die 1 (as F,B + C,T + J,N are pairs)
      # J must be on die 3 (as L is on die 1) --> N on die 1
      
      # die 1: A  S  L  N  .  .
      # die 2: A  E  O  U  R  Y
      # die 3: P  M  G  J  .  .
      
      # this leaves 4 ways to place B, F, C and T
      
      for x in ("BF", "FB"):
        for y in ("CT", "TC"):
          print("A S L N", x[0], y[0], end="  -  ")
          print("A  E  O  U  R  Y", end="  -  ")
          print("P  M  G  J", x[1], y[1])
      
      # A S L N B C  -  A  E  O  U  R  Y  -  P  M  G  J F T
      # A S L N B T  -  A  E  O  U  R  Y  -  P  M  G  J F C
      # A S L N F C  -  A  E  O  U  R  Y  -  P  M  G  J B T
      # A S L N F T  -  A  E  O  U  R  Y  -  P  M  G  J B C
      

      Like

    • Hugh Casement's avatar

      Hugh Casement 5:45 pm on 12 March 2021 Permalink | Reply

      I still don’t know who published the no-August variant, or where, but I’ve found the solution scribbled in the margin of a book. It’s a wee bit shorter than the proof of Fermat’s last theorem: the three cubes bear the letters
      A B C S U V, D F J M O P, E L N R T Y
      With two further cubes we can make the numbers 01 to 31:
      0 1 2 3 4 5, 0 1 2 6 (which also serves as 9), 7, 8.

      Like

  • Unknown's avatar

    Jim Randell 9:37 am on 9 March 2021 Permalink | Reply
    Tags: by: John Halsall   

    Brain-Teaser 699: The deflating mangoes 

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

    The Lotaseetas have a rather casual attitude to commerce. Every Monday morning, Ming, the rice-planter, gathers from the grove outside his bungalow a number of mangoes of uniform weight. For the rest of the week he uses these as weights for measuring out the rice on his scales, charging each customer according to the number of mangoes required to balance the weight purchased.

    Unfortunately, the mangoes themselves lose a fixed percentage of their weight each day by evaporation. However, Ming roughly compensates for this by using two mangoes as the unit on Wednesdays and Thursdays and three on Fridays. Clearly, Wednesday is a good day for buying rice, and Tuesday is a bad day.

    His first customer at precisely 10 o’clock each morning (they are late risers) is Fung, the rice merchant, who always buys the same quantity of rice for his shop. On Monday Fung’s rice cost him 243 cowries. On Friday it cost him 256 cowries.

    How much did it cost him on Tuesday?

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

    [teaser699]

     
    • Jim Randell's avatar

      Jim Randell 9:38 am on 9 March 2021 Permalink | Reply

      If each mango weighs m on Monday, and each day its weight reduces to a fraction f⋅m the following day then the weight of each mango (at 10am) on each of the 5 days is:

      Mon = m
      Tue = f⋅m
      Wed = (f^2)m
      Thu = (f^3)m
      Fri = (f^4)m

      And the units of weight on each day are therefore:

      Mon = m
      Tue = f⋅m
      Wed = 2(f^2)m
      Thu = 2(f^3)m
      Fri = 3(f^4)m

      And on Monday rice of weight w cost 243, and on Friday the same weight cost 256:

      Mon = w / m = 243
      Fri = w / (3(f^4)m) = 256
      w = 243m = 256×3(f^4)m
      f^4 = 243/768 = 81/256
      f = 3/4

      So, how much did the rice cost on Tuesday?

      Tue = w / f⋅m = Mon / f
      Tue = 243 / (3/4) = 324

      Solution: On Tuesday the rice cost 324 cowries.

      The costs for each day are:

      Mon = 243
      Tue = 324
      Wed = 216
      Thu = 288
      Fri = 256
      avg = 265.4

      Like

  • Unknown's avatar

    Jim Randell 9:39 am on 7 March 2021 Permalink | Reply
    Tags: by: D. G. Beech   

    Brain-Teaser 25: [Exam results] 

    From The Sunday Times, 10th September 1961 [link]

    “These examination results show that either the knowledge of mathematics, physics, and chemistry throughout the school is deplorable weak, or the papers were very stiff”, said the headmaster to the staff concerned.

    “Unfortunately, I have mislaid the detailed list, but some of the figures are easy to remember. The number of pupils taking the three subjects was 440. In chemistry 200 passed; in physics 210 failed; and in mathematics 220 passed. Of those who passed in chemistry 66 failed in mathematics, whereas of those who passed in mathematics 22 failed in physics. I cannot recall the number of pupils who passed in all three subjects, or the number who failed in all three, but both these numbers were perfect squares”. At this stage the senior mathematics master got out his pencil and paper and started to puzzle it out.

    (1) How many pupils failed in all three subjects?
    (2) Of those who passed in chemistry, how many also passed in physics?

    This puzzle was originally published with no title.

    [teaser25]

     
    • Jim Randell's avatar

      Jim Randell 9:40 am on 7 March 2021 Permalink | Reply

      For each of the three subjects a student can either pass or fail. So there are 8 regions in the corresponding Venn diagram.

      And if we had been given the square numbers for those that passed in all three subject and failed in all three subjects we would have 8 equations in 8 variables, so we would be able to work out the values for each of the variables.

      And the missing squares must be less than 15².

      The following Python program runs in 79ms.

      from enigma import (matrix, as_int, subsets, powers, printf)
      
      # solve the equations given values for MPC and X
      def solve(MPC, X):
      
        # set up the equations
        eqs = [
          # X  M  P  C  MP MC PC MPC = k
          ((1, 1, 1, 1, 1, 1, 1, 1), 440),
          ((0, 0, 0, 1, 0, 1, 1, 1), 200),
          ((1, 1, 0, 1, 0, 1, 0, 0), 210),
          ((0, 1, 0, 0, 1, 1, 0, 1), 220),
          ((0, 0, 0, 1, 0, 0, 1, 0),  66),
          ((0, 1, 0, 0, 0, 1, 0, 0),  22),
          ((0, 0, 0, 0, 0, 0, 0, 1), MPC),
          ((1, 0, 0, 0, 0, 0, 0, 0),   X),
        ]
      
        # solve for non-negative integers
        try:
          (X, M, P, C, MP, MC, PC, MPC) = (as_int(x, "0+") for x in matrix.linear(eqs))
        except ValueError:
          return
      
        printf("X={X} M={M} P={P} C={C} MP={MP} MC={MC} PC={PC} MPC={MPC}")
      
      # consider possible squares for X and MPC
      for (MPC, X) in subsets(powers(0, 14, 2), size=2, select="M"):
        solve(MPC, X)
      

      Solution: (1) 144 pupils failed all three subjects; (2) 143 passed in chemistry and also physics.

      We can make a table of the regions of the Venn diagram

      M P C = 121
      M P - =  77
      M - C =  13
      M - - =   9
      - P C =  22
      - P - =  10
      - - C =  44
      - - - = 144
      total = 440
      

      Like

    • Frits's avatar

      Frits 12:25 pm on 8 March 2021 Permalink | Reply

      Based on Jim’s solution, with some analysis so no “==” operators are used (to avoid loops).

      @Jim, the generated code is not efficient as possible as there is a loop for O and Q when CF is already known.

      from enigma import SubstitutedExpression, is_square
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # X    M     P    C    MP   MC   PC   MPC
        
        #XYZ + MA + PDE + CF + HI + KL + OQ + STU = 440
        #"352 - XYZ - HI - STU = PDE",   # substitute MA + KL = 22 and CF + OQ = 66
        "352 - XYZ - HI - STU >= 0",
       
        #                 CF +      KL + OQ + STU = 200
        "112 + MA = STU",          # substitute CF + KL = 88 - MA - OQ
        
        #XYZ + MA +       CF +      KL            = 210
        "188 - CF = XYZ", 
        
        #      MA +            HI + KL +      STU = 220
        "198 - STU = HI",          # substitute KL = 22 - MA
        
        #                 CF +           OQ       = 66
        "66 - OQ = CF",
        
        #      MA +                 KL            = 22             
        "22 - MA = KL",
        
        "is_square(STU)",
        "is_square(XYZ)",
        ],
        answer="STU, HI, KL, MA, OQ, 352 - XYZ - HI - STU, CF, XYZ",
        d2i=dict([(0, "SX")] +
                 [(k, "M") for k in range(3, 7)] +
                 [(k, "MCO") for k in range(7, 10)]),
        distinct="",
        verbose=0
        )
      
      for (_, ans) in p.solve():
        print("pupils failed in all three subjects =", ans[0])
        print("Of those who passed in chemistry", ans[0] + ans[4], 
              "also passed in physics")
      

      Like

      • Frits's avatar

        Frits 12:57 pm on 8 March 2021 Permalink | Reply

        It can easily be seen that MA has to be 9 (square 121) and CF has to be 19 or 44 (squares 169 and 144).

        Like

    • John Crabtree's avatar

      John Crabtree 5:15 pm on 10 March 2021 Permalink | Reply

      C = 66 – CP and M = 22 – CM.
      From chemistry, CM + CMP = 200 – 66 = 134, with CM <= 22
      And so CMP = 121 and CM = 13.
      From maths, MP = 200 – 22 – CMP = 77
      From physics, P = 230 – MP – CP – CMP = 32 – CP, and so CP <= 32
      From all eight variables, 318 – CP + X = 440, ie X = 122 + CP
      And so X = 144 and CP = 22
      The answers follow directly.

      Like

  • Unknown's avatar

    Jim Randell 4:34 pm on 5 March 2021 Permalink | Reply
    Tags:   

    Teaser 3050: Power struggle 

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

    Given any number, one can calculate how close it is to a perfect square or how close it is to a power of 2. For example, the number 7 is twice as far from its nearest perfect square as it is from its nearest power of 2. On the other hand, the number 40 is twice as far from its nearest power of 2 as it is from its nearest square.

    I have quite easily found a larger number (odd and less than a million!) for which one of these distances is twice the other.

    What is my number?

    [teaser3050]

     
    • Jim Randell's avatar

      Jim Randell 4:45 pm on 5 March 2021 Permalink | Reply

      A brute force approach in Python can search the solution space in 215ms. If we stop after the first candidate solution is found, then it only takes 72ms.

      Run: [ @replit ]

      from enigma import (isqrt, irange, printf)
      
      # distance to nearest square
      def dist_sq(n):
        x = isqrt(n)
        return min(n - x**2, (x + 1)**2 - n)
      
      # distance to nearest power of 2
      def dist_p2(n):
        x = n.bit_length()
        return min(2**x - n, n - 2**(x - 1))
      
      # find solutions
      for n in irange(41, 1000000, step=2):
        d1 = dist_sq(n)
        d2 = dist_p2(n)
        if d1 == 2 * d2 or d2 == 2 * d1:
          printf("{n} -> dist_sq = {d1}, dist_p2 = {d2}")
          break  # we only need the first solution
      

      Solution: Your number is 32775.

      There aren’t many odd numbers with this property.

      The first 5 are:

      7
      32775
      134223231
      2147479015
      549756110751

      Like

      • Jim Randell's avatar

        Jim Randell 12:18 pm on 6 March 2021 Permalink | Reply

        Here’s an alternative approach that considers numbers at increasing distances from the sets of “markers”.

        Also we can see that powers of 2 (greater than 1) are all even, and we are looking for an odd number n > 40, so its distance d to a power of 2 must be odd, and its distance to a square must be 2d. This reduces the number of checks we need to make.

        The following Python program runs in 50ms.

        Run: [ @replit ]

        from enigma import (first, lt, powers, irange, inf, tuples, intersect, printf)
        
        # markers less than 1 million
        M = 1000000
        count = lt(M)
        squares = first(powers(0, inf, 2), count=count)
        pow2s = first((2**i for i in irange(0, inf)), count=count)
        
        # generate numbers at a distance <d> from the nearest marker in <ms>
        def dist(ms, d):
          for (ml, m, mr) in tuples(ms, 3):
            n = m - d
            if n - ml > d: yield n
            n = m + d
            if mr - n > d: yield n
        
        # generate odd numbers at twice the distance from
        # a square than from a power of 2
        def solve():
          # consider increasing odd distances to a power of 2
          for d in irange(1, inf, step=2):
            d2 = 2 * d
            # distance d from pow2s, 2d from squares
            for n in intersect([dist(pow2s, d), dist(squares, d2)]):
              printf("[{n} -> dist_p2 = {d}, dist_sq = {d2}]")
              yield n
        
        # find the first odd solution, greater than 40, less than M
        for n in solve():
          if 40 < n < M:
            printf("answer = {n}")
            break
        

        Like

      • Jim Randell's avatar

        Jim Randell 9:30 am on 14 March 2021 Permalink | Reply

        This program finds the 5 odd solutions given above, and larger ones (by default it outputs the first 10 it finds), by examining the two squares surrounding each odd power of 2:

        Run: [ @replit ]

        from enigma import (irange, inf, isqrt, div, first, arg, printf)
        
        # generate candidate solutions
        def generate():
          # consider increasing odd powers of 2
          for i in irange(3, inf, step=2):
            p = 2**i
            # and the square below
            n = isqrt(p)
            # look for candidate solutions
            n2 = n * n
            n21 = n2 + 2 * n + 1
            p2 = 2 * p
            # and check viability
            for x in [div(p2 + n2, 3), p2 - n2]:
              if x and x % 2 == 1 and x - n2 < n21 - x:
                printf("[{x} -> dist_p2 = {dp2}, dist_sq = {dsq}]", dp2=abs(p - x), dsq=x - n2)
                yield x
            for x in [div(p2 + n21, 3), p2 - n21]:
              if x and x % 2 == 1 and n21 - x < x - n2:
                printf("[{x} -> dist_p2 = {dp2}, dist_sq = {dsq}]", dp2=abs(p - x), dsq=n21 - x)
                yield x
        
        # find the first N solutions
        N = arg(10, 0, int)
        first(generate(), N)
        
        % time python3 teaser/teaser3050d.py 10
        [7 -> dist_p2 = 1, dist_sq = 2]
        [32775 -> dist_p2 = 7, dist_sq = 14]
        [134223231 -> dist_p2 = 5503, dist_sq = 11006]
        [2147479015 -> dist_p2 = 4633, dist_sq = 9266]
        [549756110751 -> dist_p2 = 296863, dist_sq = 593726]
        [8796091840375 -> dist_p2 = 1181833, dist_sq = 2363666]
        [140737493172567 -> dist_p2 = 4817239, dist_sq = 9634478]
        [2251799795854807 -> dist_p2 = 17830441, dist_sq = 35660882]
        [36028797113301975 -> dist_p2 = 94338007, dist_sq = 188676014]
        [576460752294331351 -> dist_p2 = 9092137, dist_sq = 18184274]
        python3 teaser/teaser3050d.py 10  0.03s user 0.01s system 92% cpu 0.048 total
        

        Like

    • Hugh Casement's avatar

      Hugh Casement 2:07 pm on 6 March 2021 Permalink | Reply

      I assume that “number” means integer: the setters of these puzzles have never been able to tell the difference.

      The logarithm of n to base 2, q = ln(n) / ln(2). We round to the nearest integer and take 2^q.

      Having written and run a Basic program in about one minute, I decided to look for even numbers.
      There are dozens, starting with 54, 114, 278, 546, 982, 2002, 4182, 8370, …
      The program was ten lines: I never expected Basic to be more compact than Python.

      Like

    • Frits's avatar

      Frits 1:06 pm on 7 March 2021 Permalink | Reply

      A variation of Jim’s second program (more efficient).

      from enigma import first, powers, irange, inf, tuples, intersect, printf, \
                         isqrt
      # markers less than 1 million
      M = 1000000
      squares = first(powers(0, inf, 2), count=(lambda x: x < M))
      pow2s = first((2 ** i for i in irange(0, inf)), count=(lambda x: x < M))
       
      # generate numbers at a distance <d> from the nearest marker in <ms>
      def dist(ms, d):
        for (ml, m, mr) in tuples(ms, 3):
          n = m - d
          if n - ml > d: yield n
          n = m + d
          if mr - n > d: yield n
       
      # generate odd numbers at twice the distance from
      # a square than from a power of 2
      def solve():
        # consider increasing odd distances to a power of 2
        for d in irange(1, inf, step=2):
          d2 = 2 * d
          for n in dist(pow2s, d): 
            # is there a square at distance 2d from n?
            if n - d2 in squares or n + d2 in squares:
              # is it the nearest square?
              x = isqrt(n)
              x2 = x * x
              x2n = x2 + 2 * x + 1
              nearest = x2 if n - x2 < x2n - n else x2n
              if nearest in {n - d2, n + d2}:
                printf("[{n} -> dist_p2 = {d}, dist_sq = {d2}]")
                yield n
      
      # find the first odd solution, greater than 40, less than M
      for n in solve():
        if 40 < n < M:
          printf("answer = {n}")
          break
      

      Like

      • Jim Randell's avatar

        Jim Randell 2:53 pm on 7 March 2021 Permalink | Reply

        @Frits: I thought that a “hybrid” approach of my two programs would probably be faster. Although my second program has an internal runtime of 2.74ms, so I didn’t think it was worth the extra code.

        Interestingly when I timed the code you posted it ran slightly slower than my version (3.03ms). It might be faster the other way around (generating distances from squares and then calculating distances from powers of two), as I would assume that [[ int.bit_length() ]] is probably faster than [[ math.isqrt() ]] (certainly the code in enigma.py for [[ isqrt() ]] starts by calling [[ int.bit_length() ]], and then does more stuff. (Although if [[ math.isqrt() ]] is available it just uses that, which should be implemented by native code).

        Like

        • Frits's avatar

          Frits 5:01 pm on 7 March 2021 Permalink | Reply

          @Jim, I have tested with the Brian Gladman’s Timer function (which uses perf_counter_ns ) doing 200 runs (jra3 is my variation on your jra2 and jra4 a variation on jra2 the other way around (looking at distances from squares and then checking distances from powers of two)).

           
          PyPy:
          
          jra1 1.579 ms
          jra2 2.629 ms
          jra3 146.700 us
          jra4 539.500 us
          
          Python 3.9.0:
          
          jra1 23.978 ms
          jra2 5.238 ms
          jra3 2.547 ms
          jra4 4.525 ms
          

          I would have been surprised in jra4 was faster than jra3 as the distances from squares generates many elements (like 1900).

          Like

          • Jim Randell's avatar

            Jim Randell 12:06 pm on 8 March 2021 Permalink | Reply

            @Frits: I suppose it goes to show that different timing strategies produce different results.

            For a piece of Python code that is only going to be executed once I think that a 3ms internal runtime is perfectly fine. This run time is dwarfed by the amount of additional time the Python interpreter takes getting ready to run it, so in my preferred measure of execution time both the programs give an external runtime of 50ms.

            For code that is going to be executed thousands of times it may be worth shaving a few milliseconds off it, but code that is only executed once I’m not so sure.

            I wanted my programs to show two different approaches. My first program defines functions that calculate the two distances and then compares them. But there are nearly 500,000 candidate numbers, so this could involve a lot of calculation.

            The second program makes a set of markers, and then looks at numbers that are fixed distances from the markers, so it is essentially a searching strategy. In this case the distances involved in the solution aren’t very large, so the searching strategy wins.

            Like

        • Frits's avatar

          Frits 3:05 pm on 8 March 2021 Permalink | Reply

          @Jim,

          Talking about isqrt:

          have you considered using math.sqrt in enigma.py’s isqrt() if math.isqrt is not available (like in an up-to-date PyPy) for numbers under a certain limit? See Brian Gladman’s number_theory.py.

          Like

          • Jim Randell's avatar

            Jim Randell 6:46 pm on 8 March 2021 Permalink | Reply

            My original version of isqrt() used a float square root to provide an initial guess, and then applied Newton’s method until the result settled down to an integer. (See this comment on Enigma 1759).

            But I switched that to a binary technique I found on Wikipedia [ @wikipedia ] which performed better for numbers that overflowed floats.

            But looking at it again it turns out that that was because I was choosing a poor initial guess in the overflow case. It turns out you can do a much better guess using the fact that int.bit_length() behaves roughly like log2().

            So I think I’ll switch back to using Newton’s method for cases where math.isqrt() is not available.

            BTW: For a very fast implementation of isqrt() check out gmpy2.isqrt().

            Like

    • Hugh Casement's avatar

      Hugh Casement 1:20 pm on 7 March 2021 Permalink | Reply

      Isn’t this simpler?

      defdbl a - z
      for n = 41.0 to 999999.0 step 2.0
            p = int(log(n)/log(2.0) + 0.5)
            pp = 2.0^p
            dp = abs(pp - n)
            s = int(sqr(n) + 0.5)
            ss = s*s
            ds = abs(ss - n)
            if dp = 2.0*ds or ds = 2.0*dp then print n pp ss
      next n
      

      Turbo Basic has a log2 function, which would have made the third line a bit more compact.
      Of course some expressions could be combined, but at a slight loss of transparency.
      The .0 are not strictly necessary but make sure the numbers are interpreted as floating-point (single-length integers go only to 32767).

      Like

      • Jim Randell's avatar

        Jim Randell 1:55 pm on 7 March 2021 Permalink | Reply

        @Hugh: (I put your code into [code] ... [/code] tags so it should look better now).

        It is simpler, but it doesn’t calculate accurate values:

        e.g. The nearest power of 2 to 46 is 32, which gives a distance of 14:

        >>> dist_p2(46)
        14
        >>> f = lambda n: abs(2 ** int(log2(n) + 0.5) – n)
        >>> f(46)
        18

        You have to go much higher before the calculation of the nearest square breaks down (much more than a million), probably due to exceeding the precision of floating point numbers. (e.g. It fails at 10^37).

        Like

      • Frits's avatar

        Frits 2:00 pm on 7 March 2021 Permalink | Reply

        Hi Hugh,

        Your program suffers from the same problem Erling Torkildsen had at PuzzlingInPython (dp should be 107 iso 149 for n = 363).

        from math import log2
        
        for n in range(41, 1000000, 2):
          #p = int(log2(n) + 0.5)
          p = round(log2(n))
          pp = 2 ** p
          dp = abs(pp - n)
          #s = int(n ** 0.5 + 0.5)
          s = round(n ** 0.5)
          ss = s * s
          ds = abs(ss - n)
          if n == 363:
            print("n =", n, "p =", p, "pp =", pp, "dp =", dp, "ds =", 
                  ds, "s =", s, "ss =", ss)
          if dp == 2 * ds or ds == 2 * dp:
            print(n, pp, ss)
            break
        
        # n = 363 p = 9 pp = 512 dp = 149 ds = 2 s = 19 ss = 361
        

        Like

    • Frits's avatar

      Frits 4:52 pm on 9 March 2021 Permalink | Reply

      Looping over the power of 2 entries.

      # suppose p2 (2 ** p) is the power of 2 is the nearest power 
      # let s^2 be the nearest square smaller equal to p2 
      # so that s^2 <= p2 <= (s + 1)^2 
      # example:  484 <= 512 <= 529 with s = 22 and p2 = 2 ** 9       
      
      #          +--- 2s-1 ---+----- 2s + 1 ----+--- 2s + 3 --+  
      #          |            |                 |             |
      #  ---- (s - 1)^2 ---- s^2 --- p2 ---- (s + 1)^2 --- (s + 2)^2 ---
      #
      
      # even power exponents are also squares so ignore them
      for p in range(5, 21, 2):
        p2 = 2 ** p
        s = int(sqrt(p2))
        s2 = s ** 2
       
        # can x be between (s - 1)^2 and s^2 ?
        # then ds is maximal s - 1 and d2 is maximal (s - 1) / 2
        minx = min(p2 - ((s - 1) // 2), s2)
      
        # make sure minx is odd and ... 
        minx = minx + (minx % 2 == 0) 
      
        # ... ds is not a multiple of 4 and 
        minx = minx +  2 * (minx % 4 == 1) 
        
        # if s2 is even then first entries have an odd ds
        if s2 % 2 == 0:
          minx += s - 2
          
        # can x be between (s + 1)^2 and (s + 2)^2 ?
        # then ds is maximal s + 1 and d2 is maximal (s + 1) / 2
        maxx = max(p2 + ((s + 1) // 2), s2 + 2 * s + 1)
        
        # if s2 is odd then last entries have an odd ds
        if s2 % 2 :
          maxx -= s + 1
        
        # consider odd x's so that ds is not a multiple of 4
        for x in range(minx, maxx + 1, 4):
          # determine nearest square             
          y = int(sqrt(x))
          y2 = y ** 2
          ds = min(x - y2, y2 + 2 * y + 1 - x)  
          
          # determine nearest power of 2              
          y2min1 = 2 ** (x.bit_length() - 1)   
          d2 = min(2 * y2min1 - x, x - y2min1)
          
          # distance from x to power of 2 is always odd 
          # so ds = 2 * d2 (and not d2 = 2 * ds)
          if ds == 2 * d2:        
            print(f"number is {x}; distance to power of 2: {d2}; to square: {ds}")
            exit() # we only need the first
      

      Like

  • Unknown's avatar

    Jim Randell 8:47 am on 4 March 2021 Permalink | Reply
    Tags:   

    Teaser 2778: Cold turkey 

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

    We are having a “cold turkey” party on Boxing Day. Fewer than 100 people have indicated that they are coming, and the percentage of them choosing the vegetarian option is (to the nearest whole number) a single-digit number. My vegetarian aunt might also come. If she does, then (to the nearest whole number) the percentage having the vegetarian option will remain the same.

    If she does come, how many people will be there and how many of them will have the vegetarian option?

    [teaser2778]

     
    • Jim Randell's avatar

      Jim Randell 8:48 am on 4 March 2021 Permalink | Reply

      This Python program runs in 47ms.

      Run: [ @repl.it ]

      from enigma import divf, irange, printf
      
      # percentage a/b to the nearest whole number
      percent = lambda a, b: divf(200 * a + b, 2 * b)
      
      # total number of people expected (including aunt)
      for n in irange(2, 100):
        # consider number of vegetarians
        for v in irange(1, n):
          # percentage vegetarians (to nearest whole number)
          p = percent(v, n)
          if p > 9: break
      
          # and if aunt doesn't come, is percentage the same?
          if percent(v - 1, n - 1) == p:
            # output solution
            printf("{n} total; {v} veg [{p}% veg]")
      

      Solution: If the aunt comes there will be 95 people in total. 9 of them vegetarian.

      In this case the percentage of vegetarians is 9/95 ≈ 9.47%, which rounds down to 9%.

      But if the aunt doesn’t come both the number of guests and the number of vegetarians is reduced by 1, giving a percentage of 8/94 ≈ 8.51%, which rounds up to 9%.


      Note that Python 3’s built-in [[ round() ]] function might not do what you expect:

      % python3.9
      >>> round(11.5)
      12
      >>> round(12.5)
      12
      >>> round(1.15, 1)
      1.1
      >>> round(0.115, 2)
      0.12
      

      The decimal module allows more control over rounding behaviour.

      To keep things predictable, in my program I avoided float approximations by keeping the calculations in the integer domain, and I used the common “round half up” rule.

      Like

  • Unknown's avatar

    Jim Randell 8:41 am on 2 March 2021 Permalink | Reply
    Tags: by: J E Kessel, by: J R Partridge   

    Brain-Teaser 696: The Browning version 

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

    You see, Inspector, the combination of my safe is a six-figure number. In case anyone needed to get into it while I was away, I gave each of my clerks (Atkins, Browning and Clark) one of the two-figure numbers which make up the combination. I also told each the position in the combination of the number of another clerk, but not the number itself.

    Browning must have overheard me telling a friend that it is a coincidence that two of these numbers are squares and if you put them together you get a four-figure number that equals the other clerk’s number squared. I remember I also said something about whether or not the combination is divisible by this clerk’s number.

    When he was caught, Browning said, “I can’t understand why the alarm went off; I know Clark’s is the first number”. I later realised that what I’d told my friend about whether or not that other number was a factor was wrong, which was lucky for me as Browning had got his own number in the right place.

    What was the combination?

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

    [teaser696]

     
    • Jim Randell's avatar

      Jim Randell 8:43 am on 2 March 2021 Permalink | Reply

      This Python program looks for candidate sets of numbers where two of the numbers are 2-digit squares, that when concatenated form a 4-digit square, that is the square of the third number. (I assumed none of the squares had leading zeros).

      Once these are found we can construct a list of all possible scenarios, and then select those where B would be able to work out the combination with the information he has (even though one of the pieces of information is in fact incorrect).

      Once we find B’s deduced combinations, we can look for actual scenarios that have B’s number in the correct place, but have the correct information, and this will give us the actual combination.

      It runs in 49ms.

      Run: [ @repl.it ]

      from itertools import product
      from enigma import irange, is_square, subsets, nsplit, nconcat, filter_unique, unpack, printf
      
      # collect possible scenarios
      ss = list()
      
      # consider 2 digit numbers for one of the clerks numbers
      for x in irange(32, 99):
        # and the square of this gives the other 2 numbers
        (y, z) = nsplit(x * x, 2, base=100)
        # both of which are 2-digit squares
        if not all(n > 9 and is_square(n) for n in (y, z)): continue
        printf("[{x} -> {y} : {z}]")
        # consider possible actual combinations (s), and assignments of (x, y, z) to (A, B, C)
        for (s, (A, B, C)) in product(subsets((x, y, z), size=len, select="P"), repeat=2):
          ss.append((s, (nconcat(s, base=100) % x == 0), A, B, C))
      
      # B knows his own number, the value of the flag, and that C's number is first
      # so we can find scenarios where B would think he knew the combination
      rs = filter_unique(
        # for all possibilities with C's number first ...
        filter(unpack(lambda s, f, A, B, C: s[0] == C), ss),
        # ... knowing B's number and the value of the flag ...
        unpack(lambda s, f, A, B, C: (B, f)),
        # ... let's you work out the combination
        unpack(lambda s, f, A, B, C: s),
      ).unique
      
      # consider possible scenarios
      for (s, f, A, B, C) in rs:
        # find the index of B's pair
        b = s.index(B)
        # look for candidates with B's number in index b, but flag is the opposite
        actual = set(s1 for (s1, f1, A1, B1, C1) in ss if s1[b] == B == B1 and f1 != f)
        # output solution(s)
        for x in sorted(actual):
          printf("actual = {x} [B={B} f={f} -> attempt = {s}]")
      

      Solution: The combination was 811641.

      Fortunately there is only one candidate set of numbers, such that a 4-digit square is composed of the concatenation of two 2-digit squares.

      That is: 41² = 1681 = 16:81.

      So we know the safe’s combination is some ordering of the numbers (16, 41, 81).

      And B overheard this construction, and also (so he thought) whether the combination is divisible by 41 or not.

      There are 2 scenarios where B can think he is able to deduce the combination:

      B was given number 41, and told C’s number was first.
      He overheard that the combination is divisible by 41.
      In this case B will attempt: (16, 81, 41).

      B was given number 16, and told C’s number was first.
      He overheard that the combination is divisible by 41.
      In this case B will attempt: (41, 16, 81).

      In both cases B got his own number in the correct position, but got the other two in the wrong positions, setting off the alarm.

      So, in both cases, the actual combination is (81, 16, 41), and 181641 is not divisible by 41.

      Like

    • Frits's avatar

      Frits 11:45 pm on 2 March 2021 Permalink | Reply

      from collections import defaultdict
      from itertools import permutations
      
      # 2-digit squares
      sqs = [x * x for x in range(4, 10)]
      
      cands = [] 
      # consider 2 digit numbers for one of the clerks numbers
      for i in range(32, 100):
        # and the square of this gives the other 2 numbers
        i2 = i * i
        # view <i2> as abcd
        (ab, cd) = (i2 // 100, i2 % 100)
        # both of which are 2-digit squares
        if ab not in sqs or cd not in sqs: continue
        cands.append([i, ab, cd])
      
      fnum = defaultdict(list)    # store info for the first 2-digit number
      for cand in cands:
        # uvwxyz is a possible ordering of entries in <cand>
        for uv, wx, yz in permutations(cand):
          uvwxyz = 100 * (100 * uv + wx) + yz
          check = (uvwxyz % cand[0] == 0)
          
          # is there both a True and False combination per first 2-digit number?
          tf = check if uv not in fnum else fnum[uv][0][-1] ^ check
          fnum[uv].append([uvwxyz, check, tf])  
         
        # how many possible numbers are divisible by the first 2-digit number 
        n_divisible = sum(1 for k, vs in fnum.items() for (_, div, _) in vs if div)
        if n_divisible < 3:
          print("Browning thinks the combination is divisible by", cand[0])
        else:  
          print("Browning thinks the combination is not divisible by", cand[0])
        
        # collect first 2-digit numbers where one of the two 6-digit numbers
        # is divisible by <cand[0]> and the other isn't
        both_tf = list({k for k, vs in fnum.items() for (_, _, tf) in vs if tf})
        
        if len(both_tf) == 2:
          # suppose the two 2-digit numbers in <both_tf> are <p> and <q>
          # if B was given number <p> and C was given the first number
          # B has to look at the 6-digit numbers with first digit number <q>
          for i, p in enumerate(both_tf):
            # B will choose the other entry
            for guess, div, _ in fnum[both_tf[1-i]]:
              # B chose <guess> which didn't open the safe
              # but B had got his own number <p> in the right place
              # so we have to switch the other 2-digit numbers 
              
              if (div and n_divisible < 3) or (not div and n_divisible > 3):  
                s = str(guess)
                comb = int(s[2:4] + s[:2] + s[4:]) if guess % 100 == p \
                  else int(s[4:] + s[2:4] + s[:2])
                print(f"combination = {comb}, Brown guessed {guess} "
                      f"if he was given number {p}")  
      

      Like

  • Unknown's avatar

    Jim Randell 8:25 am on 28 February 2021 Permalink | Reply
    Tags: by: Herbert Wright   

    Brain-Teaser 24: [Shades of Gray] 

    From The Sunday Times, 3rd September 1961 [link]

    Someone here in Land’s End Lane of the name of Roger Gray (or is it “Grey”? I never can remember which), seems gratified to find that by allotting distinctive digits against appropriate letters of the alphabet he can, by substituted figures for letters, make an addition sum of his name, the answer to which, converted back into letters in similar manner, spells his house number.

    There seems nothing particularly surprising in this, for if it really is “Gray” and if he happened to live at No. 7, as I do, all he would have to do would be to write:

    and substitute say:

    However, he doesn’t live at No. 7, and moreover, I happen to know that there’s an 8 in his sum.

    So where does Roger live, and what is his sum?

    This puzzle was originally published with no title.

    [teaser24]

     
    • Jim Randell's avatar

      Jim Randell 8:26 am on 28 February 2021 Permalink | Reply

      The house number must be 5 or 6 letters, so there is only a limited number of possibilities.

      We can use some handy routines from the enigma.py library to solve this puzzle. The [[ int2words() ]] function converts a number into English, and then we can use the [[ SubstitutedSum() ]] solver to find solutions to the corresponding alphametic sums.

      The following Python program runs in 127ms.

      from enigma import (irange, int2words, translate, SubstitutedSum, printf)
      
      # suppose the house number is n (not 7)
      for n in irange(1, 100):
        if n == 7: continue
        word = translate(int2words(n).upper(), (lambda x: x if x.isalpha() else ''))
        if not (4 < len(word) < 7): continue
      
        # construct the alphametic sum
        for terms in [('ROGER', 'GRAY'), ('ROGER', 'GREY')]:
          p = SubstitutedSum(terms, word)
          for s in p.solve(verbose=0):
            # one of the letters has to be 8
            if not (8 in s.values()): continue
            # output solution
            printf("{p.text} / {t}", t=p.substitute(s, p.text))
      

      Solution: Roger Gray lives at number 12. The sum is: 94729 + 7953 = 102682.

      Like

    • Frits's avatar

      Frits 10:43 pm on 1 March 2021 Permalink | Reply

      Only calling SubstitutedExpression once, looking for trouble.

      In order to get a decent run time (1 second with PyPy) I had to use some analysis (like skipping all the words ending on “Y”). Only 4 words (THREE, EIGHT, ELEVEN and TWELVE) remain to be checked.

      from enigma import SubstitutedExpression, int2words, translate
      
      def check(num, txt):        # txt has format (_.....)
        # first letter may not be zero
        if len(str(num)) < len(txt) - 3:
          return False
        # number of different digit must match number of letters
        return len(set(str(num))) == len(set(txt)) - 3
        
      words = [] 
      # suppose the house number is n
      for n in range(1, 100):
        if n == 7: continue
        word = translate(int2words(n).upper(), (lambda x: x if x.isalpha() else ''))
        if not(4 < len(word) < 7): continue
        # units: R + Y can't be equal to Y (else R would have to be zero)
        if word[-1] == "Y": continue
        words.append(word)
      
      lower = "".join([x for x in "abcdefghijklmnopqrstuvwxyz" 
                       if x not in "if else or and not sum check str"])
      bits = lower[:len(words)]
      exprs = ["sum([" + ','.join(bits) + "]) == 1"]
      
      for i, word in enumerate(words):
        if len(word) == 6:
          exprs.append(bits[i] + " == 0 or R == 9")
          exprs.append(bits[i] + " == 0 or " + word[0] + " == 1")
          exprs.append(bits[i] + " == 0 or Y - 1 == " + word[-1])
        else:
          exprs.append(bits[i] + " == 0 or " + word[0] + " - 1 == R")
       
        exprs.append(bits[i] + " == 0 or " + word + " - ROGER in {GRAY, GREY}")
        exprs.append(bits[i] + " == 0 or '8' in str(" + word + ")")
        exprs.append(bits[i] + " == 0 or check(" + word + ", '" + word + "')")
        
      # collect letters not used in words, they can't have value 8  
      outside = "".join([x for x in "ROGEYA" if x not in "".join(words)])
      symbols = "".join(set("ROGEYA" + "".join(words))) + bits
      dist = ["ROGEYA" + c for c in [x for x in set(''.join(words)) 
              if x not in "ROGEYA"]]
      
      # puzzle
      p = SubstitutedExpression(
        exprs,
        symbols=symbols,
        distinct=dist,
        answer="(ROGER + GRAY if '8' in str(ROGER + GRAY) else ROGER + GREY), " + 
               " + ".join([x + ' * ' + str(i) for i, x in enumerate(bits)]),
        env=dict(check=check),
        verbose=0,
        d2i=dict([(0, "RG")] +  
                 [(k, bits) for k in range(2, 8)] +
                 [(8, outside + bits)] +
                 [(9, bits)])
      )
      
      # print answer
      sol = set()
      for (_, ans) in p.solve():
        sol.add(words[ans[1]] + " = " + str(ans[0]))  
      
      print(*sol)
      

      Like

  • Unknown's avatar

    Jim Randell 5:03 pm on 26 February 2021 Permalink | Reply
    Tags:   

    Teaser 3049: Plantation 

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

    Jed farms a large, flat, square area of land. He has planted trees at the corners of the plot and all the way round the perimeter; they are an equal whole number of yards apart.

    The number of trees is in fact equal to the total number of acres (1 acre is 4840 square yards). If I told you an even digit in the number of trees you should be able to work out how far apart the trees are.

    How many yards are there between adjacent trees?

    It is now 60 years since the first numbered Teaser puzzle was published — Brain-Teaser 1: Tall story — on 26th February 1961.

    Congratulations to The Sunday Times!

    [teaser3049]

     
    • Jim Randell's avatar

      Jim Randell 5:22 pm on 26 February 2021 Permalink | Reply

      Here is a constructive solution.

      This Python program runs in 56ms.

      Run: [ @repl.it ]

      from collections import defaultdict
      from enigma import irange, inf, is_square, nsplit, peek, printf
      
      # generate (number-of-trees, total-area, size-of-square, distance-between-trees)
      def generate():
        # consider total number of trees (t = 4n)
        for t in irange(4, inf, step=4):
          # this is the same as the total number of acres
          # calculate the area in square yards
          a = 4840 * t
          # but the area is a square
          # and the side is a whole number of yards
          s = is_square(a)
          if s is None: continue
          # so the distance between trees is...
          (d, r) = divmod(4 * s, t)
          if d < 1: break
          if r == 0:
            printf("[t={t} a={a} s={s} d={d}]")
            yield (t, a, s, d)
      
      # collect candidate solutions by the even digits of the number of trees
      digits = {0, 2, 4, 6, 8}
      r = defaultdict(set)
      for (t, a, s, d) in generate():
        for x in digits.intersection(nsplit(t)):
          r[x].add(d)
      
      # look for digits that give a unique distance
      for (x, ds) in r.items():
        if len(ds) == 1:
          printf("digit {x} -> distance {ds} yards", ds=peek(ds))
      

      Solution: The distance between trees is 4 yards.


      Manually:

      If there are t = 4n trees around the perimeter, then we can calculate the distance d between them as:

      d = 88 √(5 / 2n)

      So:

      n⋅d² = 19360 = (2^5)(5)(11^2)

      Which gives the following (d, n) values:

      (d=1, n=19360) → t=77440
      (d=2, n=4840) → t=19360
      (d=4, n=1210) → t=4840
      (d=11, n=160) → t=640
      (d=22, n=40) → t=160
      (d=44, n=10) → t=40

      Looking at the t values we see that, of the even digits, only 8 gives a unique solution.

      Here is a Python program that uses the same approach:

      from collections import defaultdict
      from enigma import divisors_pairs, is_square, irange, nsplit, peek, printf
      
      # generate (number-of-trees, distance-between-trees)
      def generate():
        # n.d^2 = 19360
        for (n, d2) in divisors_pairs(19360, every=1):
          d = is_square(d2)
          if d is None: continue
          # return (t, d)
          t = 4 * n
          printf("[d={d} n={n} -> t={t}]")
          yield (t, d)
      
      # group candidate solutions by the even digits of the number of trees
      digits = set(irange(0, 9, step=2))
      r = defaultdict(set)
      for (t, d) in generate():
        for x in digits.intersection(nsplit(t)):
          r[x].add(d)
      
      # look for digits with a single distance
      for (x, ds) in r.items():
        if len(ds) == 1:
          printf("digit {x} -> distance {ds} yards", ds=peek(ds))
      

      Like

      • Tony Brooke-Taylor's avatar

        Tony Brooke-Taylor 12:57 pm on 27 February 2021 Permalink | Reply

        My solution replicates my manual approach. It is an order or magnitude quicker than yours – I think because I am being much more selective about the cases I try. Ironically it took me about ten times as long to code as it took me to solve the problem manually!

        #let the number of trees on a side be n, and the distance between trees be x yards, so the length of a side is n.x 
        
        #we are told that the total number of trees is equal to the area in acres, so
        #4n=((n.x)**2)/4840 
        
        #For integer values of n, valid values of x**2 must be made up of factors of 4*4840
        
        def factors(n):    
            j = 2
            while n > 1:
                for i in range(j, int(n**(1/2)) + 1):
                    if n % i == 0:
                        n /= i ; j = i
                        yield i
                        break
                else:
                    if n > 1:
                        yield int(n); break
        
        facs=list(factors(4*4840))
        
        #furthermore, if x is an integer, x**2 can only be made up of products of squares, meaning that we can only combine factors in multiples of two
        
        cofac=1
        mults=[]
        for fac in set(facs):
          if facs.count(fac)%2==1: cofac *= fac
          if facs.count(fac)//2>0:mults.append([fac**i for i in range(facs.count(fac)//2+1)])
        
        #mults contains the possible multiples of each valid prime factor; cofac is the product of factors that do not form part of a pair
        
        #use mults to generate all possible values for x
        x_vals=[1]
        for vec in mults:
          x_vals = [(i*j) for i in x_vals for j in vec]
        
        #each value of x generates a possible value for 4n  
        n_vals=list(zip(x_vals,[4*(x**2)*cofac for x in x_vals]))
        
        #we are now looking for the value for 4n which contains a unique instance of a given even number, so let's collect all digits in all our possible 4n's
        
        digits=[]
        for n in n_vals:
          digits=digits+([dig for dig in str(n[1])])
        
        #then find the value of 4n which contains the even digit that appears in only one value of 4n
        for ev in range(2,9,2):
          if digits.count(str(ev)) == 1:
            result_n = [n for n in n_vals if str(ev) in str(n[1])][0]
        
        print("The distance between adjacent trees is",(result_n[0]),"yards.")
        

        Like

        • Jim Randell's avatar

          Jim Randell 4:40 pm on 27 February 2021 Permalink | Reply

          @Tony: I also did some analysis which gives a fairly short manual solution (or a short program). I’ve put the code up on repl.it [link], and I’ll post the analysis later.

          Like

        • Frits's avatar

          Frits 5:26 pm on 27 February 2021 Permalink | Reply

          @Tony,

          I have taken a similar approach like you (number of trees on a side is n) but I reach a different answer.

          At the Notes section of the Enigmatic Code (link in the About section) site Jim has described how you can include Python code so that it is displayed with colors.

          Like

          • Tony Brooke-Taylor's avatar

            Tony Brooke-Taylor 9:48 am on 28 February 2021 Permalink | Reply

            Brian has spotted my error. Thanks for the tip re: posting. Hopefully any posts I make in future will be more legible.

            Like

        • Brian Gladman's avatar

          Brian Gladman 9:31 am on 28 February 2021 Permalink | Reply

          Line 38 should be:

          n_vals = list(zip(x_vals, [16 * 4840 // (x ** 2) for x in x_vals]))
          

          Like

          • Tony Brooke-Taylor's avatar

            Tony Brooke-Taylor 9:46 am on 28 February 2021 Permalink | Reply

            So it should, thanks.

            Like

    • Frits's avatar

      Frits 4:53 pm on 27 February 2021 Permalink | Reply

      from enigma import divisors_pairs
      from math import isqrt
      
      #          n trees  
      #     x . . . . . . . x
      #  n  .               . n     T = 4 * (n - 1),  total number of trees
      #     .               .             
      #  t  .    Jed's      . t     A = (n - 1)^2 * D^2,  A = area, D = distance
      #  r  .               . r 
      #  e  .    land       . e     no acres A / 4840 = T
      #  e  .               . e   
      #  s  .               . s     so (n - 1) * D^2 = 4 * 4840 = 19360
      #     x . . . . . . . x
      #          n trees  
        
      # make a list of possible <T> and <D> values
      TD = [(4 * b, sa) if sa * sa == a else (4 * a, sb) 
            for (a, b) in divisors_pairs(19360) 
            if (sa := isqrt(a)) ** 2 == a or (sb := isqrt(b)) ** 2 == b]
      
      # check if a certain even digit occurs in only one of the <T> values
      for dgt in '02468':
        if len(q := [x[1] for x in TD if dgt in str(x[0])]) == 1:
          print(f"answer = {q[0]} yards")
      

      Like

  • Unknown's avatar

    Jim Randell 10:43 am on 25 February 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 846: The Rajah’s jewels 

    From The Sunday Times, 2nd October 1977 [link]

    It was a frightened, breathless, but very charming young lady who knocked on my office door late one night.

    “They have gone”, she said: “The Rajah’s rubies are no longer in their ancestral home”.

    I visited the scene of the crime and discovered a tattered piece of paper on which was written:

    Where were the jewels hidden?

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

    [teaser846]

     
    • Jim Randell's avatar

      Jim Randell 10:44 am on 25 February 2021 Permalink | Reply

      We can use the [[ SubstitutedDivision() ]] solver from the enigma.py library to solve the alphametic long division sum, and then we can create a map of digits to letters, and use that to translate to final string of digits back into letters.

      This Python program runs in 58ms.

      Run: [ @repl.it ]

      from enigma import (SubstitutedDivision, translate, printf)
      
      # the alphametic division puzzle
      p = SubstitutedDivision(
        "?DEEF? / NU = O?SU",
        [ "?DE - NU = UR", "UR? - DTE = SH", "SH? - UT? = DF", "DFD - DT? = T" ],
      )
      
      # solve the puzzle
      for s in p.solve(verbose=0):
        # map digits -> symbols
        d = dict((str(v), k) for (k, v) in s.d.items())
        # translate the string of digits
        r = translate("4936270681427669705719691270187069477266", d)
        # output solution
        printf("{r} [{s.a} / {s.b} = {s.c} (rem {s.r})]")
      

      Solution: The message reads: “UNDER THE FOURTEENTH STONE NORTH OF THE NUT TREE”.

      The division sum is: 136683 ÷ 94 = 1454 (remainder 7).

      Which gives us a mapping between digits and letters.

      The string of digits then translates as:

      UNDERTHEFOURTEENTHSTONENORTHOFTHENUTTREE

      which, with appropriate breaks reads:

      UNDER THE FOURTEENTH STONE NORTH OF THE NUT TREE

      Like

  • Unknown's avatar

    Jim Randell 8:42 am on 23 February 2021 Permalink | Reply
    Tags: ,   

    Teaser 2777: Summing up 2015 

    From The Sunday Times, 13th December 2015 [link] [link]

    I asked Harry and Tom to write down three numbers that between them used nine different digits and which added to 2015. They each succeeded and one of Harry’s three numbers was the same as one of Tom’s. I noticed that Harry’s three numbers included a perfect square and Tom’s included a higher perfect square.

    What were those two squares?

    [teaser2777]

     
    • Jim Randell's avatar

      Jim Randell 8:44 am on 23 February 2021 Permalink | Reply

      As stated there are multiple solutions to the puzzle.

      Having tackled similar problems before it seems likely that the intention of the setter is:

      “… three numbers that between them used nine different digits, each of them [exactly] once …”

      or possibly:

      “… three 3-digit numbers that between them used nine different digits …”

      Any solution for the latter will appear as a solution for the former, so we will use the first one to solve the puzzle (as it turns out it does give a unique answer).

      The following Python code generates possible sums that use a square and 2 other numbers to produce the required total, and where each of 9 digits is used exactly once in the summands.

      It then choose sums that use different squares, but have one of the other numbers in common.

      It runs in 95ms.

      Run: [ @repl.it ]

      from enigma import (
        cproduct, powers, inf, is_duplicate, irange, concat, multiset,
        group, unpack, subsets, intersect, join, arg, printf
      )
      
      # target sum
      Y = arg(2015, 0, int)
      printf("[Y={Y}]")
      
      # generate (s, a, b) sums, s is square, s + a + b = Y
      def generate(Y):
        # consider squares
        for s in powers(0, inf, 2):
          if s > Y: break
          if is_duplicate(s): continue
      
          # find sums: s + a + b = Y
          t = Y - s
          for a in irange(0, t // 2):
            b = t - a
            # check for sums with 9 different digits
            ds = concat(s, a, b)
            # check for 9 different digits
            if len(ds) == 9 and len(set(ds)) == 9:
              yield (s, a, b)
      
      # group sums by the square
      d = group(generate(Y), by=unpack(lambda s, a, b: s))
      
      # collect the squares involved
      r = multiset()
      
      # consider squares for Harry and Tom
      for (sH, sT) in subsets(sorted(d.keys()), size=2):
      
        # choose decompositions
        for (dH, dT) in cproduct([d[sH], d[sT]]):
      
          # that share a number
          if intersect([dH, dT]):
            printf("[H = {dH}; T = {dT}]", dH=join(dH, sep=" + "), dT=join(dT, sep=" + "))
            r.add((sH, sT))
      
      # output solutions
      for (k, v) in r.most_common():
        printf("squares = {k} [{v} solutions]", k=join(k, sep=", "))
      

      Solution: Harry’s square was 324. Tom’s square was 784.

      There are two ways to arrive at the solution:

      Harry: 324 + 785 + 906 = 2015
      Tom: 784 + 325 + 906 = 2015

      Harry: 324 + 786 + 905 = 2015
      Tom: 784 + 326 + 905 = 2015

      The summands in each sum use all the digits exactly once except for the digit 1.

      To see the multiple solutions without the restriction that each of the 9 digits is used exactly once, you can remove line 15 and the [[ len(ds) == 9 ]] clause from line 24.


      The same puzzle could have been set in 2019, and had the same answer.

      If the puzzle were set in 2022, there would only be one way to arrive at the (different) answer.

      Like

    • Frits's avatar

      Frits 2:04 pm on 23 February 2021 Permalink | Reply

      I assumed Harry’s and Tom’s squares were not the number they shared (ABCD).

      from enigma import SubstitutedExpression, is_square
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        # Use 4-digit numbers as sum is limited to 2015
        
        # to reduce the output assume EFGH (Harry) and MNOP (Tom) 
        # are both perfect squares
        "is_square(EFGH)",
       
        # use a variable as right hand side to reduce the number of loops
        "2015 - ABCD - EFGH = IJKL",
        
        # nine different digits
        "len(str(ABCD) + str(EFGH) + str(IJKL)) == \
         len(set(str(ABCD) + str(EFGH) + str(IJKL))) == 9",
        
        # Tom's included a higher perfect square
        "MNOP > EFGH and is_square(MNOP)",
       
        # use a variable as right hand side to reduce the number of loops
        "2015 - ABCD - MNOP = QRST",
        
        # nine different digits
        "len(str(ABCD) + str(MNOP) + str(QRST)) == \
         len(set(str(ABCD) + str(MNOP) + str(QRST))) == 9",
        ],
        answer="EFGH, MNOP",
        d2i=dict([(k, "AEIMQ") for k in range(3, 10)]),
        verbose=0,
        distinct="",
      )
      
      # Print answers
      sols = set(sol for (_, sol) in p.solve())
      print(f"answer = {sols}")
      

      Like

  • Unknown's avatar

    Jim Randell 8:53 am on 21 February 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 23: [Money sharing] 

    From The Sunday Times, 27th August 1961 [link]

    A man divided 8s. 4d. among his three sons so that the difference between the shares of the eldest and youngest, expressed in halfpence, was a perfect square. The total of Alan’s and Bob’s shared was an exact multiple of 5½d.; the total of Bob’s and Clive’s shares an exact multiple of 6½d.; and the total of Alan’s and Clive’s shares an exact multiple of 9½d.

    What was the name of the second son, and what was his share?

    This puzzle was originally published with no title.

    [teaser23]

     
    • Jim Randell's avatar

      Jim Randell 8:55 am on 21 February 2021 Permalink | Reply

      1s = 12d.

      Working in half-pennies (hp) the total amount to distribute is 100 d = 200 hp.

      And we are told:

      11 | (A + B)
      13 | (B + C)
      19 | (A + C)

      And the difference between two of the numbers is a square number of half pennies.

      The following Python program runs in 53ms.

      Run: [ @repl.it ]

      from enigma import irange, is_square, div, printf
      
      # solve the equations
      def solution(middle, AB, AC, BC):
        A = div(AB + AC - BC, 2)
        B = div(AB + BC - AC, 2)
        C = div(AC + BC - AB, 2)
        if not any(x is None or x < 0 for x in (A, B, C)):
          # output solution
          printf("A={A} hp, B={B} hp, C={C} hp; middle = {middle}")
      
      # A + C is some multiple of 19
      for AC in irange(19, 200, step=19):
      
        # B + C is some multiple of 13
        for BC in irange(13, 200, step=13):
      
          # A + B is some multiple of 11
          # and (A + B) + (B + C) + (A + C) = 2(A + B + C) = 400
          AB = 400 - (AC + BC)
          if AB % 11 > 0: continue
      
          # one of the differences is a square
          # B - C = (A + B) - (A + C)
          if is_square(abs(AB - AC)): solution('A', AB, AC, BC)
          # A - C = (A + B) - (B + C)
          if is_square(abs(AB - BC)): solution('B', AB, AC, BC)
          # A - B = (A + C) - (B + C)
          if is_square(abs(AC - BC)): solution('C', AB, AC, BC)
      

      Solution: The middle son was Clive. His share was 45d = (3s 9d).

      The shares are:

      A: 5 hp = 2.5 d
      B: 105 hp = 52.5 d (= 4s 4.5d)
      C: 90 hp = 45 d (= 3s 9d)

      And we see:

      A + B = 110 hp = 10× 11 hp
      B + C = 195 hp = 15× 13 hp
      A + C = 95 hp = 5× 19 hp

      And the difference between the eldest and the youngest is: BA = 10² hp.

      Like

    • Frits's avatar

      Frits 11:53 am on 21 February 2021 Permalink | Reply

      from enigma import SubstitutedExpression, is_square, peek
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [# A = AST, B = BVW, C = CYZ
        
        # start with the most restrictive constraint
        
        # A + C is some multiple of 19
        "(AST + CYZ) % 19 == 0",
        
        # divide 200 half-pennies over 3 sons
        "200 - AST - CYZ = BVW", 
        
        # B + C is some multiple of 13
        "(BVW + CYZ) % 13 == 0",
        
        # A + B is some multiple of 11
        "(AST + BVW) % 11 == 0",
        
        # difference between the eldest and youngest was a perfect square
        "peek(2 - i for (i, (x, y)) in \
         enumerate(zip([AST, CYZ, BVW], [BVW, AST, CYZ])) \
         if is_square(abs(x - y))) = I",
        ],
        answer="I, [AST, BVW, CYZ][I]",
        d2i=dict([(2, "ABC")] + [(k, "ABCI") for k in range(3, 10)]),
        distinct="",
        reorder=0,
        verbose=0)   
      
      for (_, ans) in p.solve():
        print(f"{['Alan', 'Bob', 'Clive'][ans[0]]} with share {ans[1]} half-pennies")
      

      Like

      • Jim Randell's avatar

        Jim Randell 12:49 pm on 21 February 2021 Permalink | Reply

        Here’s an alternative solution using the [[ SubstitutedExpression() ]] solver:

        from enigma import (SubstitutedExpression, subsets, is_square, printf)
        
        # find candidate (A, B, C) values
        p = SubstitutedExpression(
          [ "A + B + C == 200", "(A + B) % 11 = 0", "(B + C) % 13 = 0", "(A + C) % 19 = 0" ],
          base=201,
          distinct=(),
          answer="(A, B, C)",
          template="",
        )
        
        # look at possible (A, B, C) values
        for (_, vs) in p.solve(verbose=0):
          # the values for the eldest and youngest differ by a square number
          for ((i, x), (j, y)) in subsets(enumerate(vs), size=2):
            if is_square(abs(x - y)):
              # identify the middle son
              m = "ABC"[3 - i - j]
              printf("A={vs[0]} hp, B={vs[1]} hp, C={vs[2]} hp; middle={m}")
        

        Like

    • Hugh Casement's avatar

      Hugh Casement 1:51 pm on 21 February 2021 Permalink | Reply

      Theoretically Alan and Clive could each have received 57 ha’pence and Bob 86.
      Zero is also a square number! However, I doubt that is the intended solution.

      Like

      • Jim Randell's avatar

        Jim Randell 2:02 pm on 21 February 2021 Permalink | Reply

        @Hugh: Well spotted!

        In my programs, both 0 and None evaluate to false, so using the return value from [[ is_square() ]] as a boolean value will reject 0. (It also doesn’t consider 0 to be allowed as a multiple of 11, 13, 19).

        You can explicitly check [[ is_square(...) is not None ]], or just import [[ is_square_p() ]] instead which returns a boolean.

        And if you do that you do indeed get two solutions:

        % python teaser23b.py
        A=5 hp, B=105 hp, C=90 hp; middle = C
        A=57 hp, B=86 hp, C=57 hp; middle = B
        

        The published solution is: “Clive, 3s 9d”.

        Perhaps the setter intended the shares to be three different values.

        Like

    • John Crabtree's avatar

      John Crabtree 6:58 pm on 22 February 2021 Permalink | Reply

      Working in 1/2d, then A + B + C = 200
      A + B = 11p, p ≤ 18
      B + C = 13q, q ≤ 15
      A + C = 19r, r ≤ 10
      Summing the three gives 400 = 11p + 13q + 19r
      Using r as a variable as it has the fewest possible values:
      B = 200 – 19r, p = 8 + 3r mod 13, q = 3 + 7r mod 11 which leads to
      A = 31 + 52r mod 143
      C = 112 + 110r mod 143
      It is then straightforward to manually generate a table of values for A, B and C for each r. r = 1, 2 and 4 give A+B+C = 343. r = 3 and r = 5 to 10 give A+B+C = 200 and include the two solutions noted by Jim.

      Like

  • Unknown's avatar

    Jim Randell 4:51 pm on 19 February 2021 Permalink | Reply
    Tags:   

    Teaser 3048: Nero Scrag from Carregnos 

    From The Sunday Times, 21st February 2021 [link] [link]

    Our holiday rep, Nero, explained that in Carregnos an eight-digit total of car registrations results from combinations of three Greek capital letters after four numerals (e.g. 1234 ΩΘΦ), because some letters of the 24-letter alphabet and some numerals (including zero) are not permitted.

    For his own “cherished” registration the number tetrad is the rank order of the letter triad within a list of all permitted letter triads ordered alphabetically. Furthermore, all permitted numeral tetrads can form such “cherished” registrations, but fewer than half of the permitted letter triads can.

    Nero asked me to guess the numbers of permitted letters and numerals. He told me that I was right and wrong respectively, but then I deduced the permitted numerals.

    List the permitted numerals in ascending order.

    [teaser3048]

     
    • Jim Randell's avatar

      Jim Randell 5:29 pm on 19 February 2021 Permalink | Reply

      Nero Scrag is an anagram of Carregnos, which itself may be written as “car reg nos” = “car registration numbers”.

      The wording of the puzzle is a bit convoluted, but I think there is a unique solution.

      This Python program runs in 56ms.

      Run: [ @repl.it ]

      from enigma import (irange, ndigits, group, unpack, printf)
      
      # generate candidate solutions
      # return (number-of-digits, number-of-letters, total-number-of-digit-tetrads, total-number-of-letter-triads)
      def generate():
        # consider number of digits available (< 9)
        for nd in irange(1, 8):
          # number of digit tetrads
          td = nd**4
      
          # consider number of letters available (< 23)
          for ns in irange(1, 22):
            # number of letter triads
            ts = ns**3
      
            # total number of possible registrations
            t = td * ts
            # is 8 digits
            if not (ndigits(t) == 8): continue
      
            # all digit tetrads correspond to a letter triad
            # so remove impossible situations
            if 1111 * nd > ts: continue
      
            # fewer than half the letter triads correspond to number tetrads
            if not (ts > 2 * td): continue
      
            printf("[nd={nd} -> td={td}; ns={ns} -> ts={ts}; t={t}]")
            yield (nd, ns, td, ts)
      
      # group candidate solutions by the number of letters (which the setter guessed right)
      d = group(generate(), unpack(lambda nd, ns, td, ts: ns))
      
      # look for a group with only two candidates
      for (k, vs) in d.items():
        if len(vs) != 2: continue
        # look for candidates with only one set of digits
        for (nd, ns, td, ts) in vs:
          # are the digits forced to be 1..nd?
          if 1111 * (nd + 1) > ts:
            # output solution
            printf("digits = 1..{nd} [nd={nd} ns={ns}]")
      

      Solution: The digits are: 1, 2, 3, 4, 5, 6, 7.

      The setter guessed there were 20 letters and 6 digits.

      He was right about the number of letters, but wrong about the number of digits.

      The only other possibility with 20 letters is 7 digits, so that must be the correct number of digits.

      With 20 digits there are 20^3 = 8000 possible letter triads (which can be enumerated from 1 to 8000).

      The lowest valued set of 7 digits is 1..7, and the highest numeric tetrad in this case is 7777, which corresponds to an entry in the enumeration of letter triads.

      If not all of the digits from 1..7 are permissible, then the highest digit would be 8 or 9, and the highest numeric tetrad would be 8888 or 9999, but neither of these correspond to entries in the enumeration of letter triads. So these situations are not possible.

      So we know that the set of permissible digits must be 1..7.

      This means there are 2401 digit tetrads and 8000 letter triads.

      Every digit tetrad (from 1111 to 7777) corresponds to one of the letter triads, but any letter triad that has an index containing a 0, 8, or 9 does not have a corresponding digit tetrad. (Where we represent the indices with leading zeros to pad them to 4 digits, if necessary). There are 5599 of these, so less than half of the letter triads have a corresponding digit tetrad, as required.

      Like

      • Frits's avatar

        Frits 9:10 pm on 19 February 2021 Permalink | Reply

        Ah, I see you now have a different answer. I couldn’t understand your previous solution.
        This is a puzzle where I needed your code to understand the puzzle.

        Like

        • Jim Randell's avatar

          Jim Randell 7:24 am on 20 February 2021 Permalink | Reply

          @Frits: The wording of the puzzle is a bit complicated, but I think it makes sense.

          In my original program I got the final check the wrong way round, but I have corrected that now.

          Like

      • Jim Randell's avatar

        Jim Randell 8:14 am on 28 February 2021 Permalink | Reply

        Here is a more detailed explanation of the logic used in the program:

        Some digits (including 0) are not permitted, so there are fewer than 9 digits available.

        And some letters are not permitted, so there are fewer than 23 letters available.

        If there are nd digits available, then we can make nd^4 possible digit tetrads.

        And if there are ns letters (symbols) available, then we can make ns^3 possible letter triads.

        The total number of registrations that can be made is therefore: (nd^4) × (ns^3), and this must be an 8-digit number.

        Also the letter triads can be enumerated (in lexicographical order) from 1 to ns^3.

        And each digit tetrad provides a valid index into the list of enumerated letter triads.

        So if d is a permitted digit then the digit tetrad dddd must be an index into the list of letter triads.

        So: 1111 × d ≤ ns^3.

        And with nd possible digits, that exclude the digit 0, the maximum permitted digit must be at least nd.

        So we can skip situations where: 1111 × nd > ns^3, as there are no viable digit sets.

        We are also told that fewer than half the letter triads correspond to digit triads.

        So: (ns^3) > 2 × (nd^4)

        These restrictions limit the possible values of nd and ns.

        The setter makes guesses for these values, and gets ns right and nd wrong.

        But from this information he is able to deduce, not just the value of nd, but the actual set of permitted digits.

        So from his guess for ns, we know there must be 2 possibilities for nd. On being told the candidate he chose was wrong, the setter can deduce that the other candidate must be the correct one.

        So now he knows the values of both ns and nd. But he can go further and deduce the actual set of permitted digits.

        This means that there can only be one possible set of digits for the values of ns and nd.

        The set with the smallest valued digit tetrads will be {1..nd}, and we already know that the value (1111 × nd) indexes into the list of letter triads.

        And we want to exclude all other possible sets. A set that excludes just one of the digits 1..nd would have a maximum digit of (nd + 1), and in order for this to be excluded the maximum digit tetrad of (1111 × (nd + 1)) must be larger than the maximum index into the list of letter triads.

        i.e.: (1111 × (nd + 1)) > ns^3

        In that case only the set of digits {1..nd} forms a viable set, so this provides the answer.

        Like

    • Brian Gladman's avatar

      Brian Gladman 10:44 pm on 19 February 2021 Permalink | Reply

      @Jim I have the same logic up to line 30 but I don’t see the rationale for only considering solutions with two candidates (line 36). But it does seem necessary to obtain a unique solution.

      Like

      • Jim Randell's avatar

        Jim Randell 10:54 pm on 19 February 2021 Permalink | Reply

        @Brian: My reasoning was that when the setter guesses the number of permissible letters and digits, and gets the number of letters right, but the number of digits wrong, he is then able to deduce the correct number of digits. So for the number of letters guessed, there must be only two options for the number of digits. The setter went for the wrong one to begin with, but on being told he was wrong he was able to deduce the correct number, so there was only one other option remaining.

        Like

        • Brian Gladman's avatar

          Brian Gladman 11:37 pm on 19 February 2021 Permalink | Reply

          Thanks Jim. But it is logical for the setter to apply your line 40 logic before he is asked to guess and if he does this he only needs the first of the two answers to deduce the solution. So we have to assume he is acting illogically in order to deduce a unique solution, which seems a bit odd to me.

          Like

          • Jim Randell's avatar

            Jim Randell 7:17 am on 20 February 2021 Permalink | Reply

            @Brian: But the setter doesn’t know he is going to be able to deduce the set of digits before he makes his guesses. Once he gets the answers however he knows how many letters there are (as he guessed that correctly), and as explained above there is only one candidate remaining for the number of digits, so he knows that too.

            He then realises that given those values for the number of digits and number of letters, there is only one possible set of digits that can work, so he can deduce what the digits are (not just how many of them there are).

            We aren’t told what guesses the setter made, but the fact that the scenario unrolled as described lets us work out what the setters guesses must have been, and so we can also determine what the set of digits must be, and hence answer the puzzle.

            Like

            • Brian Gladman's avatar

              Brian Gladman 12:54 pm on 20 February 2021 Permalink | Reply

              Thanks Jim. My logic was originally the same as yours (up to your line 30) but I have now changed it to avoid the assumption (on your line 23) that the number of digits in use and the maximum digit in use are the same. We now arrive at the same answer in different ways.

              Like

              • Jim Randell's avatar

                Jim Randell 1:45 pm on 20 February 2021 Permalink | Reply

                @Brian: I don’t assume the maximum digit is the same as the number of digits, only it is at least this value (which it must be, as 0 is excluded).

                But I think in your method you also need to consider that the maximum digit could be 9.

                Liked by 1 person

                • Brian Gladman's avatar

                  Brian Gladman 5:09 pm on 20 February 2021 Permalink | Reply

                  @Jim. Good point, changed now.

                  Like

                • Tony Brooke-Taylor's avatar

                  Tony Brooke-Taylor 9:39 am on 21 February 2021 Permalink | Reply

                  Jim, doesn’t your line 40 forbid cases where the maximum digit (int(ts/1111)) is greater than the number of digits (ns)? I am sure we must apply that condition somewhere to get a unique answer, and it is the only way our subject could have inferred all the digits.

                  I applied almost exactly the same logic as you but I did a bit of analysis first to reduce the ranges for the two main loops. We only need to explore the space defined by 10^7 <=T<10^8, where T=nd^4*ns^3. Reduces run-time by about half.

                  Like

                  • Jim Randell's avatar

                    Jim Randell 9:54 am on 21 February 2021 Permalink | Reply

                    @Tony: Yes. That line is there to ensure there is only one possible set of digits at that stage, because if there were more than one the setter would not have been able to deduce what the permissible digits are.

                    And there is only one set of digits if the maximum digit is constrained to be the same as the number of digits (as 0 is excluded). So the set of digits is {1..nd}.

                    Like

    • Frits's avatar

      Frits 12:58 am on 21 February 2021 Permalink | Reply

      Under the assumption that “some letters” means “at least 2 letters”

      from collections import defaultdict
      from enigma import SubstitutedExpression, divc
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
        #  N = number of permitted numerals
        # LE = number of permitted letters
        
        # the maximum possible car registration has at least 8 characters:
        # formula: no_numerals ** 4  *  no_letters ** 3 >= 10 ** 7 
        #
        # choose the highest number of permitted letters (twenty two)  
        # to calculate the lowest number of permitted numbers
        "N >= (divc(10 ** 7, 22 ** 3) ** 0.25)",
        "N < 9",
        
        "LE >= (divc(10 ** 7, N ** 4) ** (1 / 3))",
        "LE < 23",
        
        # consider the maximum 4-digits numbers (1111 * highest digit)
        # the smallest must not be greater than the number of letter triads
        "1111 * N <= LE ** 3",
        
        # fewer than half the letter triads correspond to number tetrads
        "LE ** 3 > 2 *( N ** 4)",
        ],
        answer="LE, N, LE ** 3",
        d2i=dict([(0, "N")] + [(k, "L") for k in range(3, 10)]),
        distinct="",
        verbose=0)   
      
      # collect and sort solutions
      sols = sorted(y for _, y in p.solve())
      
      # group solutions by number of permitted letters
      d = defaultdict(list)
      for le, n, ntriads in sols:
        d[le].append([n, ntriads])
        
      for k, v in d.items():
        # only with 2 options for the number of permitted numerals 
        # you can still make deductions
        if len(v) != 2: continue
        
        # "I deduced the permitted numerals"
        
        # you can only know the permitted numerals if they are 1...N
        # this must be the last entry in <v>
        
        # check that N+1 is not permitted as a numeral
        if 1111 * (v[1][0] + 1) > v[1][1]:  
          print(f"answer 1...{v[1][0]}")
      

      Like

      • Tony Brooke-Taylor's avatar

        Tony Brooke-Taylor 9:43 am on 21 February 2021 Permalink | Reply

        Frits, your lines 15-19 are equivalent to the analysis I referred to in my comment to Jim. I also relaxed the assumption that ‘some’ meant ‘at least two’, and the solution still works.

        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