Design a site like this with WordPress.com
Get started

Tagged: by: Danny Roth Toggle Comment Threads | Keyboard Shortcuts

  • Jim Randell 4:34 pm on 30 December 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3145: Easier to ask the audience 

    From The Sunday Times, 1st January 2023 [link] [link]

    “I have forgotten the phone number!”, complained Martha, about to phone a friend. “So have I!”, replied George, “but I have some vague memories of it. It is a perfect square with all the digits different, and the last digit is equal to the number of digits to be dialled. The last-but-one digit is odd and one of the digits is zero. Also the second and third and last-but-one digits are all exact multiples of the first digit. Maybe you can work it out”.

    Martha proceeded to dial the number correctly.

    What number did she dial?

    Happy New Year from S2T2!

    [teaser3145]

     
    • Jim Randell 4:47 pm on 30 December 2022 Permalink | Reply

      The final digit of the square number tells us how long it is, so we only need to check up to 9 digits, and it must have a “second”, “third”, and “last-but-one” digit, so (assuming these are all different) there need to be at least 5 digits. And to also fit a 0 digit in there must be at least 6 digits.

      And a perfect square cannot end with 7 or 8, so we only need to consider 6 or 9 digit numbers.

      This Python program just tries all candidate squares in this range (and we could add further analysis to reduce the number of candidates).

      It runs in 122ms. (Internal run time is 23.3ms).

      Run: [ @replit ]

      from enigma import (powers, inf, nsplit, printf)
      
      # is <x> a (proper) multiple of <n>?
      is_multiple = lambda n, x: (x > n > 0 and x % n == 0)
      
      # consider squares with 6 - 9 digits
      for n in powers(317, inf, 2):
        # split into digits
        ds = nsplit(n)
        k = len(ds)
        # there are at most 9 digits...
        if k > 9: break
        # but not 7 or 8
        if not (k == 6 or k == 9): continue
        # ... and they are all different
        if not (len(set(ds)) == k): continue
        # the final digit is equal to the total number of digits
        if not (ds[-1] == k): continue
        # the last but one digit is odd
        if not (ds[-2] % 2 == 1): continue
        # and the digit 0 is used
        if not (0 in ds): continue
        # the 2nd, 3rd and last but 1 digit are multiples of the 1st digit
        if not all(is_multiple(ds[0], ds[i]) for i in (1, 2, -2)): continue
        # output solution
        printf("n = {n}")
      

      Solution: The number is: 173056.

      173056 = 416².

      Like

      • Jim Randell 10:37 pm on 31 December 2022 Permalink | Reply

        With a bit of analysis we see that a 6-digit square must be of the form 1xx0x6, and so is the square of a number in the range [351, 445] that ends in the digit 4 or 6.

        And a 9-digit square must be of the form 1xxxxxxx9, and so is the square of a number in the range [11093, 13698] that ends in the digit 3 or 7. (Thanks to Frits for correcting my upper bound).

        This Python program just checks these numbers.

        It runs in 52ms. (Internal runtime is 1.2ms).

        Run: [ @replit ]

        from enigma import (irange, nsplit, printf)
        
        # is <x> a (proper) multiple of <n>?
        is_multiple = lambda n, x: (x > n > 0 and x % n == 0)
        
        def check_n(n):
          # split into digits
          ds = nsplit(n)
          k = len(ds)
          # all digits are all different
          if not (len(set(ds)) == k): return
          # the final digit is equal to the total number of digits
          if not (ds[-1] == k): return
          # the last but one digit is odd
          if not (ds[-2] % 2 == 1): return
          # and the digit 0 is used
          if not (0 in ds): return
          # the 2nd, 3rd and last but 1 digit are multiples of the 1st digit
          if not all(is_multiple(ds[0], ds[i]) for i in (1, 2, -2)): return
          # output solution
          printf("n = {n}")
        
        def check(seq):
          for i in seq:
            check_n(i * i)
        
        # check 6-digit numbers of the form 1xx0x6
        check(irange(354, 444, step=10))
        check(irange(356, 436, step=10))
        
        # check 9-digit numbers of the form 1xxxxxxx9
        check(irange(11093, 13693, step=10))
        check(irange(11097, 13697, step=10))
        

        Like

        • Frits 11:07 am on 1 January 2023 Permalink | Reply

          @Jim, I came to the same conclusion but with ranges [351-445] (you probably had a typo) and [11093-13698].

          Like

        • Tony Smith 2:17 pm on 1 January 2023 Permalink | Reply

          The only squares with odd penultimate digits have last digit 6.

          Like

          • Jim Randell 2:21 pm on 1 January 2023 Permalink | Reply

            Neat. So we only need to check 9 cases:

            check(irange(356, 436, step=10))
            

            Like

            • Frits 3:25 pm on 1 January 2023 Permalink | Reply

              @Jim, the check(irange(354, 444), step=10) is still needed.

              Like

              • Jim Randell 9:19 pm on 1 January 2023 Permalink | Reply

                Right. Still, it is only 19 cases to check. Easy enough to do on a pocket calculator.

                Like

      • Jim Randell 2:10 pm on 2 January 2023 Permalink | Reply

        Here is a version that uses less brute force (and less analysis).

        The final two digits of the square number depend only on the final two digits of its root, so we can consider possible values for these 2 digits, and find those that give acceptable final two digits of the square. The final digit must corresponding to the digit length of the square, and then penultimate digit must be odd.

        We then know the length of the square, final two digits and the leading digit must be a divisor of the penultimate digit, so we can consider possible roots that give the required conditions.

        In the end we only need to apply the remaining tests on a handful of values.

        This Python program runs in 51ms. (Internal runtime is 368µs).

        Run: [ @replit ]

        from enigma import (irange, inf, sq, nsplit, sqrt, ceil, printf)
        
        # is <x> a (proper) multiple of <n>?
        is_multiple = lambda n, x: (x > n > 0 and x % n == 0)
        
        # consider final 2 digits of the root of the perfect square
        for i in irange(0, 99):
          # and calculate the final 2 digits of the square
          (y, z) = nsplit(sq(i), 2)
          # y must be odd
          if y % 2 != 1: continue
          # z counts the total number of digits, which cannot be less than 5
          if z < 5: continue
          # y is a multiple of the leading digit (a)
          for a in irange(1, y // 2):
            if y % a != 0: continue
            # consider possible roots
            lo = ceil(sqrt(a * pow(10, z - 1)) - i, 100)
            for j in irange(lo, inf, step=100):
              n = sq(i + j)
              ds = nsplit(n)
              # there must be z digits
              if len(ds) < z: continue
              if len(ds) > z: break
              # and it must start with a
              if ds[0] < a: continue
              if ds[0] > a: break
              # there must be a 0 digit
              if 0 not in ds: continue
              # each digit must be different
              if len(set(ds)) != z: continue
              # 2nd and 3rd digits must be multiples of the 1st
              if not (is_multiple(a, ds[1]) and is_multiple(a, ds[2])): continue
              # output solution
              printf("n = {n} [{r}^2]", r=i + j)
        

        Liked by 1 person

    • GeoffR 10:25 am on 31 December 2022 Permalink | Reply

      # check 5 - 9 digit square numbers
      sq = [x * x for x in range(100, 31622)]
      
      for n in sq:
        # check the number of digits are all different
        if len(str(n)) == len(set(str(n))):
          n_str = str(n)
      
          # check last-but-one digit is odd 
          # ... and one of the digits is zero after the third digit
          if int(n_str[-2]) % 2 == 1 and '0' in n_str[2:]:
            # check 2nd and third digits are a multiple of the 1st digit
            if int(n_str[1]) % int(n_str[0]) == 0:
              if int(n_str[2]) % int(n_str[0]) == 0:
                  
                # check last-but-one digit is a multiple of the 1st digit
                if int(n_str[-2]) % int(n_str[0]) == 0:
                  # check last digit is length of number dialled
                  if int(n_str[-1]) == len(str(n)):
                    print(f"Number dialled was {n}.")
      
      
      

      Like

    • GeoffR 11:32 am on 2 January 2023 Permalink | Reply

      This solution uses previous postings in that the telephone number is six or nine digits long and the possible ranges of square values.

      from enigma import nsplit, all_different
      
      # check six digit phone numbers (abcdef)
      n = 353
      
      while n < 445:
        n += 1
        n1 = n * n  # potential square tel no.
        a, b, c, d, e, f = nsplit(n1)
        
        # teaser digit conditions
        if f == 6 and d == 0 and e % 2 == 1:
          if all_different(a, b, c, d, e, f):
            # 2nd, 3rd and penultimate digits
            #...are multiples of 1st digit
            if all(x % a == 0 for x in (b, c, e)):
              print(f"Martha dialled {n1}.")
              break
      
      # check nine digit phone numbers (abcdefghi)
      m = 11092
      
      while m < 13699:
        m += 1
        m1 = m * m  # potential square tel no.
        a, b, c, d, e, f, g, h, i = nsplit(m1)
        
        # teaser digit conditions
        if i == 9 and 0 in (d, e, f, g) and h % 2 == 1:
          if all_different(a, b, c, d, e, f, g, h, i):
            # 2nd, 3rd and penultimate digits
            # ...are multiples of 1st digit
            if all(x % a == 0 for x in (b, c, h)):
              print(f"Martha dialled {m1}.")
              break
      
      
      
      
      

      Like

    • GeoffR 5:20 pm on 2 January 2023 Permalink | Reply

      This program checks for a 6-digit solution only.
      No 9-digit solution was found in previous programs.

      from enigma import is_square 
      D, F = 0, 6  # values fixed by teaser conditions
      for A in range(1, 10):
        for B in range(1, 10):
          if B == A:continue
          if B % A != 0:continue
          for C in range(1, 10):
            if C in (A, B):continue
            if C % A != 0:continue
            for  E in range(1, 10):
              if E in (A,B,C,F):continue
              if E % 2 != 1 or E % A != 0: continue
              num = A * 10**5 + B * 10**4 + C * 10**3 + E*10 + F
              if num // 100 % 10 != D:continue
              if not is_square(num):continue
              print(f"Martha dialled {num}.")
      

      Like

  • Jim Randell 8:40 am on 10 November 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2611: Simple arithmetic 

    From The Sunday Times, 7th October 2012 [link]

    George and Martha are teaching their great-grandchildren some simple arithmetic. “If you add two thirties to four tens you get a hundred”, George was saying, and he wrote it like this:

    “Strangely”, added Martha, there are nine different letters there, and if you allow each letter to stand for a different digit, there is a unique sum which works”.

    Which digit would be missing?

    [teaser2611]

     
    • Jim Randell 8:40 am on 10 November 2022 Permalink | Reply

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

      The following run file executes in 58ms. (Internal runtime is 4.74ms).

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, irange, diff, join, printf)
      
      # construct the alphametic sum
      p = SubstitutedExpression.split_sum(
        ["THIRTY"] * 2 + ["TEN"] * 4, "HUNDRED",
        template="(2 * {THIRTY} + 4 * {TEN} = {HUNDRED})",
      )
      
      # solve the puzzle, and output unused digit(s)
      for s in p.solve():
        ds = diff(irange(0, 9), s.values())
        # output solution
        printf("-> unused digits = {ds}", ds=join(ds, sep=", "))
      

      Solution: The missing digit is 7.

      Like

    • GeoffR 11:40 am on 10 November 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      %          T H I R T Y
      %          T H I R T Y
      %                T E N
      %                T E N
      %                T E N
      %                T E N
      %        -------------
      %        H U N D R E D
      %        -------------
      
      set of int: INT = 0..9;
      var INT: T; var INT: H; var INT: I;
      var INT: R; var INT: Y; var INT: E;
      var INT: N; var INT: U; var INT: D;
      
      constraint T > 0 /\ H > 0;
      constraint all_different( [T,H,I,R,Y,E,N,U,D]);
      
      % Carry digits from RH end
      var 0..6:c1; var 0..6:c2; var 0..6:c3;
      var 0..1:c4; var 0..1:c5; var 0..1:c6;
      
      % Adding digits in columns, with carry digits from RH end of sum
      constraint D == (4*N + 2*Y) mod 10 /\ c1 == (4*N + 2*Y) div 10;
      constraint E == (c1 + 4*E + 2*T) mod 10 /\ c2 == (c1 + 4*E + 2*T) div 10;
      constraint R == (c2 + 4*T + 2*R) mod 10 /\ c3 == (c2 + 4*T + 2*R) div 10;
      constraint D == (c3 + 2*I) mod 10 /\ c4 == (c3 + 2*I) div 10;
      constraint N == (c4 + 2*H) mod 10 /\ c5 == (c4 + 2*H) div 10;
      constraint U == (c5 + 2*T) mod 10 /\ c6 == (c5 + 2*T) div 10;
      constraint H == c6;
      
      solve satisfy;
      
      output ["[T,H,I,R,Y,E,N,U,D] = " ++ show([T,H,I,R,Y,E,N,U,D]) ];
      % [T, H, I, R, Y, E, N, U, D] = 
      % [9, 1, 5, 2, 6, 0, 3, 8, 4]
      % ----------
      % ==========
      % By inspection, missing digit = 7.
      % TEN = 903, THIRTY = 915296, HUNDRED = 1834204.
      
      

      Like

    • GeoffR 10:25 pm on 10 November 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      %          T H I R T Y
      %          T H I R T Y
      %                T E N
      %                T E N
      %                T E N
      %                T E N
      %        -------------
      %        H U N D R E D
      %        -------------
       
      set of int: INT = 0..9;
      var INT: T; var INT: H; var INT: I;
      var INT: R; var INT: Y; var INT: E;
      var INT: N; var INT: U; var INT: D;
       
      constraint T > 0 /\ H > 0;
      constraint all_different( [T,H,I,R,Y,E,N,U,D]);
       
      var 100..999:TEN = 100*T + 10*E + N;
      var 100000..999999:THIRTY = 100000*T + 10000*H + 1000*I + 100*R + 10*T + Y;
      var 1000000..9999999:HUNDRED = 1000000*H + 100000*U + 10000*N + 1000*D + 100*R + 10*E + D;
      
      constraint 2 * THIRTY + 4 * TEN == HUNDRED;
      
      solve satisfy;
      
      output [ "T,H,I,R,Y,E,N,U,D] = " ++ show( [T,H,I,R,Y,E,N,U,D] ) ];
      
      % [T, H, I, R, Y, E, N ,U, D] = 
      % [9, 1, 5, 2, 6, 0, 3, 8, 4]
      % time elapsed: 0.17 s
      % ----------
      % ==========
      % By inspection, the missing digit is 7.
      
      
      

      Like

  • Jim Randell 10:18 am on 20 October 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2746: Five finger exercise 

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

    George and Martha have a five-figure code for their burglar alarm. George commented that the three-figure number formed by the first three digits of the code equalled the sum of the cubes of those first three digits.

    Martha added that the three-figure number formed by the last three digits of the code equalled the sum of the factorials of those three digits. (She had to remind George what a factorial was — he remembered once she had pointed out that the factorial of 4 was 4×3×2×1 = 24).

    What is their code?

    This puzzle was not included in the book The Sunday Times Brain Teasers Book 1 (2019).

    This completes the archive of Teaser puzzles originally published in 2015. There is a complete archive of puzzles from 2015 – present available on S2T2, as wall as many older puzzles going back to 1949.

    [teaser2746]

     
    • Jim Randell 10:19 am on 20 October 2022 Permalink | Reply

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

      The following run file executes in 63ms. (The internal runtime of the generated program is 424µs).

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      SubstitutedExpression
      
      --distinct=""
      --answer="ABCDE"
      
      "cb(A) + cb(B) + cb(C) = ABC"
      "factorial(C) + factorial(D) + factorial(E) = CDE"
      

      Solution: The code is: 37145.

      And we have:

      3³ + 7³ + 1³ = 371
      1! + 4! + 5! = 145

      Although not a condition of the puzzle, it turns out that the digits in the code are all different.

      Like

    • GeoffR 10:47 am on 20 October 2022 Permalink | Reply

      from itertools import permutations
      from enigma import factorial as fact
      
      for p1 in permutations(range(1, 10), 5):
          A,B,C,D,E = p1
          if A**3 + B**3 + C**3 == 100*A + 10*B + C:
              if fact(C) + fact(D) + fact(E) == 100*C + 10*D + E:
                  code = 10000*A + 1000*B + 100*C + 10*D + E
                  print(f"Code is {code}.")
                             
      # Code is 37145.
      
      
      

      Like

  • Jim Randell 8:04 am on 13 October 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2737: Pinpoint accuracy 

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

    George and Martha’s bank PIN is an odd four-figure number. George did some calculations and wrote down three numbers: the first was the sum of the four digits in the PIN; the second was the average of the four digits; and the third was the product of the four digits.

    Then Martha multiplied these three numbers together and was surprised that the answer equalled the original PIN.

    What is their PIN?

    This puzzle was not included in the book The Sunday Times Brain Teasers Book 1 (2019).

    [teaser2737]

     
    • Jim Randell 8:05 am on 13 October 2022 Permalink | Reply

      None of the digits can be 0, as then the product of the digits would also be 0.

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

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

      Run: [ @replit ]

      #! python3 -m enigma -r
      
      SubstitutedExpression
      
      --distinct=""
      --digits="1-9"
      --answer="ABCD"
      
      # The following multiplied together give the value of the PIN
      # (i) the sum of the digits;
      # (ii) the mean of the digits (= (i) / 4);
      # (iii) the product of the digits
      "sq(A + B + C + D) * (A * B * C * D) == 4 * ABCD"
      
      # the PIN is odd
      "D % 2 = 1"
      

      Solution: The PIN is: 1715.

      Like

    • GeoffR 12:16 pm on 13 October 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D;
      constraint D mod 2 == 1; % PIN number is odd
      
      var 1000..9999:PIN = 1000*A + 100*B + 10*C + D;
      
      % Num 1the sum of the four digits in the PIN
      var 4..36:Num1 = A + B + C + D;
      
      var 4..36: Num2; %  average = Num2 / 4
      constraint Num2 == A + B + C + D;
      
      % Num3 was the product of the four digits
      var 1..6561: Num3 == A * B * C * D ; % UB = 9^4
      
      % Martha multiplied these three numbers together
      constraint 4 * PIN == Num1 * Num2 * Num3;
      
      solve satisfy;
      
      output[ "PIN = " ++ show(PIN) ];
      
      % PIN = 1715
      % ----------
      % ==========
      
      
      

      Like

  • Jim Randell 10:12 am on 9 June 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2754: Family history 

    From The Sunday Times, 5th July 2015 [link] [link]

    Three of George’s relatives who were born in different years of the 20th century shared the same birthday. Writing these in the form D/M/Y with just two digits for the year, he tells Martha that: in one case D divided by M, when expressed as a percentage, is Y; in another M is the average of D and Y; in the remaining one D raised to the power M equals Y. George then told Martha that knowing the day, D, would not enable her to work out the three dates, but knowing any one of the three years would enable her to work out all three dates.

    What is the most recent of the three birth dates?

    [teaser2754]

     
    • Jim Randell 10:13 am on 9 June 2022 Permalink | Reply

      The years are all in the 20th Century (i.e. 1901 – 2000).

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

      Run: [ @replit ]

      from enigma import (
        irange, product, div, seq_all_different as all_different,
        filter_unique, unpack, intersect, printf
      )
      
      # generate possible dates
      def generate():
        # choose values for D and M
        for (D, M) in product(irange(1, 31), irange(1, 12)):
          if D > 28 and M == 2: continue
          if D > 30 and M in {4, 6, 9, 11}: continue
      
          # calculate the 3 (truncated) years
          Ys = (div(100 * D, M), 2 * M - D, D ** M)
          if any(y is None or y < 0 or y > 99 for y in Ys): continue
          if not all_different(Ys): continue
          yield (D, M, Ys)
      
      # candidate solutions
      ss = generate()
      
      # knowing D does not let you work out the solution
      ss = filter_unique(ss, unpack(lambda D, M, Ys: D)).non_unique
      
      # but knowing _any_ of the years would let you work it out
      fn = lambda k: unpack(lambda D, M, Ys: Ys[k])
      ss = intersect(filter_unique(ss, fn(k)).unique for k in (0, 1, 2))
      
      # output solution
      for (D, M, Ys) in ss:
        (Y1, Y2, Y3) = Ys
        printf("D={D} M={M} -> Y1={Y1:02d} Y2={Y2:02d} Y3={Y3:02d}")
        Y = max((2000 if y == 0 else y + 1900) for y in Ys)
        printf("-> most recent (D/M/Y) = {D}/{M}/{Y}")
      

      Solution: The most recent of the birth dates is: 2/5/40 (i.e. 2nd May 1940).

      All birthdays are 2nd May, and the years are: 1908, 1932, 1940.

      So we have:

      2/5 = 40% → 1940
      5 is the mean of 2 and 8 → 1908
      2^5 = 32 → 1932

      At the time of publication the most recent birthdays would be: 107th, 83rd, 75th.


      There are 7 possible D/M pairs:

      D=1 M=2 → Ys=(50, 3, 1)
      D=1 M=4 → Ys=(25, 7, 1)
      D=1 M=5 → Ys=(20, 9, 1)
      D=1 M=10 → Ys=(10, 19, 1)
      D=2 M=4 → Ys=(50, 6, 16)
      D=2 M=5 → Ys=(40, 8, 32)
      D=3 M=4 → Ys=(75, 5, 81)

      The last of these is excluded as D is unique.

      The D=2 M=5 case is the only one which can be uniquely determined given any of the Y values.

      Like

  • Jim Randell 4:50 pm on 27 May 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3114: Colourful characters 

    From The Sunday Times, 29th May 2022 [link] [link]

    George and Martha have recently taken a great-grandchild to a toddler’s birthday party. The youngsters like to traipse around over a pen with a large number of brightly coloured plastic balls. Actually there were 200 in total, some of red, yellow, blue and green. There were at least 30 but fewer than 70 of each colour, with the following properties:

    Red = perfect square
    Yellow = prime number
    Blue = palindromic number
    Green = divisible by three [different] single-digit prime numbers

    George told Martha the above information and the number of red balls. Martha was then able to work out the numbers of each of the others.

    How many of each colour were there?

    [teaser3114]

     
    • Jim Randell 5:18 pm on 27 May 2022 Permalink | Reply

      i.e. the number of red balls is a perfect square; the number of yellow balls is prime; etc.

      I required the number of green balls to be “divisible by exactly three different single-digit prime numbers”.

      The following Python program runs in 54ms. (Internal run time is 310µs).

      Run: [ @replit ]

      from enigma import (
        irange, is_prime, is_square, is_npalindrome, product,
        filter_unique, unpack, printf
      )
      
      # select candidate quantities [30, 69]
      select = lambda fn: list(x for x in irange(30, 69) if fn(x))
      
      # single digit primes
      sdps = list(x for x in irange(0, 9) if is_prime(x))
      
      # find the candidate quantities for each colour
      Rs = select(is_square)
      Ys = select(is_prime)
      Bs = select(is_npalindrome)
      Gs = select(lambda x: sum(x % d == 0 for d in sdps) == 3)
      
      # generate possible (R, Y, B, G) combinations
      def generate():
        for qs in product(Rs, Ys, Bs, Gs):
          if sum(qs) == 200:
            yield qs
      
      # find combinations unique by the number of reds
      reds = unpack(lambda R, Y, B, G: R)
      for (R, Y, B, G) in filter_unique(generate(), reds).unique:
        printf("R={R} Y={Y} B={B} G={G}")
      

      Solution: The numbers of balls are: Red = 36, Yellow = 67, Blue = 55, Green = 42.


      Manually:

      There are only a few options for each colour:

      Red = (36, 49, 64)
      Yellow = (31, 37, 41, 43, 47, 53, 59, 61, 67)
      Blue = (33, 44, 55, 66)
      Green = (30, 42, 60)

      And we want combinations of these that sum to 200. Note that Yellow (prime) is always odd, so the sum of the other three must also be odd – Green is always even, so Red and Blue must be one odd and one even.

      We quickly find there are 5 candidate solutions:

      R=36 B=55 G=42 Y=67
      R=49 B=44 G=60 Y=47
      R=49 B=66 G=42 Y=43
      R=64 B=33 G=42 Y=61
      R=64 B=33 G=60 Y=43

      And only R=36 give a unique set of values.

      Like

      • Frits 10:37 am on 28 May 2022 Permalink | Reply

        @Jim, I would have probably chosen a different alias name for the itertools cartesian product as using the name product in combination with numbers is ambiguous (to me).

        Like

        • Jim Randell 11:06 am on 28 May 2022 Permalink | Reply

          @Frits: A long time ago there used to have a product() function in enigma.py to calculate the product of a sequence of numbers.

          But I renamed it multiply() to avoid confusion with itertools.product() (which is a useful function in solving puzzles).

          Python 3.8 added a similar function to multiply() as math.prod().

          Like

        • Jim Randell 11:50 am on 10 July 2022 Permalink | Reply

          @Frits: I’ve added the [[ cproduct() ]] function to the enigma.py library. Which is an unpacked version of the [[ itertools.product() ]] function.

          It makes it neater to make a Cartesian product from a generator (which is quite a common occurrence in puzzles):

          # using itertools.product
          product(*(<generator>))
          # using cproduct
          cproduct(<generator>) 
          

          You can also use it for a fixed list of sets like this:

          # using itertools.product
          product(As, Bs, Cs)
          # using cproduct
          cproduct([As, Bs, Cs])
          

          Like

    • GeoffR 6:29 pm on 27 May 2022 Permalink | Reply

      Multiple output configuration produces five potential solutions, but only one of these solutions has a unique number of red balls, which gives the answer.

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      % Red, Yellow, Blue and Green coloured ball numbers
      var 30..69:R; var 30..69:Y; var 30..69:B; var 30..69:G;
      
      constraint R + Y + B + G == 200;
      
      predicate is_sq(var int: y) =
      exists(z in 1..ceil(sqrt(int2float(ub(y))))) (z*z = y);
      
      % R is a square number
      constraint is_sq(R);
      
      predicate is_prime(var int: x) = 
      x > 1 /\ forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) 
      ((i < x) -> (x mod i > 0));
      
      % Y is a prime number
      constraint is_prime(Y);
      
      % B is a 2-digit palindromic number
      constraint B div 10 == B mod 10;
      
      % G has 3 single digit prime divisors i.e three from 2, 3, 5, 7
      constraint sum( [bool2int(G mod 2 == 0), bool2int(G mod 3 == 0), 
      bool2int(G mod 5 == 0), bool2int(G mod 7 == 0)] ) == 3;
      
      solve satisfy;
      
      output [ "[R,Y,B,G ]= " ++ show( [R,Y,B,G ]) ];
      
      
      

      Like

    • Frits 6:52 pm on 27 May 2022 Permalink | Reply

         
      from enigma import SubstitutedExpression
      
      # return entry where column <col> is unique
      def unique_col(seq, col=0):
        d = dict()
        for s in seq:
             d[s[col]] = s[col] in d
        
        return [s for s in seq if not d[s[col]]]
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          # Red = perfect square
          "is_square(RD)",
          # Yellow = prime number
          "is_prime(YW)",
          # Blue = palindromic number
          "BE in {33, 44, 55, 66}",
          
          # there were 200 in total
          "200 - RD - YW - BE = GN",
          # Green = divisible by three single-digit prime numbers
          "sum(GN % p == 0 for p in [2, 3, 5, 7]) == 3",
         
        ],
        answer="RD, YW, BE, GN",
        d2i=dict([(k, "RYBG") for k in range(3)] + 
                 [(k, "RYBG") for k in range(7, 10)]),
        distinct="",
        verbose=0,
      )
      
      colors = ["Red", "Yellow", "Blue", "Green"]
      # get unique answers for number of red balls
      uniq = unique_col([y for _, y in p.solve()])
      print(*list(zip(colors, uniq[0])))
      

      Like

    • GeoffR 9:01 am on 28 May 2022 Permalink | Reply

      Separate research found three possible values for Green between 30 and 69:

      G = 30, prime factors = 2 * 3 * 5
      G = 42, prime factors = 2 * 3 * 7
      G = 60, prime factors = 2 * 2 * 3 * 5

      
      from collections import defaultdict
      COL = defaultdict(list)
      
      # Using R = Red, Y = Yellow, B = Blue, G = Green
      for R in (36, 49, 64):  # Squares from 30..69
        # Primes between 30 and 69
        for Y in (31, 37, 41, 43, 47, 53, 59, 61, 67):
          # Palindromes between 30 and 69
          for B in (33, 44, 55, 66):
            for G in (30, 42, 60):
              if R + Y + B + G != 200:
                continue
              COL[R] += [(R, Y, B, G)]
      
      for k, v in COL.items():
        if len(v) == 1:
          print(f"R={v[0][0]}, Y={v[0][1]}, B={v[0][2]}, G={v[0][3]}")
      
      

      Like

    • Frits 10:12 am on 28 May 2022 Permalink | Reply

      No imports, using methods from Brian and Jim.

         
      # pick one value from each entry of a <k>-dimensional list <ns>
      # so that all elements sum up to <t>
      def pickOneFromEach(k, ns, t, s=[]):
        if k == 1:
           # does the remainder occur in the first list?
           if t in ns[0]:
             yield s + [t]
        else:
          for n in ns[k-1]:
            yield from pickOneFromEach(k - 1, ns, t - n, s + [n])
      
      sqs = [x * x for x in range(6, 9)]
      # use set() as it will be only used for lookups
      prms = set(x for x in range(31, 70, 2) if all(x % i for i in [2, 3, 5, 7]))
      pals = [11 * x for x in range(3, 7)]
      divs = [x for x in range(30, 70) if sum(x % i == 0 for i in [2, 3, 5, 7]) == 3]
      
      # place the list with the most elements up front and squares at the end
      mlst = [prms, divs, pals, sqs]
        
      seen_once = dict()
      # get unique answers for number of red balls
      for x in pickOneFromEach(4, mlst, 200):  
        seen_once[x[0]] = x if x[0] not in seen_once else []
      
      colors = ["Red", "Blue", "Green", "Yellow"]
      for k, vs in seen_once.items():
        if vs:
          print(*list(zip(colors, vs)))
      

      Like

  • Jim Randell 8:46 am on 21 April 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2857: Significant errors 

    From The Sunday Times, 25th June 2017 [link]

    Last year George and Martha were going to visit one of their daughters. Her house number was a two-figure number but unfortunately Martha wrote the number incorrectly by making the first digit less than it should be. When George discovered the error he commented that the incorrect number was a whole number percentage of the correct one. If you knew that percentage then you should be able to work out their daughter’s house number.

    Then the daughter moved to a different two-figure house number, Martha again wrote the number incorrectly by making the first digit less than it should be, and again the incorrect number was a whole number percentage of the correct one. If you knew that percentage then you should be able to work out their daughter’s new house number.

    What was the daughter’s original house number, and what is the new one?

    [teaser2857]

     
    • Jim Randell 8:46 am on 21 April 2022 Permalink | Reply

      This Python program collects possible (house-number, wrong-number, percentage-reduction) values, and then uses the [[ filter_unique() ]] function from the enigma.py library to find the required values for the first and second house numbers.

      It runs in 55ms. (Internal runtime is 333µs).

      Run: [ @replit ]

      from enigma import irange, div, filter_unique, unpack, printf
      
      # record (n, w, p) results
      rs = list()
      
      # consider two digit house numbers
      for n in irange(20, 99):
        # consider different numbers with the first digit less than the
        # correct digit, but the second digit is correct
        for w in irange(10 + (n % 10), n - 1, step=10):
          # calculate the percentage w / n
          p = div(100 * w, n)
          if p is None: continue
          rs.append((n, w, p))
      
      # if you knew the percentage you would know the correct house number
      fn_n = unpack(lambda n, w, p: n)
      fn_p = unpack(lambda n, w, p: p)
      for (n1, w1, p1) in filter_unique(rs, fn_p, fn_n).unique:
        printf("p1={p1}% -> n1={n1} w1={w1}")
      
        # look for results with a different house number
        rs1 = filter(unpack(lambda n2, w2, p2: n2 != n1), rs)
        for (n2, w2, p2) in filter_unique(rs1, fn_p, fn_n).unique:
          printf("  p2={p2}% -> n2={n2} w2={w2}")
        printf()
      

      Solution: The first house number was 50. The second house number was 75.

      There are two possible scenarios:

      Daughter’s first house was 50, but she wrote 20 (= 40% of 50).
      Daughter’s second house was 75, but she wrote 15 (= 20% of 75).

      Daughter’s first house was 50, but she wrote 40 (= 80% of 50).
      Daughter’s second house was 75, but she wrote 15 (= 20% of 75).

      But in both cases the first house number is 50 and the second house number is 75.


      Here are the 15 possible (house number, wrong number) pairs, grouped by percentage:

      20%: (50 → 10) (75 → 15)
      25%: (40 → 10) (80 → 20)
      40%: (50 → 20)
      50%: (20 → 10) (40 → 20) (60 → 30) (80 → 40)
      60%: (25 → 15) (50 → 30) (75 → 45)
      75%: (40 → 30) (80 → 60)
      80%: (50 → 40)

      From which we see the only values with a unique percentage are:

      40%: (50 → 20)
      80%: (50 → 40)

      Both cases give a first house number of 50.

      If we remove pairs with a first house number of 50 from those listed above we get:

      20%: (75 → 15)
      25%: (40 → 10) (80 → 20)
      50%: (20 → 10) (40 → 20) (60 → 30) (80 → 40)
      60%: (25 → 15) (75 → 45)
      75%: (40 → 30) (80 → 60)

      And we see there is only one value with a unique percentage:

      20%: (75 → 15)

      And so the second house number is 75.

      Like

  • Jim Randell 9:31 am on 27 January 2022 Permalink | Reply
    Tags: by: Danny Roth,   

    Teaser 2879: Blood line 

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

    Patients numbered 1 to 99 were waiting for a blood test. George’s and Martha’s numbers were consecutive. Patients were called in [a] random order to one of the available cubicles numbered 1 to 9. The nurse in each cubicle took a fixed whole number of minutes to do the test, but that time varied from cubicle to cubicle. All cubicles started at 10am and they all finished together before 5pm on the same day. Amazingly, it turned out that each patient’s cubicle number was the highest digit that divided into their patient number. George noted that, when he was called, the session had been running for a number of minutes whose digits [when] added to[gether gave] his cubicle number. Martha noted that the same was true for her.

    What were their patient numbers?

    Unfortunately this puzzle has multiple solutions (apparently introduced by the editing process for puzzles at the paper).

    This puzzle was not included in the published collection of puzzles The Sunday Times Brainteasers Book 1 (2019).

    [teaser2879]

     
    • Jim Randell 9:31 am on 27 January 2022 Permalink | Reply

      The following program finds 5 possible pairs of patient numbers that satisfy the conditions.

      We know which cubicle each patient is to visit, so we can form them into queues for that cubicle.

      The session lasts for less than 7 hours = 420 minutes, and the cubicles finish at the same time. So the total length of the session must be a multiple of the length of each queue. (And it turns out there is only one possible duration).

      We can now calculate the time taken for each cubicle, and look for cubicles that have elapsed call times with a digit sum that is the same as the cubicle number.

      Then we can look for consecutive patient numbers that are called to these cubicles, and these give us possible patient numbers for Martha and George.

      The following Python program runs in 50ms.

      Run: [ @replit ]

      from enigma import irange, peek, group, mlcm, dsum, tuples, union, printf
      
      # map patients to queue number
      ns = irange(1, 99)
      queue = lambda n: peek(d for d in irange(9, 1, step=-1) if n % d == 0)
      n2q = dict((n, queue(n)) for n in ns)
      
      # form the queues
      qs = group(ns, by=(lambda n: n2q[n]))
      
      # consider the total length of the session < 7h = 420m
      m = mlcm(*(len(v) for v in qs.values()))
      for t in irange(m, 419, step=m):
        printf("duration = {t} mins")
      
        # find cubicles with elapsed call times that sum to their digit
        ks = set()
        for (k, vs) in qs.items():
          x = t // len(vs)
          for s in irange(x, t - x, step=x):
            if dsum(s) == k:
              printf("queue {k}; {x} mins/test; call at {s} mins")
              ks.add(k)
      
        # find consecutive numbers for patients in queues in ks
        for (a, b) in tuples(sorted(union(qs[k] for k in ks)), 2):
          if b == a + 1:
            printf("({a}, {b}) -> ({qa}, {qb})", qa=n2q[a], qb=n2q[b])
      

      Solution: The are 5 possible pairs of patient numbers for Martha and George: (26, 27), (44, 45), (45, 46), (62, 63), (81, 82).


      After constructing the queues for each of the digits 1-9, we see that the only possible duration of the session is 264 minutes, and there are 6 possible times when a patient could be called such that the digit sum of the elapsed number of minutes is the same as the cubicle number.

      called at 72 minutes to cubicle 9
      called at 110 minutes to cubicle 2
      called at 132 minutes to cubicle 6
      called at 144 minutes to cubicle 9
      called at 216 minutes to cubicle 9
      called at 220 minutes to cubicle 4

      This means that we can have the following consecutive numbers for George and Martha:

      26 & 27 → called to cubicle 2 (after 110 minutes) and cubicle 9 (after 72, 144, or 216 minutes)
      44 & 45 → called to cubicle 4 (after 220 minutes) and cubicle 9 (after 72, 144, or 216 minutes)
      45 & 46 → called to cubicle 9 (after 72, 144, or 216 minutes) and cubicle 2 (after 110 minutes)
      62 & 63 → called to cubicle 2 (after 110 minutes) and cubicle 9 (after 72, 144, or 216 minutes)
      81 & 82 → called to cubicle 9 (after 72, 144, or 216 minutes) and cubicle 2 (after 110 minutes)

      The additional constraint that “George and Martha were called less than half an hour apart” would narrow these down to a single solution:

      44 & 45 → called to cubicle 4 (after 220 minutes) and cubicle 9 (after 216 minutes)

      Like

  • Jim Randell 4:40 pm on 7 January 2022 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3094: There’s always next year 

    From The Sunday Times, 9th January 2022 [link] [link]

    George and Martha annually examine around 500 candidates (give or take 5%). It is board policy to pass about a third of the entrants but the precise recent percentage pass rates were as above.

    The number of entrants in each year up to 2021 was different as was the number of successful candidates. George told Martha of the number of entries for 2022 (different again) and Martha calculated that, if X were to be once again a whole number (also different again but within the above range), the total number of successful candidates over the five-year period would be a perfect square.

    How many entries are there for 2022 and how many successful candidates did Martha calculate for 2022?

    [teaser3094]

     
    • Jim Randell 5:08 pm on 7 January 2022 Permalink | Reply

      I considered total candidates for each year in the range [475, 525], and by allowing the (integer) percentages to be in the range [30, 36] I was able to find a unique solution.

      This Python program runs in 53ms.

      Run: [ @replit ]

      from enigma import (
        divc, divf, div, irange, cproduct, unzip,
        seq_all_different, is_square, printf
      )
      
      # given pass rates (percentages)
      rates = [30, 32, 35, 36]
      
      # find viable "<n> of <t>" pairs for percentages 30 - 36
      pairs = dict()
      for x in irange(30, 36):
        pairs[x] = list()
        for n in irange(divc(475 * x, 100), divf(525 * x, 100)):
          t = div(100 * n, x)
          if t is not None:
            pairs[x].append((n, t))
      
      # consider possible (n, t) pairs for the given years
      for nts in cproduct(pairs[x] for x in rates):
        (ns, ts) = unzip(nts)
        if not (seq_all_different(ns) and seq_all_different(ts)): continue
        nsum = sum(ns)
      
        # consider possible (n, t) pairs for 2022
        for (k, vs) in pairs.items():
          if k in rates: continue
          for (n, t) in vs:
            if n in ns or t in ts: continue
            # is the total number of n's a square?
            if is_square(n + nsum):
              printf("k={k}% -> {n} of {t} [ns={ns} ts={ts}]")
      

      Solution: There were 500 entries for 2022. Martha’s calculation was for 165 successful candidates.

      Like

    • GeoffR 8:29 pm on 7 January 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      predicate is_sq(var int: y) =
      exists(z in 1..ceil(sqrt(int2float(ub(y))))) (z*z = y );
      
      % Total entrants for each year & given pass rates
      var 475..525:Y2018;  %Pass % = 30
      var 475..525:Y2019;  %Pass % = 32
      var 475..525:Y2020;  %Pass % = 35
      var 475..525:Y2021;  %Pass % = 36
      
      var 475..525:Y2022;  
      var 30..36:P5;  % Per Cent pass rate for Y2022
      
      % total entrants, successful candidates, pass rates are all different
      constraint all_different( [Y2018, Y2019, Y2020, Y2021, Y2022]);
      constraint all_different ([SC2018, SC2019, SC2020, SC2021, SC2022]);
      constraint all_different([30, 32, 35, 36, P5]);
      
      % Total passes for each year
      % LB = 0.3 * 475 = 142, UB = 0.36 * 525 = 189
      var 142..189:SC2018; var 142..189:SC2019; var 142..189:SC2020;
      var 142..189:SC2021; var 142..189:SC2022;
      
      % calculate successful candidates for each year
      constraint 100 * SC2018 == 30 * Y2018;
      constraint 100 * SC2019 == 32 * Y2019;
      constraint 100 * SC2020 == 35 * Y2020;
      constraint 100 * SC2021 == 36 * Y2021;
      constraint 100 * SC2022 == P5 * Y2022;
      
      % total successful candidates for years (2018 - 2022) is a square
      constraint is_sq(SC2018 + SC2019 + SC2020 + SC2021 + SC2022);
      
      solve satisfy;
      
      output [ "Successful candidates for 2022 = " ++ show(SC2022) ++ "\n"
      ++ "Total entrants for 2022 = " ++ show(Y2022) 
      ++ "\n"  ++ "2022 Pass % = " ++ show(P5)];
      
      
      

      Like

  • Jim Randell 10:11 am on 12 October 2021 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2830: Time for Christmas 

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

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

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

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

    [teaser2830]

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

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

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

      Run: [ @replit ]

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

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

      The times are:

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

      Like

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

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

      Like

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

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

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

        Like

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

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

      Like

  • Jim Randell 5:23 pm on 6 August 2021 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3072: Dial M for Marriage 

    From The Sunday Times, 8th August 2021 [link]

    George and Martha work in a town where phone numbers have seven digits. “That’s rare!”, commented George. “If you look at your work number and mine, both exhibit only four digits of the possible ten (0-9 inclusive), each appearing at least once. Furthermore, the number of possible phone numbers with that property has just four single-digit prime number factors (each raised to a power where necessary) and those four numbers are the ones in our phone numbers”.

    “And that is not all!”, added Martha. “If you add up the digits in the two phone numbers you get a perfect number in both cases. Both have their highest digits first, working their way down to the lowest”.

    [A perfect number equals the sum of its factors, e.g., 6 = 1 + 2 + 3].

    What are two phone numbers?

    [teaser3072]

     
    • Jim Randell 5:43 pm on 6 August 2021 Permalink | Reply

      This puzzle sounds more complicated than it is.

      There are only 4 single digit primes (2, 3, 5, 7), so these must be digits of the phone numbers. And they must occur in the order 7*5*3*2*, where the * is zero or more repeats of the previous digit, to give 7 digits in total.

      This Python program considers the possible phone numbers composed of these digits, and finds those with a digit sum that is a perfect number.

      It runs in 51ms.

      Run: [ @replit ]

      from enigma import (irange, divisors, cache, join, printf)
      
      # decompose <t> into <k> positive integers
      def decompose(t, k, ns=[]):
        if k == 1:
          yield ns + [t]
        else:
          k_ = k - 1
          for n in irange(1, t - k_):
            yield from decompose(t - n, k_, ns + [n])
      
      # is a number perfect?
      @cache
      def is_perfect(n):
        return sum(divisors(n)) == 2 * n
      
      # find possible distributions of digits
      for (m7, m5, m3, m2) in decompose(7, 4):
        # the digits ...
        ds = [7] * m7 + [5] * m5 + [3] * m3 + [2] * m2
        # ... and their sum ...
        t = sum(ds)
        # ... is it perfect?
        if is_perfect(t):
          printf("{ds} -> {t}", ds=join(ds))
      

      Solution: The phone numbers are 7553332 and 7753222.

      Both numbers have a digit sum of 28, which is a perfect number (28 = 1 + 2 + 4 + 7 + 14).


      For completeness:

      There are 1764000 possible 7-digit phone numbers that consist of exactly 4 different digits. And we can factorise this as:

      1764000 = (2^5)(3^2)(5^3)(7^2)

      So it does indeed have a prime factorisation that consists of (positive) powers of the 4 single-digit prime numbers.

      But this is not needed to solve the puzzle.

      This number can be determined by calculation, or just counting (it takes a few seconds in PyPy):

      >>> icount(n for n in irange(0, 9999999) if len(set(nsplit(n, 7))) == 4)
      1764000
      

      But if you can’t wait that long (or you want to look at larger values) they can be calculated using a recurrence relation:

      from enigma import (cache, arg, printf)
      
      # how many n-digit phone numbers consisting of k different digits?
      @cache
      def N(n, k):
        if k == 0 or n < k: return 0
        if k == 1: return 10
        return k * N(n - 1, k) + (11 - k) * N(n - 1, k - 1)
      
      n = arg(7, 0, int)
      k = arg(4, 1, int)
      printf("N({n}, {k}) = {r}", r=N(n, k))
      

      I use this function in a “complete” solution, that calculates N(7, 4), and the single digit prime factors of it (and it ensures there are 4 of them), it then finds distributions of these prime digits that give a phone number with a perfect digit sum.

      from enigma import (decompose, divisors, prime_factor, flatten, join, cached, printf)
      
      # how many n-digit phone numbers consisting of k different digits?
      @cached
      def _N(n, k, b):
        if k == 0 or n < k: return 0
        if k == 1: return b
        return k * _N(n - 1, k, b) + (b + 1 - k) * _N(n - 1, k - 1, b)
      
      N = lambda n, k, base=10: _N(n, k, base)
      
      # is a number perfect?
      @cache
      def is_perfect(n):
        return sum(divisors(n)) == 2 * n
      
      # find number 7-digit phone numbers with 4 different digits
      n = N(7, 4)
      
      # determine the prime factorisation
      fs = list(prime_factor(n))
      printf("N(7, 4) = {n} -> {fs}")
      
      # look for single digit primes
      ps = list(p for (p, e) in fs if p < 10)
      printf("primes = {ps}")
      assert len(ps) == 4
      
      # find possible distribution of the prime digits
      for ns in decompose(7, 4, increasing=0, sep=0, min_v=1):
        # the digits of the phone number
        ds = flatten([d] * n for (d, n) in zip(ps, ns))
        # and their sum
        t = sum(ds)
        # is it perfect?
        if is_perfect(t):
          ds.reverse()
          printf("number = {ds} -> dsum = {t}", ds=join(ds))
      

      Like

      • Jim Randell 9:27 am on 7 August 2021 Permalink | Reply

        Here’s an alternative (shorter) program. It starts by looking for possible values of the sum (there is only one candidate that is a perfect number), and then uses the [[ express() ]] function from the enigma.py library to determine possible combinations of 7 digits to achieve the sum. It runs in 49ms.

        Run: [ @replit ]

        from enigma import irange, divisors, express, join, printf
        
        # is a number perfect?
        is_perfect = lambda n: sum(divisors(n)) == 2 * n
        
        # the smallest possible sum is (2+3+5+7)+(2+2+2) = 23
        # the largest  possible sum is (2+3+5+7)+(7+7+7) = 38
        for t in irange(23, 38):
          if not is_perfect(t): continue
        
          # make the sum from the amounts
          for ns in express(t, [2, 3, 5, 7], min_q=1):
            if sum(ns) != 7: continue
        
            # construct the phone number
            ds = join(d * n for (d, n) in zip("7532", reversed(ns)))
            printf("{ds} -> {t}")
        

        Liked by 2 people

        • GeoffR 12:37 pm on 7 August 2021 Permalink | Reply

          Perfect numbers are 6, 28, 496, 8128, …
          Only possible perfect number for sum of seven digits is 28
          i.e. for seven digits containing 2, 3, 5 or 7.

          A shorter version using “express” from enigma.py

          
          from enigma import express
          
          # list of potential telephone numbers
          TN = list(express(28, [7, 5, 3, 2], min_q=1))
          
          for t in TN:
            if sum(t) == 7:
              a, b, c, d  = t[0], t[1], t[2], t[3]
              print('7'*a + '5'*b + '3'*c + '2'*d)
          
          
          

          Like

          • Jim Randell 2:22 pm on 7 August 2021 Permalink | Reply

            Or, as a single expression:

            >>> list('7' * d + '5' * c + '3' * b + '2' * a for (a, b, c, d) in express(28, [2, 3, 5, 7], min_q=1) if a + b + c + d == 7)
            ['7553332', '7753222']
            

            Like

          • Frits 9:31 am on 8 August 2021 Permalink | Reply

            Or:

             
            >>> list('7' * (d + 1) + '5' * (c + 1) + '3' * (b + 1) + '2' * (a + 1) for (a, b, c, d) in express(11, [2, 3, 5, 7]) if a + b + c + d == 3)
            

            Like

    • GeoffR 8:16 pm on 6 August 2021 Permalink | Reply

      
      from enigma import divisors, dsum, nsplit
      
      tel_list = []
      
      # Re-using Jim's function
      def is_perfect(n):
        return sum(divisors(n)) == 2 * n
      
      def is_descending(n):
        # 7-digit nos. only
        a, b, c, d, e, f, g = nsplit(n)
        if a >= b and b >= c and c >= d and d >= e \
           and e >= f and f >= g:
            return True
        return False
      
      # find 7-digit Tel. Nos. with specified digit pattern
      for n in range(7532222, 7777533):
          s = set(str(n))
          if s == {'7', '5', '3', '2'}:
            if is_descending(n) and is_perfect(dsum(n)):
              tel_list.append(n)
      
      if len(tel_list) == 2:
          print(f"Two tel. nos. are {tel_list[0]} and {tel_list[1]}.")
      
      
      

      Like

    • GeoffR 4:26 pm on 7 August 2021 Permalink | Reply

      Setting the output to multiple configurations gives the two telephone numbers.

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Both telephone numbers consist of four single digit primes
      set of int: pr = {2, 3, 5, 7};
      
      % 7 digits in telephone numbers
      var pr: A; var pr: B; var pr: C; var pr: D;
      var pr: E; var pr: F; var pr: G; 
      
      % Possible range of telephone numbers
      var 7532222..7777533: TelNum = 1000000*A + 100000*B + 10000*C
       + 1000*D + 100*E + 10*F + G;
      
      % Both telephone numbers include the digits 2, 3, 5 and 7
      constraint {A, B, C, D, E, F, G} == {2, 3, 5, 7};
      
      % 28 is only viable perfect number for the sum of seven digits
      constraint A + B + C + D + E + F + G == 28;
      
      % digits must not be in ascending order
      constraint A >= B /\ B >= C /\ C >= D /\ D >= E
      /\ E >= F /\ F >= G;
      
      solve satisfy;
      
      output ["Tel. No. = " ++ show(TelNum) ];
      
      
      

      Like

    • GeoffR 7:28 pm on 8 August 2021 Permalink | Reply

      Module Module1
        ' A Solution in Visual Basic 2019
      
        Sub Main()
          For a As Integer = 1 To 4
            For b As Integer = 1 To 4
              For c As Integer = 1 To 4
                For d As Integer = 1 To 4
      
                  ' tel. no.is 7 digits long
                  If (a + b + c + d = 7) Then
                    ' tel digits sum to the perfect number 28
                    If (7 * a + 5 * b + 3 * c + 2 * d = 28) Then
                      ' form tel. no.
                      Dim TelNo As String
      
                      TelNo = StrDup(a, CStr(7)) + StrDup(b, CStr(5)) +
                              StrDup(c, CStr(3)) + StrDup(d, CStr(2))
                      Console.WriteLine("Tel. No. is {0}", TelNo)
                      Console.ReadLine()
                    End If
                  End If
      
                Next 'd Loop
              Next 'c Loop
            Next  'b loop
          Next   'a loop
      
        End Sub
      
      End Module
      
      
      

      Like

  • Jim Randell 2:08 pm on 18 June 2021 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3065: Members of the jury 

    From The Sunday Times, 20th June 2021 [link]

    A jury had twelve members, all with different ages (at least 20 but not more than 65), except that two were twins with a common age over 40. The average age was a prime number. A counsel objected to one of the members and he was replaced by another with the result that the average age was reduced to another prime number. Between the two juries, there were twelve different ages and they formed a progression with a common difference (e.g., 1, 4, 7, 10, 13, etc. or 6, 13, 20, 27, 34, etc.,). None of the individuals had a perfect square age, and the replacement jury still included both twins.

    How old were the twins?

    [teaser3065]

     
    • Jim Randell 2:38 pm on 18 June 2021 Permalink | Reply

      This Python program runs in 53ms.

      Run: [ @replit ]

      from enigma import irange, inf, div, is_prime, printf
      
      squares = set(i * i for i in irange(5, 8))  # disallowed square ages
      
      # consider possible start ages
      for a in irange(20, 54):
        # and possible common differences
        for d in irange(1, inf):
          # the progression consisting of 12 ages
          ns = list(irange(a, a + 11 * d, step=d))
          if ns[-1] > 65: break
          # there are no squares
          if squares.intersection(ns): continue
      
          # one of the 12 ages is the replacement age (y)
          t = sum(ns)
          for (i, y) in enumerate(ns):
            # the ages of the original jury
            js = ns[:i] + ns[i + 1:]
            # duplicate one of them
            for z in reversed(js):
              # duplicate age is over 40
              if z < 41: break
              # and gives a prime mean age
              m1 = div(t - y + z, 12)
              if m1 is None or not is_prime(m1): continue
      
              # replace one of the original ages x with y
              for x in js[i:]:
                if x == z: continue
                # mean age of the new jury is a lower prime
                m2 = div(t - x + z, 12)
                if m2 is None or not is_prime(m2): continue
      
                # output solution
                printf("twins = {z} [{js} + {z}; avg={m1}; {x} -> {y}; avg={m2}]", js=tuple(js))
      

      Solution: The twins are 41.

      The sequence of ages for both juries are all those ages between 26 and 59 in steps of 3.

      The ages of the original jury are:

      26, 29, 32, 38, 41, 41, 44, 47, 50, 53, 56, 59; mean = 43

      The 59 year old is then replaced by a 35 year old:

      26, 29, 32, 35, 38, 41, 41, 44, 47, 50, 53, 56; mean = 41


      One way to test your program is to remove the constraint that there are no squares. Without this condition you should find 10 different answers for the age of the twins.

      Liked by 1 person

    • Tony Brooke-Taylor 12:05 pm on 19 June 2021 Permalink | Reply

      Similar routine but with a bit less trial and error, so marginally quicker

      
      primes=list(SieveOfEratosthenes(65))#function left out for ease of reading
      squares=[x**2 for x in range(4,9)]
      
      for delta in range(1,5): #possible common differences
        for a_min in range(20,66-11*delta): #consequential possible start ages
          ages=[a_min+delta*i for i in range(12)] #sequence of 12 ages
          if set(ages).intersection(squares):continue #no square ages
      
          twins=[t for t in ages if t>40]#subset of possible ages of the twins
          a_ave=a_min+11*delta/2#average of the 12 ages
          min_ave = a_ave+(40-max(ages))/12#lowest possible average age of a jury
          max_ave = a_ave+(max(ages)-min(ages))/12#highest possible average age of a jury
          poss_aves = [p for p in primes if p>=min_ave and p<max_ave]
          if len(poss_aves)<2:continue
          #possible prime average ages for a jury, must be (at least) 2 of them
      
          for twin in twins: #find twins' age by trial and error
            rejects=[]
            for poss_ave in poss_aves:
              swapped=twin+(a_ave-poss_ave)*12#age of swapped juror relative to twins
              if swapped>a_min+11*delta:break
              if swapped >=20:rejects.append(swapped)
              #reject impossible juror ages
      
            if len(rejects)>1: #solution contains at least two swapped juror ages
              print("The ages of the twins was",twin)
            #happily there is one solution and it has exactly two swapped jurors
          
      
      
      
      

      Liked by 1 person

      • Jim Randell 10:20 am on 20 June 2021 Permalink | Reply

        The [[ Primes ]] class in the enigma.py library implements a prime sieve, so you can use the following to avoid having to include the code in your program, but still posting runnable code.

        from enigma import Primes
        primes = Primes(66)
        

        And it might be worth placing longer comments on the line above the code, as horizontal space is limited in replies.

        Like

        • Jim Randell 10:57 am on 20 June 2021 Permalink | Reply

          @Tony: It’s an interesting approach.

          Here’s a version using a similar idea, but calculates the separation of the replacement/replaced pair in the progression from the difference between the prime means, and then looks for suitable candidates, from which the repeated age can be determined:

          from enigma import Primes, subsets, irange, inf, sq, div, divc, divf, printf
          
          squares = set(map(sq, irange(5, 8)))  # disallowed square ages
          primes = Primes(60)
          
          # consider possible start ages
          for a in irange(20, 54):
            # and possible common differences
            for d in irange(1, inf):
              # the sequence of 12 ages
              ns = list(irange(a, a + 11 * d, step=d))
              if ns[-1] > 65: break
              # there are no squares
              if squares.intersection(ns): continue
              t = sum(ns)
          
              # consider possible mean ages (m2, m1)
              m_min = divc(t + 41 - ns[-1], 12)
              m_max = divf(t + ns[-1] - ns[0], 12)
              for (m2, m1) in subsets(primes.irange(m_min, m_max), size=2):
                # calculate separation between replaced and replacement
                k = div(12 * (m1 - m2), d)
                if k is None: continue
          
                # consider possible replacement pairs (x = ns[j - k] replaces y = ns[j])
                for j in irange(11, k, step=-1):
                  # calculate repeated age (z)
                  z = 12 * m2 + ns[j] - t
                  if z < 41: break
                  if z in ns and z != ns[j] and z != ns[j - k]:
                    # output solution
                    printf("twins = {z} [{ns}; {x} replaces {y}; avg: {m1} -> {m2}]", ns=tuple(ns), x=ns[j - k], y=ns[j])
          

          There is only one candidate pair of prime ages, and only one of the potential replacement/replaced pairings gives a duplicate age greater than 40.

          (And a bit more analysis fixes the values of the common difference, the mean ages, and the difference between the replaced and replacement ages).

          Like

      • Brian Gladman 10:27 am on 20 June 2021 Permalink | Reply

        A nice approach, Tony, which I ‘stole’ here. For those who want to run your code, here is a one line replacement for line one:

        primes = {x for x in range(23, 63, 2) if all(x % p for p in {2, 3, 5, 7})}
        

        Like

    • GeoffR 1:27 pm on 20 June 2021 Permalink | Reply

      Hi Tony,

      I have re-formatted your code to PEP8, which can easily be done under the Wingware IDE for Python,
      using Source/Reformatting from the main menu – hope you don’t mind, but it does make code much
      easier to read and appreciate by others. Have also replaced end of line comments as separate comments, as suggested by Jim. An interesting solution.

      https://wingware.com/downloads

      #primes = list(SieveOfEratosthenes(65))
      # using Brian's replacement for line above
      primes = {x for x in range(23, 63, 2)
                if all(x % p for p in {2, 3, 5, 7})}
      squares = [x**2 for x in range(4, 9)]
      
      # possible common differences
      for delta in range(1, 5):
        # consequential possible start ages
        for a_min in range(20, 66 - 11 * delta):
          # sequence of 12 ages
          ages = [a_min + delta * i for i in range(12)]
          # no square ages
          if set(ages).intersection(squares):
            continue
      
          # subset of possible ages of the twins
          twins = [t for t in ages if t > 40]
      
          # average of the 12 ages
          a_ave = a_min + 11 * delta / 2
          # lowest possible average age of a jury
          min_ave = a_ave + (40 - max(ages)) / 12
          # highest possible average age of a jury
          max_ave = a_ave + (max(ages) - min(ages)) / 12
          poss_aves = [p for p in primes
                       if p >= min_ave and p < max_ave]
      
          # possible prime average ages for a jury,
          # ...must be (at least) 2 of them
          if len(poss_aves) < 2:
            continue
      
          # find twins' age by trial and error
          for twin in twins:
            rejects = []
            for poss_ave in poss_aves:
              # age of swapped juror relative to twins
              swapped = twin + (a_ave - poss_ave) * 12
              if swapped > a_min + 11 * delta:
                break
              # reject impossible juror ages
              if swapped >= 20:
                rejects.append(swapped)
      
            # solution contains at least two swapped juror ages
            if len(rejects) > 1:
              # happily there is one solution
              #.. and it has exactly two swapped jurors
              print("The ages of the twins was", twin)
      
      
      
      

      Like

    • Tony Brooke-Taylor 12:04 pm on 26 June 2021 Permalink | Reply

      Thanks all for your comments. I apologise for my continued struggles making my code readable in these replies. I’ll have a look at the Wingware IDE.

      Like

    • Hugh Casement 11:34 am on 27 June 2021 Permalink | Reply

      With the same sequence the twins could have been aged 32, 35, or 38 (had those been allowed), still with the same two average ages. I found no other sequences that work, assuming no square ages .

      Like

  • Jim Randell 4:55 pm on 23 April 2021 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3057: Cut for partners 

    From The Sunday Times, 25th April 2021 [link]

    George and Martha are playing bridge with an invited married couple. Before play starts, the players have to cut for partners. Each player draws a card from a standard pack and those drawing the two highest-ranking cards play together against the other two. For this purpose, the rank order is ♠ A, ♥ A, ♦ A, ♣ A, ♠ K, ♥ K etc. down to ♦ 3, ♣ 3, ♠ 2, ♥ 2, ♦ 2, ♣ 2 (the lowest).

    George drew the first card, then Martha drew a lower-ranking card. “That is interesting!” commented the male visitor to his wife. “The probability that we shall play together is now P. Had Martha drawn the ♥ 7 instead of her actual card, that chance would have been reduced to P/2, and had she drawn the ♥ 6, the chance would have been reduced to P/3”.

    Which cards did George and Martha draw?

    [teaser3057]

     
    • Jim Randell 5:30 pm on 23 April 2021 Permalink | Reply

      The equations can be solved manually without too much trouble to give a direct solution, but here is a Python program that solves the puzzle. It runs in 45ms.

      Run: [ @replit ]

      from enigma import P, irange, peek, printf
      
      # the cards in order (highest to lowest)
      cards = list(v + s for v in "AKQJX98765432" for s in "SHDC")
      
      # index of certain cards we are interested in
      (i6H, i7H) = (cards.index(c) for c in ('6H', '7H'))
      
      # count number of pairs where X+Y play together
      # given G chose card index g and M chose card index m
      N = lambda g, m: P(g, 2) + P(51 - m, 2)
      
      # choose a card for G
      for g in irange(0, i7H - 2):
      
        # count pairs if M had chosen 6H or 7H
        ns = { 3 * N(g, i6H), 2 * N(g, i7H) }
        if len(ns) > 1: continue
      
        # choose a card for M
        for m in irange(g + 1, i7H - 1):
      
          # that gives the correct probability
          if N(g, m) in ns:
            # output solution
            printf("G={G} [{g}]; n={n}; M={M} [{m}]", G=cards[g], M=cards[m], n=peek(ns))
      

      Solution: George’s card is A♣. Martha’s card is 9♠.


      Manually:

      There are 52 cards to start with, and after George has chosen one (at index g in the list of cards) there are 51 remaining.

      Martha chooses a lower card than George (at index m in the list of cards), and now there are 50 cards remaining.

      The other couple (X and Y) then choose cards, and if they are both lower valued than Martha’s card, or higher valued than George’s card then they will play together.

      The number of possible (unordered) pairs of cards for X+Y is: P(50, 2) = 2450, but that is the same in all scenarios, so we can just compare the number of pairs which result in X+Y playing together (n = 2450p).

      The number of pairs which are better than George’s card is: P(g, 2)

      And the number of pairs which are worse than Martha’s card is: P(51 − m, 2)

      If Martha had chosen 6♥ (the card at index 33) the probability is p/3, so:

      P(g, 2) + P(51 − 33, 2) = n / 3

      If Martha had chosen 7♥ (the card at index 29) the probability is p/2, so:

      P(g, 2) + P(51 − 29, 2) = n / 2

      Together these solve to give:

      P(22, 2) − P(18, 2) = n / 6
      ⇒ n = 936
      g² − g − 6 = 0
      (g + 2)(g − 3) = 0
      ⇒ g = 3

      So G chose the card at index 3 = A♣.

      (And the probability is p = 936/2450 ≈ 0.382).

      To find Martha’s card:

      P(g, 2) + P(51 − m, 2) = 936
      m² − 101m + 2556 = 936
      m² − 101m + 1620 = 0
      (m − 20)(m − 81) = 0
      ⇒ m = 20

      So M chose the card at index 20 = 9♠.

      Like

    • Frits 11:45 pm on 23 April 2021 Permalink | Reply

      from enigma import div
      
      # chance is numerator / denominator
      # P = lambda g, m: ((m - 1) * (m - 2)  + (52 - g) * (51 - g)) / (50 * 49)
      
      # range cards: 1 - 52
      # H7 is card 23, H6 = 19
      
      suits = ["clubs", "diamonds", "hearts", "spades"]
      nums = ["2", "3", "4", "5", "6", "7", "8", "9", "10", 
              "jack", "queen", "king", "ace"]
        
      # check values for George
      for g in range(25, 53):
        G = (52 - g) * (51 - g)
        
        h7_numerator = (23 - 1) * (23 - 2) + G
        if h7_numerator % 3: continue
        
        h6_numerator = 2 * h7_numerator // 3
        if h6_numerator != (19 - 1) * (19 - 2) + G: continue
        
        m_numerator = 2 * h7_numerator
        
        # (m - 1) * (m - 2) + G = m_numerator, m^2 -3m + (2 + G - m_numerator) = 0
        m = div(3 + (1 + 4 * (m_numerator - G)) ** 0.5, 2)
        if m is None: continue
        m = int(m)
        
        print(f"George and Martha drew {nums[(g - 1) // 4]} of {suits[(g - 1) % 4]}"
              f" and {nums[(m - 1) // 4]} of {suits[(m - 1) % 4]}")
      

      With more analysis:

      from enigma import div
      
      # range cards: 1 - 52
      
      suits = ["clubs", "diamonds", "hearts", "spades"]
      nums = ["2", "3", "4", "5", "6", "7", "8", "9", "10", 
              "jack", "queen", "king", "ace"]
      
      # numerator of P = (m - 1) * (m - 2) + (52 - g) * (51 - g) 
      # P is twice the chance for H7 (card 23)
      
      # (m - 1) * (m - 2) + (52 - g) * (51 - g) = 
      # 2 * ((23 - 1) * (23 - 2) + (52 - g) * (51 - g))
      
      # g = 103 / 2 - ((4 * m^2 - 12*m - 3687) ^ 0.5) / 2
      
      # P is three times the chance for H6 (card 19)
      
      # (m - 1) * (m - 2) + (52 - g) * (51 - g) = 
      # 3 * ((19 - 1) * (19 - 2) + (52 - g) * (51 - g))
      
      # g = 103 / 2 - ((2 * m^2 - 6*m - 1831) ^ 0.5) / 2
      
      # (4 * m^2 - 12*m - 3687) - (2 * m^2 - 6*m - 1831) = 0
      # 2 * m^2 - 6 * m - 1856 = 0
      m = div(6 + (36 + 4 * 2 * 1856) ** 0.5, 4)
      if m is None: exit() 
      m = int(m)
      
      g = div(103 - (4 * m ** 2 - 12 * m - 3687) ** 0.5, 2)
      if g is None: exit() 
      g = int(g)
      
      print(f"George and Martha drew {nums[(g - 1) // 4]} of {suits[(g - 1) % 4]}"
            f" and {nums[(m - 1) // 4]} of {suits[(m - 1) % 4]}")
      

      Like

  • Jim Randell 5:01 pm on 26 March 2021 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3053: Endless number 

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

    George and Martha have a telephone number consisting of nine digits; there is no zero and the others appear once each. The total of the digits is obviously 45, so that the number is divisible by nine. Martha noticed that, if she removed the last (i.e., the least significant) digit, an eight-digit number would remain, divisible by eight. George added that you could continue this process, removing the least significant digit each time to be left with an n-digit number divisible by n right down to the end.

    What is their telephone number?

    [teaser3053]

     
    • Jim Randell 5:04 pm on 26 March 2021 Permalink | Reply

      See also: Enigma 339b, Enigma 1643, Enigma 1667, and the recently posed Puzzle #102.

      Here’s a solution using the [[ SubstitutedExpression ]] solver from the enigma.py library. It runs in 96ms.

      #!/usr/bin/env python3 -m enigma -r
      
      SubstitutedExpression
      
      --digits="1-9"
      
      "ABCDEFGH % 8 = 0"
      "ABCDEFG % 7 = 0"
      "ABCDEF % 6 = 0"
      "ABCDE % 5 = 0"
      "ABCD % 4 = 0"
      "ABC % 3 = 0"
      "AB % 2 = 0"
      
      --answer="ABCDEFGHI"
      

      Solution: The number is 381654729.

      Like

    • Frits 10:40 pm on 26 March 2021 Permalink | Reply

      Tested in a Python online editor.

      from itertools import permutations
      
      print(*["".join(x) for x in permutations("123456789")
            if all(int("".join(x[:i])) % i == 0 for i in range(2, 9))])
      

      Like

      • Tony Brooke-Taylor 9:02 pm on 28 March 2021 Permalink | Reply

        I used a few shortcuts with the aim of minimising wasted iterations. Running my code on Replit takes about 3.5ms, compared with 1.13s for Frits’ sledgehammer approach. On the other hand, mine takes a few more lines!

        Hope I manage to paste this in an appropriate format:

        
        evens=list(range(2,9,2))
        Xs=list(range(2,9,4))
        odds=list(range(1,10,2))
        odds.remove(5)
        
        #Exploit the fact that the three-digit and six-digit numbers must be divisible by 3, and that the 6-digit number can be expressed as the sum of ABC*1000 and XYZ, so the digits of both ABC and XYZ must sum separately to 3. Let the last three digits be RST. 
        
        #For a four-digit number to be divisible by four, the last two digits must make a number divisible by four. Therefore if the third digit is odd, the fourth must be an odd multiple of 2
        for X in Xs:
          vec=[None for i in range(9)]
          vec[4]=5#no zeros allowed, and the five-digit number divisible by 5
          vec[3]=X
          Z=10-X#consequence of sum(X,Y,Z) divisible by three
          vec[5]=Z
          Bs=[i for i in evens if i not in vec]#remaining evens go to 2nd and 8th
          for B in Bs:
            vec[1]=B
            S=Bs[(Bs.index(B)-1)%2]
            vec[7]=S  
        
        #Try each pair of odd numbers (other than 5) in positions 1 and 3, subject to requiring that ABC divisible by 3 (requiring A+B+C divisible by 3)  
            for A in odds:
              Cs=odds.copy()
              Cs.remove(A)
              for C in Cs:
                if sum([A,B,C])%3==0:
                  vec[0]=A
                  vec[2]=C
        
        #Complementary odds then fall into positions 7 and 9          
                  Rs=Cs.copy()
                  Rs.remove(C)
                  for R in Rs:
                    vec[6]=R
                    if sum([i*10**(6-vec.index(i)) for i in vec[:7]])%7==0 and sum([i*10**(7-vec.index(i)) for i in vec[:8]])%8==0:
                      Ts=Rs.copy()
                      Ts.remove(R)
                      for T in Ts:
                        vec[8]=T
                        print("The telephone number is",sum([i*10**(8-vec.index(i)) for i in vec[:9]]))
        
        
        
        
        

        Like

        • Frits 10:57 am on 29 March 2021 Permalink | Reply

          Hi Tony,

          A couple of recommendations:

          index() is expensive so line 35 may be written to :

           
          if sum([x * 10**(2 - i) for i, x in enumerate(vec[5:8])]) % 8 == 0 and \
             sum([x * 10**(6 - i) for i, x in enumerate(vec[:7])]) % 7 == 0:
          

          You can also use something like “dgts2nbr” as in puzzle [[ https://enigmaticcode.wordpress.com/2017/04/10/enigma-1120-assorted-numbers/ ]]

          – line 16-19 can be rewritten to:

           
          for j, B in enumerate(Bs):
            vec[1] = B
            vec[7] = Bs[1 - j]
          

          or

           
          for j in range(2):
            vec[1] = Bs[j]
            vec[7] = Bs[1 - j]
          

          or

           
          for (B, S) in permutations(Bs):
            vec[1] = B
            vec[7] = S 
          

          Like

          • Tony 6:01 pm on 29 March 2021 Permalink | Reply

            Thanks Frits: now I know about enumerate and reduce. As I’m getting the hang of looping over iterables I had forgotten it is still possible to loop with a counter, so I would probably choose your second approach to lines 16-19. I was trying to avoid using itertools once I realised I only needed to deal with pairs.

            Like

    • Hugh Casement 12:51 pm on 27 March 2021 Permalink | Reply

      Isn’t this effectively the same as Brainteaser 1040 (4 July 1982)?

      That one at least spared us George and Martha, who get dragged in again and again for no good reason.

      Like

      • Jim Randell 3:36 pm on 27 March 2021 Permalink | Reply

        @Hugh: It does seem to be. I suppose it is hard to come up with over 3000 puzzles without some overlap. Or to check that there is no overlap!

        Like

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

          And the recent Puzzle 102 in New Scientist. I call that plagiarism!

          Like

    • GeoffR 1:39 pm on 20 May 2021 Permalink | Reply

      from itertools import permutations
      from enigma import join
      
      # the number 45 is divisible by nine
      i = 9
      
      for p in permutations('123456789', 8):
        a, b, c, d, e, f, g, h = p
      
        # A geometric pattern to this code  
      
        if int(a + b + c + d + e + f + g + h) % 8 == 0:
          if int(a + b + c + d + e + f + g) % 7 == 0:
            if int(a + b + c + d + e + f) % 6 == 0:
              if int(a + b + c + d + e) % 5 == 0:
                if int(a + b + c + d) % 4 == 0:
                  if int(a + b + c) % 3 == 0:
                    if int(a + b) % 2 == 0:
                      tel_num = join((a, b, \
                      c, d, e, f, g, h, i))
                      print('No. is',tel_num)
                      # No. is 381654729
      
      

      Like

    • Dirk 9:43 am on 9 July 2021 Permalink | Reply

      Teaser 1882 (11/10/1998): Multiple Solutions by Rex Gooch is also linked with brainteaser 1040/3053.

      Like

  • Jim Randell 4:52 pm on 5 February 2021 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3046: Square phone numbers 

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

    George and Martha run a business in which there are 22 departments. Each department has a perfect-square three-digit extension number, i.e., everything from 100 (10²) to 961 (31²), and all five of their daughters are employees in separate departments.

    Andrea, Bertha, Caroline, Dorothy and Elizabeth have extensions in numerical order, with Andrea having the lowest number and Elizabeth the highest. George commented that, if you look at those of Andrea, Bertha and Dorothy, all nine positive digits appear once. Martha added that the total of the five extensions was also a perfect square.

    What is Caroline’s extension?

    [teaser3046]

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

      A simple Python program finds the solution in 67ms.

      Run: [ @repl.it ]

      from enigma import powers, subsets, concat, is_square, printf
      
      # possible square numbers
      squares = powers(10, 31, 2)
      
      # choose 5 of the squares (in order)
      for (A, B, C, D, E) in subsets(squares, size=5):
      
        # the sum of the numbers is also a square
        if not is_square(A + B + C + D + E): continue
      
        # A, B, D use the digits 1-9
        ds = concat(A, B, D)
        if '0' in ds or len(set(ds)) != 9: continue
      
        # output solution
        printf("A={A} B={B} C={C} D={D} E={E}")
      

      Solution: Caroline’s extension is 729.

      Manually, you can write out the squares, and then working through the possible candidates for A, B, D, the solution is discovered quite quickly.

      Like

    • GeoffR 7:51 pm on 5 February 2021 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
       
      % Andrea, Bertha, Caroline, Dorothy and Elizabeth's numbers
      var 100..961:A; var 100..961:B; var 100..961:C;
      var 100..961:D; var 100..961:E;
      
      constraint all_different([A, B, C, D, E]);
      constraint A < B /\ B < C /\ C < D /\ D < E;
      
      % digits for squares A, B and D
      var 1..9:a1; var 1..9:a2; var 1..9:a3;
      var 1..9:b1; var 1..9:b2; var 1..9:b3;
      var 1..9:d1; var 1..9:d2; var 1..9:d3;
      
      % all 3-digit squares
      set of int: sq3 = {n*n | n in 10..31};
      
      predicate is_sq(var int: y) =
      exists(z in 1..ceil(sqrt(int2float(ub(y)))))
      (z*z = y);
              
      % the total of the five extensions was also a perfect square        
      constraint is_sq(A + B + C + D + E);
      
       % all five telephone extensions are 3-digit squares       
      constraint A in sq3 /\ B in sq3 /\ C in sq3 /\ D in sq3 /\ E in sq3;
      
      % digit components in squares A, B and D
      constraint a1 == A div 100 /\ a2 == A div 10 mod 10 /\ a3 == A mod 10;
      constraint b1 == B div 100 /\ b2 == B div 10 mod 10 /\ b3 == B mod 10;
      constraint d1 == D div 100 /\ d2 == D div 10 mod 10 /\ d3 == D mod 10;
      
      set of int: all_dig ={1, 2, 3 ,4, 5, 6, 7, 8, 9};
      var set of int: S1 == {a1, a2, a3, b1, b2, b3, d1, d2, d3};
      constraint all_dig == S1;
      
      solve satisfy;
      
      output ["Caroline's extension is " ++ show(C)
      ++"\nOther extensions are " ++ show(A) ++ ", " ++ show(B)
      ++ ", " ++ show(D) ++ ", " ++ show(E) ];
      
      

      Like

    • GeoffR 8:44 am on 6 February 2021 Permalink | Reply

      from itertools import combinations
      from enigma import nsplit, is_square
      
      squares =[x * x for x in range(10, 32)]
      
      for A, B, C, D, E in combinations(squares, 5):
        # five extensions are in numerical order
        if A < B < C < D < E:
          sq_total = A + B + C + D + E 
          if is_square(sq_total):
            # find individual digits of A, B and D
            a1, a2, a3 = nsplit(A)
            b1, b2, b3 = nsplit(B)
            d1, d2, d3 = nsplit(D)
            S1 = set((a1, a2, a3, b1, b2, b3, d1, d2, d3))
            # check this set contains nine positive digits
            if len(S1) == 9 and 0 not in S1:
              print(f"Five extensions are {A}, {B}, {C}, {D}, {E}")
      
      
      

      Like

    • Frits 7:44 pm on 6 February 2021 Permalink | Reply

      squares = [x * x for x in range(15, 66)]
      ABD_squares = [x * x for x in range(13, 31) 
                     if len(set(str(x * x) + "0")) == 4]
      
      for i, a in enumerate(ABD_squares[:-2]):
        for j, b in enumerate(ABD_squares[i + 1:-1]):
          if len(set(str(a) + str(b))) != 6: continue
          for d in ABD_squares[i + j + 2:]:
            if len(set(str(a) + str(b) + str(d))) != 9: continue
            p = squares.index(b)
            q = squares.index(d)
            
            for c in squares[p + 1:q]:
              for e in squares[q + 1:17]:
                if not (a + b + c + d + e) in squares: continue
                print(f"Caroline's extension is {c}.")
      

      Like

    • Tony Brooke-Taylor 10:21 am on 7 February 2021 Permalink | Reply

      import itertools
      
      digits = set([str(j) for j in range(1, 10)])
      
      for M in itertools.combinations([i**2 for i in range(10, 32)], 5):
        if (sum(M)**(1/2))%1 == 0:
          if set(str(M[0])+str(M[1])+str(M[3])) == digits: print("Caroline's extension is", M[2])
      

      Like

    • Frits 4:21 pm on 7 February 2021 Permalink | Reply

      Logic can reduce a,b and d to specific squares.
      The version without reduction logic is faster.

      from collections import defaultdict
      
      squares = [x * x for x in range(15, 66)]
      ABD_squares = [x * x for x in range(13, 31) 
                     if len(set(str(x * x) + "0")) == 4]
          
      # return first column    
      col0 = lambda li: [x[0] for x in li]               
      
      fixed = set()
      str_fixed = ""
      mut_excl = defaultdict(set)
      
      # reduce candidates in list <ABD_squares>
      # by looking for digits occurring once or twice in list <ABD_squares>
      for loop in range(9):
        occ = defaultdict(list)
        
        for x in "123456789":
          # add entries to occurence dictionary <occ>
          for i, s in enumerate(ABD_squares):
            s1 = str(s)
            if x not in s1 or s1 in fixed: continue
            # skip if a square digit already occurs in <fixed>
            if any(y in str_fixed for y in s1): continue
            occ[x].append([i, s1])
      
          if len(occ[x]) == 1:  
            # a square has to occur in <ABD_squares>
            if not occ[x][0][1] in fixed:
              fixed.add(occ[x][0][1])
              str_fixed = "".join(fixed)
              # also update occurrence for other 2 related digits to same square
              for c in occ[x][0][1]:
                if c == x: continue
                occ[c] = occ[x]
          elif len(occ[x]) == 2:
            # we have a square with digits x,e,f and a square with digits x,g,h
            # so e, e, f, f may not occur in another square with resp. g, h ,g, h
            for y in occ[x][0][1]: 
              if y == x: continue
              for z in occ[x][1][1]:
                if z == x: continue
                mut_excl[y].add(z)  
                mut_excl[z].add(y) 
      
        # check if 2 digits always occur in different squares
        for x1, y1 in occ.items():
          for x2, y2 in occ.items():
            if x1 >= x2: continue
            if len(set(col0(y1) + col0(y2))) != \
               len(col0(y1)) + len(col0(y2)): continue
            # digit x1 and x2 occur in different squares
            # so maintain dictionary <mut_excl>
            mut_excl[x1].add(x2)
            mut_excl[x2].add(x1)
      
        # reduce squares if a square contains mutually exclusive digits
        ABD_squares = [s for s in ABD_squares 
                       if not any(m in str(s) for y in str(s) 
                                  for m in mut_excl[y])
                      ]
         
        if len(ABD_squares) == 3: break
        
        
      # start with a,b,d and then check c and e
      for i, a in enumerate(ABD_squares[:-2]):
        for j, b in enumerate(ABD_squares[i + 1:-1]):
          if len(set(str(a) + str(b))) != 6: continue
          for d in ABD_squares[i + j + 2:]:
            if len(set(str(a) + str(b) + str(d))) != 9: continue
            p = squares.index(b)
            q = squares.index(d)
            
            for c in squares[p + 1:q]:
              for e in squares[q + 1:17]:
                if not (a + b + c + d + e) in squares: continue
                print(f"Caroline's extension is {c}.")
      

      Like

    • Frits 1:25 pm on 8 February 2021 Permalink | Reply

      Not very efficient.

      # decompose a number into <k> increasing 3-digit squares a, b, c, d, e
      # (a, b, d) has to contain all digits 1-9
      def decompose(t, k, m=1, ss=[]):
        if k == 1:
          # check if last number is higher and a square
          if t > ss[-1] and int(t ** 0.5) ** 2 == t:
            yield ss + [t]
        else:
          for i in range(m, 32 - k + 1):
            s = i * i
            if s > t: break
            
            if k in {3, 1} or len(set(str(s) + "0")) == 4:
              yield from decompose(t - s, k - 1, i + 1, ss + [s])
              
      # sum first five 3-digit squares is 750 and last five 3-digit squares is 4215
      for r in range(28, 65):
        # get five 3-digit squares which add up to square <r * r>
        for x in decompose(r * r, 5, 10): 
          sabd = set(str(1000000 * x[0] + 1000 * x[1] + x[3]))
          if len(sabd) != 9: continue
      
          print(f"Caroline's extension is {x[2]}.")
      

      Like

  • Jim Randell 4:30 pm on 11 December 2020 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3038: Progressive raffle 

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

    George and Martha were participating in the local village raffle. 1000 tickets were sold, numbered normally from 1 to 1000, and they bought five each. George noticed that the lowest-numbered of his tickets was a single digit, then each subsequent number was the same multiple of the previous number, e.g. (7, 21, 63, 189, 567). Martha’s lowest number was also a single digit, but her numbers proceeded with a constant difference, e.g. (6, 23, 40, 57, 74). Each added together all their numbers and found the same sum. Furthermore, the total of all the digits in their ten numbers was a perfect square.

    What was the highest numbered of the ten tickets?

    [teaser3038]

     
    • Jim Randell 4:52 pm on 11 December 2020 Permalink | Reply

      If the sum of the 5 terms of the geometric progression is t, and this is also the sum of the 5 term arithmetic progression (a, a + d, a + 2d, a + 3d, a + 4d), then we have:

      t = 5(a + 2d)

      So the sum must be a multiple of 5.

      The following Python program runs in 44ms.

      Run: [ @repl.it ]

      from enigma import irange, div, union, nsplit, is_square, printf
      
      # generate geometric progressions with k elements
      # n = max value for 1st element, N = max value for all elements
      def geom(k, n=9, N=1000):
        # first element
        for a in irange(1, n):
          # second element is larger
          for b in irange(a + 1, N):
            # calculate remaining elements
            (gs, rs, x) = ([a, b], 0, b)
            while len(gs) < k:
              (x, r) = divmod(x * b, a)
              gs.append(x)
              rs += r
            if x > N: break
            if rs == 0:
              yield tuple(gs)
      
      # digit sum of a sequence
      dsums = lambda ns: sum(nsplit(n, fn=sum) for n in ns)
      
      # consider geometric progressions for G
      for gs in geom(5):
        x = div(sum(gs), 5)
        if x is None: continue
        
        # arithmetic progressions for M, with the same sum
        # and starting with a single digit
        for a in irange(1, 9):
          d = div(x - a, 2)
          if d is None: continue
          ms = tuple(irange(a, a + 4 * d, step=d))
          ns = union([gs, ms])
          if len(ns) != 10: continue
      
          # the sum of the digits in all the numbers is a perfect square
          r = is_square(dsums(ns))
          if r is None: continue
      
          m = max(gs[-1], ms[-1])
          printf("max = {m}; gs = {gs}, ms = {ms}; sum = {t}, dsum = {r}^2", t=5 * x)
      

      Solution: The highest numbered ticket is 80.

      Note that the [[ geom() ]] function will generate all possible geometric progressions, including those that have a non-integer common ratio, (e.g. for a 4 element progression we could have (8, 28, 48, 343)), but for a 5 element progression we would need the initial term to have a factor that is some (non-unity) integer raised to the 4th power, and the smallest possible value that would allow this is 2^4 = 16, which is not possible if the initial term is a single digit. So for this puzzle the solution can be found by considering only those progressions with an integer common ratio (which is probably what the puzzle wanted, but it could have said “integer multiple” to be completely unambiguous).

      Like

    • GeoffR 7:48 pm on 11 December 2020 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % terms of arithmetic series
      var 1..9: A1; var 2..100: A2;var 2..150: A3;
      var 2..250: A4; var 2..500: A5;
      
      % terms of geometric series
      var 1..9: G1; var 2..100: G2;var 2..150: G3;
      var 2..250: G4; var 2..500: G5;
      
      % geometric ratio and arithmetic difference
      var 2..9:Gratio; 
      var 2..200:Adiff;
      
      %  total sum of  geometric and arithmetic series 
      var 2..500: Gsum; 
      var 2..500: Asum;
      
      % maximum raffle ticket number
      var 2..999: max_num;  
      
      constraint A2 == A1 + Adiff;
      constraint A3 == A2 + Adiff;
      constraint A4 == A3 + Adiff;
      constraint A5 == A4 + Adiff;
      
      constraint Asum = A1 + A2 + A3 + A4 + A5;
      
      constraint G2 == G1*Gratio;
      constraint G3 == G2*Gratio;
      constraint G4 == G3*Gratio;
      constraint G5 == G4*Gratio;
      
      constraint Gsum = G1 + G2 + G3 + G4 + G5;
      
      constraint Gsum == Asum;
      
      constraint all_different ([A1,A2,A3,A4,A5,G1,G2,G3,G4,G5]);
      
      constraint max_num = max({A1,A2,A3,A4,A5,G1,G2,G3,G4,G5});
      
      solve satisfy;
      
      output[ "Geometric Series: " ++ show([G1,G2,G3,G4,G5])  
      ++ "\n" ++ "Arithmetic Series = " ++ show([A1,A2,A3,A4,A5]) 
      ++ "\n" ++ "Max ticket number = " ++ show(max_num) ];
      

      This programme produces three solutions, all with the same maximum value.
      In only one of the solutions do the digits add to a perfect square.

      Like

    • Jim Randell 8:05 pm on 11 December 2020 Permalink | Reply

      @Geoff: There’s only one copy of each ticket, so both George and Martha’s sets of numbers are fully determined. (Even though we are only asked for the highest numbered ticket).

      Like

    • Frits 2:30 pm on 12 December 2020 Permalink | Reply

      for k in range(2, 6):
        print(sum(k**i for i in range(5)))
      

      The result of this piece of code are all numbers ending on a 1. This limits George’s lowest-numbered ticket to one number.

      Like

    • Frits 7:57 pm on 12 December 2020 Permalink | Reply

      The generated code can be seen with option verbose=256.

      from enigma import SubstitutedExpression, is_prime, is_square, \
                         seq_all_different
      
      # Formula
      # G * (1 + K + K^2 + K^3 + K^4) == 5 * (M + 2*D) 
      
      # (1 + K + K^2 + K^3 + K^4) equals (K^5 - 1) / (K - 1) 
      # (idea Brian Gladman)
      
      # sum the digits of the numbers in a sequence
      dsums = lambda seq: sum(sum(int(x) for x in str(s)) for s in seq)
      
      # domain lists
      dlist = list(range(3))  # d < 250
      elist = list(range(10))
      flist = list(range(10))
      glist = list(range(1, 10))
      klist = []
      mlist = list(range(1, 10))
      
      lastdigit = set()           # last digit of the total of 5 tickets
      # K^4 may not be higher than 1000, so K < 6
      for k in range(2, 6):
        t = sum(k**i for i in range(5))
        klist.append(k)
        lastdigit.add(t % 10)
        
      lastdigit = list(lastdigit)  
      
      if 5 not in lastdigit:
        # George's lowest ticket must be 5 as total is a multiple of 5
        glist = [5]
        
      if len(lastdigit) == 1:
        # Martha's tickets sum to 5 * (M + 2*D) 
        if lastdigit[0] % 2 == 1:
          # Martha's lowest ticket must be odd
          mlist = [1, 3, 5, 7, 9]
        else:
          # Martha's lowest ticket must be even
          mlist =[2, 4, 6, 8]
        
      if len(glist) == 1:
        # George's highest ticket may not be higher than 1000
        klist = [x for i, x in enumerate(klist, start=2) 
                               if i**4 * glist[0] <= 1000]
        # calculate highest sum of 5 tickets                       
        t = sum(max(klist)**i for i in range(5)) * glist[0]
        
        # Martha's highest ticket may not be higher than (t/5 - M) / 2
        dlist = [x for x in dlist if x <= (t / 10) // 100]
        if dlist == [0]:
          elist = [x for x in elist if x <= (t // 10) // 10]
          
        mlist = [x for x in mlist if x != glist[0]]
        
      # build dictionary for invalid digits 
      vars = "DEFGKM"
      invalid = "dict("
      for i in range(10):
        txt = ""
        for j, li in enumerate([dlist, elist, flist, glist, klist, mlist]):
          if i not in li:
            txt += vars[j]
        invalid += "[(" + str(i) + ", '" + txt + "')] + "
      
      invalid = invalid[:-3] + ")"  
      
      
      exprs = []
      
      # George's and Martha's sum was the same
      exprs.append("G * (K ** 5 - 1) // (K - 1) == 5 * (M + 2 * DEF)")
      # all ticket numbers have to be different
      exprs.append("seq_all_different([G, G * K, G * K**2, G * K**3, G * K**4, \
                                     M, M + DEF, M + 2*DEF, M + 3*DEF, M + 4*DEF])")
      # total of all the digits in the ten numbers was a perfect square                               
      exprs.append("is_square(dsums([G, G * K, G * K**2, G * K**3, G * K**4, \
                                     M, M + DEF, M + 2*DEF, M + 3*DEF, M + 4*DEF]))")    
                                     
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        exprs,
        answer="(G, G * K, G * K**2, G * K**3, G * K**4), \
                (M, M + DEF, M + 2*DEF, M + 3*DEF, M + 4*DEF), 5 * (M + 2 * DEF)",
        d2i=eval(invalid),
        env=dict(dsums=dsums),
        distinct="GM",
        verbose=0)   
      
      # Print answers 
      for (_, ans) in p.solve():
          print(f"{ans}")
      

      Like

    • Tony Brooke-Taylor 9:10 am on 14 December 2020 Permalink | Reply

      My first solution generated the series explicitly and then applied the tests to each series, but then I realised we could equate the expressions for the sums of each series to reduce the sets of parameters we need to test. My second attempt was similar to below but instead of constraining the sum to a multiple of 5 I initially constrained my ‘b’ parameter to an integer, which I think is mathematically equivalent but came inside another loop so was less efficient.

      #Generator function to create possible combinations of parameters for George and Martha
      def core_parameters():
        #George's tickets form the geometric progression x.(y^0, y^1, y^2, y^3, y^4)
        #Martha's tickets form the arithmetic progression (a+b*0, a+b*1, a+b*2, a+b*3, a+b*4)
      
        for x in range(1,10): #we are told that George's first ticket has a single-digit value
          max_y = int((1000/x)**(0.25)//1)+1 #defined by the maximum possible value for George's highest-value ticket
          for y in range(2,max_y): #y=1 not possible because this would give all George's tickets the same value
      
            sum_values = x*(1-y**5)/(1-y) #sum of finite geometric progression
            #sum_values = a*5 + b*10      #sum of finite arithmetic progression
            #=> b = (sum_values - a*5)/10 = sum_values/10 - a/2
            if sum_values%5 == 0:      #equality also requires that the sum is a multiple of 5
      
              for a in range(1,10): #we are told that Martha's first ticket has a single-digit value
                if a != x:          #Martha's first ticket cannot have the same value as George's
                  b = sum_values/10 - a/2
                  yield x, y, a, b
      
      #Function to sum all digits in an integer
      def digit_sum(num):
        s = 0
        for dig in str(num):
          s = s + int(dig)
        return s  
      
      #Control program
      
      for first_george_ticket, george_multiple, first_martha_ticket, martha_multiple in core_parameters():
        george_tix = [first_george_ticket*george_multiple**n for n in range(5)]
        martha_tix = [int(first_martha_ticket+martha_multiple*m) for m in range(5)]
      
        #they can't both have the same ticket and we are told that the sum of all digits is a square
        if set(martha_tix).intersection(george_tix) == set() and \
        ((sum([digit_sum(j) for j in george_tix]) + sum([digit_sum(k) for k in martha_tix]))**(1/2))%1 == 0: 
        
          print("George:", george_tix)
          print("Martha:", martha_tix)
          print("Highest-valued ticket:",max(george_tix + martha_tix))     
          break
      

      Like

  • Jim Randell 9:17 am on 1 December 2020 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2769: Coming home 

    From The Sunday Times, 18th October 2015 [link] [link]

    George, Martha and their daughter all drive at their own steady speeds (whole numbers of mph), the daughter’s speed being 10mph more than Martha’s. One day George left home to drive to his daughter’s house at the same time as she left her house to visit her parents: they passed each other at the Crossed Keys pub. Another time Martha left her daughter’s to return home at the same time as her daughter started the reverse journey: they too passed at the Crossed Keys. The distance from George’s to the pub is a two-figure number of miles, and the two digits in reverse order give the distance of the pub from their daughter’s.

    How far is it from George’s home to the Crossed Keys?

    [teaser2769]

     
    • Jim Randell 9:17 am on 1 December 2020 Permalink | Reply

      This Python program considers candidate 2-digit distances from the parent’s house to the pub. It runs in 43ms.

      Run: [ @repl.it ]

      from enigma import irange, nreverse, div, printf
      
      # consider 2 digit distance from parent's house to X
      for p in irange(10, 99):
        # distance to the daughter's house to X is the reverse
        d = nreverse(p)
        if not(d < p): continue
      
        # in the second journey the mother's speed is...
        vm = div(10 * d, p - d)
        if vm is None: continue
        vd = vm + 10
      
        # in the first journey the father's speed is...
        vf = div(p * vd, d)
        if vf is None: continue
      
        printf("p={p} d={d}, vm={vm} vd={vd} vf={vf}")
      

      Solution: It is 54 miles from George’s house to the Crossed Keys.

      So it is 45 miles from the daughter’s house.

      The speeds are: mother = 50 mph, daughter = 60 mph, father = 72 mph.

      Like

  • Jim Randell 2:58 pm on 27 October 2020 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 2761: Digital shuffle 

    From The Sunday Times, 23rd August 2015 [link] [link]

    George and Martha have nine cards with a different non-zero digit on each. To teach their nephew to count they lined up the cards in increasing order. He then rearranged the order of the line and Martha was impressed when she noticed that no digit was in its original position. George was even more impressed when he found that the six-figure number formed by the last six cards was the square of the three-figure number formed by the first three.

    What was that three-figure number?

    [teaser2761]

     
    • Jim Randell 2:59 pm on 27 October 2020 Permalink | Reply

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

      The following run file executes in 99ms.

      Run: [ @repl.it ]

      #!/usr/bin/env python -m enigma -r
      
      SubstitutedExpression
      
      # non-zero digits
      --digits="1-9"
      
      # no digit is in its original position
      --invalid="1,A"
      --invalid="2,B"
      --invalid="3,C"
      --invalid="4,D"
      --invalid="5,E"
      --invalid="6,F"
      --invalid="7,G"
      --invalid="8,H"
      --invalid="9,I"
      
      "ABC ** 2 = DEFGHI"
      
      --answer="ABC"
      

      Solution: The three-figure number was 854.

      Giving:

      854² = 729316

      If we remove the condition that “no digit is in its original position”, we find there is a further solution to the alphametic of:

      567² = 321489

      But this is disallowed as the digits 8 and 9 are in positions 8 and 9.

      Like

    • GeoffR 4:23 pm on 27 October 2020 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D; 
      var 1..9:E; var 1..9:F; var 1..9:G; var 1..9:H;
      var 1..9:I;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I]);
      
      % Nine cards not in original position
      constraint A != 1 /\ B != 2 /\ C != 3
      /\ D != 4 /\ E != 5 /\ F != 6 
      /\ G != 7 /\ H != 8 /\ I != 9;
      
      % Number formed by 1st three cards
      var 100..999: ABC = 100*A + 10*B + C;
      
      % Number formed by last six cards
      var 100000..999999: DEFGHI = 100000*D + 10000*E
      + 1000*F + 100*G + 10*H + I;
      
      %  the six-figure number formed by the last six cards was
      % the square of the three-figure number formed by the first three
      constraint DEFGHI = ABC * ABC;
      
      solve satisfy;
      
      output ["Three figure number was " ++ show(ABC)
      ++"\nSix figure number was " ++ show(DEFGHI) ];
      
      % Three figure number was 854
      % Six figure number was 729316
      % ----------
      % ==========
      
      
      

      Like

    • Frits 10:47 am on 28 October 2020 Permalink | Reply

      from itertools import dropwhile, count
       
      # return True if not all rules are met 
      def wrongSquare(trueList):
        
        # check if number meets the rules
        def OK(x):
          # booleans
          bs = trueList.copy()
          
          i = 0
          while x and i < 9:
            y = x % 10
            # no derangement
            if y != 9 - i:
              bs[y] = False
            x //= 10
            i += 1
          # all digits 1-9 used and no zero   
          return sum(bs) == 1 and bs[0]
          
        return lambda n: not OK(1000000 * n + (n * n)) 
       
      trueList = [True] * 10
      sol = next(dropwhile(wrongSquare(trueList), count(317)))
      print(f"{sol}^2 = {sol*sol}") 
      

      Like

    • GeoffR 1:43 pm on 28 October 2020 Permalink | Reply

      
      from itertools import permutations
      
      for p1 in permutations('123456789'):
          a, b, c, d, e, f, g, h, i = p1
          # check numbers not in increasing order
          # no digit is in its original position
          if (a == '1' or b == '2' or c == '3' or
          d == '4' or e == '5' or f == '6' or
          g == '7' or g == '8' or i == '9'):
              continue
          # form 3-digit and 6-digit numbers
          abc = int(a + b + c)
          defghi = int(d + e + f + g + h + i)
          if abc ** 2 == defghi:
              print(f"Three digit number is {abc}")
              print(f"Six digit number is {defghi}")
              
      # Three digit number is 854
      # Six digit number is 729316
      
      

      Like

  • Jim Randell 7:09 pm on 28 August 2020 Permalink | Reply
    Tags: by: Danny Roth   

    Teaser 3023: Timely coincidence 

    From The Sunday Times, 30th August 2020 [link]

    George and Martha possess two digital “clocks”, each having six digits. One displays the time on a 24-hour basis in the format hh mm ss, typically 15 18 45, and the other displays the date in the format dd mm yy, typically 18 07 14.

    On one occasion, George walked into the room to find that the two “clocks” displayed identical readings. Martha commented that the long-term (400-year) average chance of that happening was 1 in just over a six-digit number. That six-digit number gives the birth date of one their daughters.

    On what date was that daughter born?

    [teaser3023]

     
    • Jim Randell 7:35 pm on 28 August 2020 Permalink | Reply

      On any particular day (ignoring jumps in time, such as leap seconds, daylight savings time, calendar reforms, etc.) there is either a 1/86400 chance of observing the clocks displaying the same an any given second (assuming they clocks are equally likely to be observed at any particular second of the day – which is unlikely), or a 0/86400 chance if the date does not correspond to a valid time.

      The following Python program runs in 100ms.

      Run: [ @repl.it ]

      import datetime
      from enigma import int2base, printf
      
      # consider a 400 year period, and count dates that give a valid time
      d = datetime.date(day=1, month=1, year=1900)
      i = datetime.timedelta(days=1)
      n = 0
      t = 0
      while d.year < 2300:
        if d.day < 24 and d.year % 100 < 60: n += 1
        d += i
        t += 1
      
      # output solution
      t *= 86400
      x = t // n
      printf("p = {n} / {t} (~ 1 / {x}); date = {b}", b=int2base(x, group=2, sep=".", width=6))
      

      Solution: The daughter was born on 19th May 1961.


      In order for the date to be read as a valid time we need the day of the month to be in the range 1-23, and the last 2 digits of the year to be in the range 0-59.

      In each year of the required range there are 23×12 = 276 viable days.

      In each 100 year period there are 60×276 = 16560 viable days.

      In each 400 year period there are 4×16560 = 66240 viable days.

      And in a 400 year period there is a total of 400×365 + 100 − 3 = 400×365.2425 = 146097 days.

      Converting to seconds we get a chance of 1 in (146097 × 86400 / 66240) = 190561.304….

      Truncating this result to an integer and reading as a date gives: Friday, 19th May 1961.

      Like

  • Jim Randell 9:06 am on 25 August 2020 Permalink | Reply
    Tags: by: Danny Roth,   

    Teaser 2677: One for each day 

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

    George and Martha have been looking into tests for divisibility,  including one for the elusive number seven. George wrote down a thousand-figure number by simply writing one particular non-zero digit a thousand times. Then he replaced the first and last digits by another non-zero digit to give him a thousand-figure number using just two different digits. Martha commented that the resulting number was divisible by seven. George added that it was actually divisible by exactly seven of 2, 3, 4, 5, 6, 7, 8, 9 and 11.

    What were the first two digits of this number?

    Note: This puzzle is marked as flawed, as under the intended interpretation there is no solution.

    [teaser2677]

     
    • Jim Randell 9:06 am on 25 August 2020 Permalink | Reply

      Although the puzzle is flawed, I didn’t have a problem solving it, as I interpreted the “actually” in George’s statement to mean that George was correcting Martha’s statement, so it didn’t really matter if Martha’s statement was true or false. All we needed to do was find a number divisible by exactly seven of the given divisors. Which is what my code does, and it finds a unique solution, so I was happy enough with the puzzle.

      Python’s support for large integers means this one can be solved in a straightforward way. The following program runs in 55ms.

      Run: [ @replit ]

      from enigma import (subsets, printf)
      
      digits = "123456789"
      divisors = (2, 3, 4, 5, 6, 7, 8, 9, 11)
      
      for (a, b) in subsets(digits, size=2, select="P"):
        n = int(a + b * 998 + a)
        ds = list(d for d in divisors if n % d == 0)
        if len(ds) == 7:
          printf("a={a} b={b} [ds={ds}]")
      

      Solution: The first two digits of George’s number are 6 and 3.

      So the 1,000-digit number is 6333…3336. Which is divisible by 2, 3, 4, 6, 8, 9, 11.


      However, apparently the setter intended Martha’s statement to be true, and the number to be divisible by 7 and exactly six of the other divisors. Unfortunately, there is no such number. (As we can see by adding [[ and 7 in ds ]] to the condition on line 9).

      The intended solution was 2777…77772, and the setter mistakenly determined that this was divisible by 8. In fact the number is divisible by only six of the divisors: 2, 3, 4, 6, 7, 11.

      Like

    • Hugh+Casement 11:33 am on 26 December 2021 Permalink | Reply

      The puzzle could have stated that the number is an integer multiple of seven of the integers 2 to 12 inclusive.

      However, we don’t need the ability to handle huge numbers (or even a computer).

      We can regard the number as jk followed by 498 blocks of kk followed by kj.
      The test for divisibility by 11 involves taking the alternating positive and negative sum of the digits. jk and kj cancel out, and each kk cancels, so the alternating digit sum is 0: the number is an integer multiple of 11 whatever the values of j and k.

      It cannot be a multiple of 10 since we were told the digits are non-zero.

      A test for divisibility by 7 is to take the alternating positive and negative sum of groups of three digits from the right. If and only if the result is a multiple of 7 is the overall number also a multiple of 7.

      In this case we have a number with j on the left, then 332 blocks of kkk, and kkj on the right.
      The even number of blocks cancel out, leaving kkj − j = kk0 = k × 110.
      That is an integer multiple of 7 only if k = 7.

      If the number is not a multiple of 2 then it cannot be a multiple of 4, 6, 8, or 12.
      If it is not a multiple of 3 then it cannot be a multiple of 6, 9, or 12.
      In order to have seven factors in the list it must be a multiple of both 2 and 3.

      If j = 2 the number is an integer multiple of 2 and 4 (but, as Jim says, not 8).

      So now we know j and k.
      We can regard the number as 27 followed by 332 blocks of 777 followed by 72.
      777 is a multiple of 3, so the digit sum is a multiple of 3 and the number is an integer multiple of 3 (but not of 9). Being a multiple of 3 and 4 it is also a multiple of 12.

      The number is not a multiple of 5, 8, or 9 (nor 10).

      Like

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel
%d bloggers like this: