Recent Updates Page 18 Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 11:17 am on 31 December 2023 Permalink | Reply
    Tags:   

    Brain teaser 988: Pythagoras was never like this 

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

    In accordance with tradition the retiring President of the All Square Club set members a special competition. Titled as above, it required new meanings for the signs “+” and  “=” as in:

    6² + 1² = 19²

    indicating that both sides could be taken to represent 361.

    The number 361 was then called a “Special Pythogorean Square” (SPS) and the numbers 36 and 1 its “contributing squares”.

    Other examples given were:

    7² + 27² = 223²
    15² + 25² = 475²

    giving 49729 and 225625 as Special Pythogorean Squares.

    Members were invited to submit series of Special Pythogorean Squares which they could claim to be unique in some way. Each number in their series had to be less than a million, and no two numbers in their series could have the same number of digits.

    After much deliberation the prize was awarded to the member who submitted a series of Special Pythogorean Squares which he correctly claimed was the longest possible list of such numbers all with the same second contributing square.

    What (in increasing order) were the numbers in the winning series?

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

    [teaser988]

     
    • Jim Randell's avatar

      Jim Randell 11:17 am on 31 December 2023 Permalink | Reply

      So we treat “+” and “=” as string operations, being concatenation and equality respectively.

      This Python program generates all SPSs below 1 million, and then constructs maximal length sequences which share the same suffix square.

      It runs in 61ms. (Internal runtime is 2ms).

      Run: [ @replit ]

      from enigma import (
        defaultdict, Accumulator, irange, inf, sq, cproduct, join, printf
      )
      
      # generate SPSs
      def generate(N):
        # record squares (as strings)
        d = dict()
        for i in irange(0, inf):
          n = sq(i)
          if not (n < N): break
          s = str(n)
          d[s] = i
      
          # split the square into prefix and suffix
          for j in irange(1, len(s) - 1):
            (pre, suf) = (s[:j], s[j:])
            (pi, si) = (d.get(pre), d.get(suf))
            if pi is None or si is None: continue
            printf("[{n} = {pre} + {suf} -> {i}^2 = {pi}^2 + {si}^2]")
            yield (s, pre, suf)
      
      # collect SPSs: <suffix> -> <length> -> [<SPS>...]
      d = defaultdict(lambda: defaultdict(list))
      for (s, pre, suf) in generate(1000000):
        d[suf][len(s)].append(s)
      
      # find maximal length sequences sharing the same suffix
      r = Accumulator(fn=max, collect=1)
      for (suff, v) in d.items():
        r.accumulate_data(len(v.keys()), suff)
      printf("maximal sequence length = {r.value}")
      
      # output maximal length sequences
      for k in r.data:
        printf("suffix = {k}")
        for ss in cproduct(d[k].values()):
          printf("-> {ss}", ss=join(ss, sep=", ", enc="()"))
      

      Solution: The winning series was: 49, 169, 3249, 64009, 237169.

      Which are constructed as follows:

      49 = 4 + 9 → 7^2 = 2^2 + 3^2
      169 = 16 + 9 → 13^2 = 4^2 + 3^2
      3249 = 324 + 9 → 57^2 = 18^2 + 3^2
      64009 = 6400 + 9 → 253^2 = 80^2 + 3^2
      237169 = 23716 + 9 → 487^2 = 154^2 + 3^2

      Each has a suffix square of 9.

      Like

      • Frits's avatar

        Frits 4:55 pm on 3 January 2024 Permalink | Reply

        Slower.

            
        from enigma import SubstitutedExpression
        
        # the alphametic puzzle
        p = SubstitutedExpression(
          [
            # ABC^2 concatenated with DEF^2 = square
            "ABC > 0",
            # RHS must be less than a million
            "(d2 := DEF**2) < 10 ** (6 - len(str(a2 := ABC**2)))",
            "is_square(int(str(a2) +  str(d2)))",
          ],
          answer="ABC, DEF",
          d2i="",
          distinct="",
          reorder=0,
          verbose=0,    # use 256 to see the generated code
        )
        
        # store answers in dictionary (key = suffix)
        d = dict()
        for a, b in p.answers():
          d[b] = d.get(b, []) + [a]
        
        # find maximal length sequences sharing the same suffix
        m = len(max(d.values(), key=lambda k: len(k)))
        
        # assume more than one answer is possible
        print("answer:", ' or '.join(str(x) for x in
             [sorted(int(str(v**2) + str(k**2)) for v in vs)
                     for k, vs in d.items() if len(vs) == m]))
        

        Like

        • Jim Randell's avatar

          Jim Randell 10:25 pm on 4 January 2024 Permalink | Reply

          @Frits: Would this work if there were multiple SPSs with the same length and suffix?(e.g. if 99856+9 were a valid SPS).

          Like

          • Frits's avatar

            Frits 6:39 pm on 5 January 2024 Permalink | Reply

            @Jim, you are right. This wouldn’t work as I forgot the condition “no two numbers in their series could have the same number of digits”.

            Like

  • Unknown's avatar

    Jim Randell 2:42 pm on 29 December 2023 Permalink | Reply
    Tags:   

    Teaser 3197: Three or four? 

    From The Sunday Times, 31st December 2023 [link] [link]

    Here are some clues about my PIN:

    (a) It has 3 digits (or it might be 4).
    (b) The first digit is 3 (or it might be 4).
    (c) The last digit is 3 (or it might be 4).
    (d) It is divisible by 3 (or it might be 4).
    (e) It differs from a square by 3 (or it might be 4).

    In each of those statements above just one of the guesses is true, and the lower guess is true in 3 cases (or it might be 4).

    That should enable you to get down to 3 (or it might be 4) possibilities for my PIN. But, even if you could choose any one of the statements, knowing which guess was correct in that statement would not enable you to determine the PIN.

    What is my PIN?

    This completes the archive of Teaser puzzles from 2023.

    For my selection of the more interesting puzzles encountered in 2023 see 2023 in review on Enigmatic Code.

    [teaser3197]

     
    • Jim Randell's avatar

      Jim Randell 2:59 pm on 29 December 2023 Permalink | Reply

      See also: Enigma 1609.

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

      Run: [ @replit ]

      from enigma import (irange, subsets, nconcat, is_square, filter_unique, intersect, join, printf)
      
      # check f(v, 3) or f(v, 4) are true (but not both)
      def check(v, f):
        (v3, v4) = (f(v, 3), f(v, 4))
        if v3 and not v4: return 3
        if v4 and not v3: return 4
        raise ValueError()
      
      # generate candidate PINs
      def generate():
        # consider 3- and 4-digit PINs
        for ds in subsets(irange(0, 9), min_size=3, max_size=4, select='M'):
          # evaluate the statements
          ss = list()
          try:
            # (a) "It has 3 or 4 digits"
            ss.append(check(ds, (lambda x, k: len(x) == k)))
            # (b) "The first digit is 3 or 4"
            ss.append(check(ds, (lambda x, k: x[0] == k)))
            # (c) "The last digit is 3 or 4"
            ss.append(check(ds, (lambda x, k: x[-1] == k)))
            # (d) "It is divisible by 3 or 4"
            n = nconcat(ds)
            ss.append(check(n, (lambda x, k: x % k == 0)))
            # (e) "It differs from a square by 3 or 4
            ss.append(check(n, (lambda x, k: is_square(x - k) or is_square(x + k))))
          except ValueError:
            continue
          # there are 3 or 4 answers of 3
          if not ss.count(3) in {3, 4}: continue
          # this is a viable candidate
          printf("[{ds} = {n} -> {ss}]")
          yield (ds, ss)
      
      # collect possible candidates (<digits>, <statements>)
      rs = list(generate())
      # there should be 3 or 4 candidates
      assert len(rs) in {3, 4}
      
      # for statement <k> generate ambiguous PINs
      def ambiguous(rs, k):
        stk = (lambda x: x[1][k])  # value of statement <k>
        pin = (lambda x: x[0])  # PIN
        # find PINs that are ambiguous by statement <k>
        for x in filter_unique(rs, stk, pin).non_unique:
          yield pin(x)
      
      # look for values that are ambiguous at each statement
      for ds in intersect(ambiguous(rs, k) for k in irange(5)):
        # output solution
        printf("PIN = {n}", n=join(ds))
      

      Solution: The PIN is 3603.

      There are 3 candidate numbers:

      (3, 3, 4, 4, 3) → 364
      (4, 3, 3, 3, 3) → 3603
      (4, 4, 3, 3, 3) → 4353

      And only 3603 is ambiguous by every statement.


      Manually:

      The PIN ends in 3 or 4, and is distance 3 or 4 from a perfect square.

      So we can look at squares that end in 0, 1, 6, 9 that are distance 3 or 4 away from a number matching “[34]*[34]”, and check the statements (a) – (e):

      19^2 + 3 = 364 → [3, 3, 4, 4, 3] OK
      20^2 + 3 = 403 → [3, 4, 3, X, 3]
      20^2 + 4 = 404 → [3, 4, 4, 4, 4]
      21^2 + 3 = 444 → [3, 4, 4, X, 3]
      56^2 − 3 = 3133 → [4, 3, 3, X, 3]
      57^2 + 4 = 3253 → [4, 3, 3, X, 4]
      59^2 + 3 = 3484 → [4, 3, 4, 4, 3]
      60^2 + 3 = 3603 → [4, 3, 3, 3, 3] OK
      60^2 + 4 = 3604 → [4, 3, 4, 4, 4]
      61^2 + 3 = 3724 → [4, 3, 4, 4, 3]
      63^2 + 4 = 3973 → [4, 3, 3, X, 4]
      64^2 − 3 = 4093 → [4, 4, 3, X, 3]
      66^2 − 3 = 4353 → [4, 4, 3, 3, 3] OK
      67^2 + 4 = 4493 → [4, 4, 3, X, 4]
      69^2 + 3 = 4764 → [4, 4, 4, X, 3]
      70^2 + 3 = 4903 → [4, 4, 3, X, 3]
      70^2 + 4 = 4904 → [4, 4, 4, 4, 4]

      Which gives the 3 viable candidates.

      Like

    • Frits's avatar

      Frits 4:01 pm on 29 December 2023 Permalink | Reply

      from enigma import is_square
      
      # does number <n> differ from a square by <k>
      nearsq = lambda n, k: any(is_square(n + i) for i in (-k, k)) 
      
      # check number <n> for 5 clues
      def check(n):
        s = str(n)
        # 3 or 4 digits
        c1 = 1 if len(s) == 3 else 2 if len(s) == 4 else 0
        if not c1: return []
        
        # first digit is 3 or 4
        c2 = 1 if s[0] == "3" else 2 if s[0] == "4" else 0
        if not c2: return []
        
        # last digit is 3 or 4
        c3 = 1 if s[-1] == "3" else 2 if s[-1] == "4" else 0
        if not c3: return []
        
        # divisible by 3 or 4
        c4 = 1 if n % 3 == 0 and n % 4 else 2 if n % 4 == 0 and n % 3 else 0
        if not c4: return []
        
        # differs from a square by 3 or 4
        c5 = 1 if nearsq(n, 3) and not nearsq(n, 4) else 2 \
               if nearsq(n, 4) and not nearsq(n, 3) else 0
        if not c5: return []
        
        return [c1, c2, c3, c4, c5]
        
      sols = []
      
      # check 3 and 4-digit numbers
      for n in range(303, 4995):
        # check all 5 clues
        row = check(n)
        if row:
          # 3 or 4 answers of the lower guess
          if not row.count(1) in {3, 4}: continue
          sols.append((n, row))
      
      # 3 or 4 possibilities for my PIN
      if len(sols) not in {3, 4}: 
        print("no solution")
        exit(0)    
      
      # create matrix for guesses
      mat = [s[1] for s in sols]
      tmat = list(zip(*mat))        # transpose matrix
      
      #  remove solutions which have an unique answer for a clue
      sols = [n for (n, r) in sols if all(tmat[i].count(r[i]) > 1 for i in range(5))]
      
      if len(sols) != 1: 
        print("no unique solution")
        exit(0) 
        
      print("answer:", *sols)      
      

      Like

    • Frits's avatar

      Frits 12:13 am on 30 December 2023 Permalink | Reply

      Fast with PyPy.

          
      sqs = []
      # determine possible squares
      for m, M  in ((299, 498), (2999, 4998)):
        sqs += [x2 for x in range(int(m**.5), int(M**.5) + 1) 
                if m <= (x2 := x * x) <= M and x2 % 10 not in {2, 3, 4, 5}]
      
      # PINs differ from a square by 3 or 4 and are divisible by 3 or 4
      pins = [(str(pin), sq) for sq in sqs for k in [-4, -3, 3, 4] 
              if all(str(pin := sq + k)[i] in "34" for i in [-1, 0]) and
                 sum([pin % 3 == 0, pin % 4 == 0]) == 1]
      
      # the lower guess is true in 3 or 4 cases 
      pins = [(p, r) for (p, sq) in pins 
              if sum(r := [p[0] == '4', 
                     p[-1] == '4', 
                     len(p) == 4, 
                     int(p) % 4 == 0,
                     abs(sq - int(p)) == 4]) in {1, 2}] 
                     
      # 3 or 4 possibilities for my PIN
      if len(pins) not in {3, 4}: 
        print("no solution")
        exit(0)                   
      
      # create matrix for guesses
      mat = [s[1] for s in pins]
      tmat = list(zip(*mat))        # transpose matrix
      
      # remove PINs that have an unique answer for a clue
      pins = [n for (n, r) in pins if all(tmat[i].count(r[i]) > 1 for i in range(5))]
      
      if len(pins) != 1: 
        print("no unique solution")
        exit(0) 
        
      print("answer:", *pins)
      

      Like

    • NigelR's avatar

      NigelR 10:07 am on 31 December 2023 Permalink | Reply

      is_square = lambda n: round(n**(0.5))**2 == n
      #conditions return 'l' for lower value (3), 'u' for upper (4), 'f' for neither
      g1 = lambda n: 'l' if len((ns:=str(n)))==3 else ('u' if len(ns)==4 else 'f')
      g2 = lambda n: 'l' if (ns:=str(n))[0]=='3' else ('u' if ns[0]=='4' else 'f')
      g3 = lambda n: 'l' if (ns:=str(n))[-1]=='3' else ('u' if ns[-1]=='4' else 'f')
      g4 = lambda n: 'f' if n%12==0 else ('l' if n%3==0 else ('u' if n%4==0 else 'f'))
      g5 = lambda n: 'l' if is_square(n-3) or is_square(n+3) else('u' if is_square(n-4) or is_square(n+4) else 'f')
      
      tests = (g1,g2,g3,g4,g5)
      res={}
      #test either side of square numbers between 300 and 5000
      for i in range(18,71):
          for pin in [(isq:= i*i)+3,isq-3,isq+4,isq-4]:
              #use loop not comprehension to allow break on first invalid test
              outc=[]
              for test in tests:
                  if (r:=test(pin))=='f':break 
                  else: outc.append(r)
              #all tests must pass and  3 or 4 lower guesses are true
              if len(outc)!=5 or outc.count('l') not in {3,4}: continue        
              else: res[pin] = outc  #current value of pin is valid
      #remove candidate PINs with unique correct guesses
      guesses = [[res[x][y] for x in res] for y in range(5)]
      soltn = [x for x in res if not [j for i,j in enumerate(guesses) if j.count(res[x][i])==1]]
      if len(soltn)!=1: print('no unique solution found')
      else: print('PIN is',soltn[0])
      

      Like

  • Unknown's avatar

    Jim Randell 4:41 pm on 22 December 2023 Permalink | Reply
    Tags:   

    Teaser 3196: Mind the edge 

    From The Sunday Times, 24th December 2023 [link] [link]

    Kate and Lucy play a pub game on an isosceles triangle table top. They take turns to push a penny from the centre of the table top’s base towards the triangle’s apex, 120cm distant, scoring the sum of their distances from the base, or zero if it ever falls off the table.

    Each player aims to maximise their distance, avoiding the chance that the penny might fall off the table. Their penny has an equal chance of landing anywhere within an error radius around the aimed point. This radius is proportional to the distance aimed. As a skilled player Lucy’s error ratio is half of Kate’s.

    They both expect to score a whole number of cm per push, but to give each an equal chance of winning Kate gets one more push than Lucy. This number of pushes is the largest possible, given the above information.

    How many pushes complete a game between the two?

    Happy Christmas from S2T2!

    For my selection of the more interesting puzzles encountered in 2023 see 2023 in review on Enigmatic Code.

    [teaser3196]

     
    • Jim Randell's avatar

      Jim Randell 5:15 pm on 22 December 2023 Permalink | Reply

      If I understand the puzzle correctly it works like this:

      Suppose the integer distances they are aiming for are L and K (where 0 < K < L < 120), and if L has n turns (so K has (n + 1) turns), then we have:

      n L = (n + 1) K
      ⇒ n = K / (L − K)

      so n is a divisor of K.

      And if L’s target circle has radius r(L):

      r(L) = k L

      for some constant k.

      And K’s circle is given by:

      r(K) = 2k K

      And if θ is half the angle at the apex of the triangle we have:

      sin(θ) = r(L) / (120 − L)
      sin(θ) = r(K) / (120 − K)

      (120 − K) L = 2 K (120 − L)
      K L = 120 (2K − L)
      L = 240 K / (120 + K)

      This Python program finds possible K, L, n values, and looks for maximal n values.

      It run in 55ms (and has an internal runtime of 110µs).

      Run: [ @replit ]

      from enigma import (Accumulator, irange, div, printf)
      
      # find maximum number of goes
      r = Accumulator(fn=max, collect=1)
      
      # consider distance K
      for K in irange(1, 118):
        # calculate L
        L = div(240 * K, 120 + K)
        if L is None or not (L < 120): continue
        # calculate n
        n = div(K, L - K)
        if n is None: continue
        # record maximal n
        printf("[K={K} L={L} n={n}]")
        r.accumulate_data(n, (K, L))
      
      # output solution
      n = r.value
      for (K, L) in r.data:
        printf("K={K} L={L} n={n} -> {t} turns", t=2 * n + 1)
      

      Solution: The total number of turns in the game is 31.

      L has 15 turns, and K has 16.

      Like

    • Frits's avatar

      Frits 11:56 am on 25 December 2023 Permalink | Reply

         
      from enigma import divisors
       
      # consider the distance for Lucy
      for L in range(119, 1, -1):
        # Lucy has n pushes, n = 120 / (120 - L), K = 120L / (240 - L)
        if (120 - L) not in divisors(120) or (120 * L) % ((240 - L)): continue
        
        print(f"answer: {240 // (120 - L) + 1} pushes")
        break # maximum reached
      

      Like

      • Jim Randell's avatar

        Jim Randell 9:24 am on 26 December 2023 Permalink | Reply

        @Frits: A very compact and efficient approach (only 8 cases are considered).

        Here’s a version that prints a bit more information:

        Run: [ @replit ]

        from enigma import (irange, div, printf)
        
        # find values of n in decreasing order
        for L in irange(119, 2, step=-1):
          K = div(120 * L, 240 - L)
          if K is None: continue
          n = div(120, 120 - L)
          if n is None: continue
          # output solutions
          printf("L={L} -> K={K} n={n} -> {t} turns", t=2 * n + 1)
          break  # only need largest n
        

        Like

  • Unknown's avatar

    Jim Randell 7:59 am on 21 December 2023 Permalink | Reply
    Tags: by: Anthony Isaacs   

    Brainteaser 1457: All square 

    From The Sunday Times, 12th August 1990 [link]

    Your task this week is to place a non-zero digit in each of the 16 squares shown:

    When you’ve finished each of the four rows should read across as a four-figure perfect square, and each of the four columns should read down as a four-figure perfect square.

    What is the sum of the 16 digits?

    [teaser1457]

     
    • Jim Randell's avatar

      Jim Randell 8:00 am on 21 December 2023 Permalink | Reply

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      #  A B C D
      #  E F G H
      #  I J K L
      #  M N P Q
      
      --digits="1-9"
      --distinct=""
      
      # rows read as squares
      "is_square(ABCD)"
      "is_square(EFGH)"
      "is_square(IJKL)"
      "is_square(MNPQ)"
      
      # columns read as squares
      "is_square(AEIM)"
      "is_square(BFJN)"
      "is_square(CGKP)"
      "is_square(DHLQ)"
      
      # [optional] squares must end in 1, 4, 5, 6, 9
      --invalid="2|3|7|8,DHLMNPQ"
      
      --answer="sum([A, B, C, D, E, F, G, H, I, J, K, L, M, N, P, Q])"
      --template=""
      

      Solution: The sum of the 16 digits is 56.

      The completed grid is as follows:

      Like

    • GeoffR's avatar

      GeoffR 6:21 pm on 21 December 2023 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % A B C D
      % E F G H
      % I J K L
      % M N P Q
      
      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; var 1..9:J; 
      var 1..9:K; var 1..9:L; var 1..9:M; var 1..9:N; var 1..9:O; 
      var 1..9:P; var 1..9:Q; 
      
      var 16..108:total; % UB = 2*45 + 18
      set of int: sq4 = {n*n | n in 34..96}; 
      % 97,98,99 have zeroes in squares, as do 32 and 33
      
      % ROWS
      var 1156..9216:ABCD == 1000*A + 100*B + 10*C + D;
      var 1156..9216:EFGH == 1000*E + 100*F + 10*G + H;
      var 1156..9216:IJKL == 1000*I + 100*J + 10*K + L;
      var 1156..9216:MNPQ == 1000*M + 100*N + 10*P + Q;
      % COLUMNS
      var 1156..9216:AEIM == 1000*A + 100*E + 10*I + M;
      var 1156..9216:BFJN == 1000*B + 100*F + 10*J + N;
      var 1156..9216:CGKP == 1000*C + 100*G + 10*K + P;
      var 1156..9216:DHLQ == 1000*D + 100*H + 10*L + Q;
       
      % Last digit of squares is 1,4,5,6 or 9.
      set of int: dig_end = {1,4,5,6,9};
      constraint sum([D in dig_end, H in dig_end, L in dig_end, Q in dig_end]) == 4;
      constraint sum([M in dig_end, N in dig_end, P in dig_end]) == 3;
      
      constraint sum([ABCD in sq4, EFGH in sq4, IJKL in sq4, MNPQ in sq4,
      AEIM in sq4, BFJN in sq4, CGKP in sq4, DHLQ in sq4]) = 8; 
      
      constraint total = A+B+C+D+E+F+G+H+I+J+K+L+M+N+P+Q;
      solve satisfy;
      
      output ["Total of all digits = " ++ show(total) 
      ++ "\n" ++ show(ABCD) ++ "\n" ++ show(EFGH)
      ++ "\n" ++ show(IJKL) ++ "\n" ++ show(MNPQ)];
      
      % Total of all digits = 56
      % 2116
      % 1225
      % 1296
      % 6561
      % ----------
      % ==========
      % Finished in 442msec.
      
      

      Like

    • Frits's avatar

      Frits 12:58 pm on 22 December 2023 Permalink | Reply

      Using permutations is a lot slower (1 second with PyPy).

         
      from itertools import permutations
      
      sqs =  {x2 for x in range(32, 100) if '0' not in (x2 := str(x * x))}
      
      for hor in permutations(sqs, 4):
        # check columns, transpose the permutation to get the columns
        if any(''.join(x) not in sqs for x in (zip(*hor))): continue
        for h in hor: print(h)
        print("\nanswer:", sum(int(d) for h in hor for d in h))
      

      Like

  • Unknown's avatar

    Jim Randell 8:30 am on 19 December 2023 Permalink | Reply
    Tags:   

    Brainteaser 1087: Pennies from heaven 

    From The Sunday Times, 5th June 1983 [link]

    In our club we have three one-armed bandits. The Saturn Skyjacker accepts 10p, 2p and 1p coins, the Mars Marauder accepts 10p and 1p coins, and the Aries Axeman accepts 5p and 2p coins.

    I am the club treasurer, so each week I have the onerous task of emptying the machines and counting the coins. Last week, my efforts were rewarded with the discovery of an interesting series of coincidences. On counting the coins for the Saturn Skyjacker, I found that there were the same number of coins of two of the denominations, and that the number of coins of the third denomination differed from this number by only one. In addition, the total value of all the coins was an exact number of pounds less than one hundred.

    The coins from the Mars Marauder were similarly distributed: the numbers of 10p and 1p coins differed by only one, and the total value was again an exact number of pounds. In fact, this total value was the same as for the Saturn Skyjacker.

    Incredibly, the same was true for coins from the Aries Axeman: the numbers of 5p and 2p coins differed by one, and the total value was the same as for the Mars Marauder and the Saturn Skyjacker.

    What was the total number of coins I emptied that day?

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

    [teaser1087]

     
    • Jim Randell's avatar

      Jim Randell 8:31 am on 19 December 2023 Permalink | Reply

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

      Run: [ @replit ]

      from enigma import (irange, div, cproduct, printf)
      
      # decompose t into k * x and (k +/- 1) * y
      def decompose(t, x, y):
        k = div(t - y, x + y)
        if k: yield (k, k + 1)
        k = div(t + y, x + y)
        if k: yield (k, k - 1)
      
      # total number of pounds is less than 100
      for t in irange(100, 9900, step=100):
        # can we make this total for (10, 1) and (5, 2)
        for (m, a) in cproduct([decompose(t, 10, 1), decompose(t, 5, 2)]):
          # try to make t from (10, 2, 1) by combining 2 of the values
          for (x, y) in [(12, 1), (11, 2), (3, 10)]:
            for s in decompose(t, x, y):
              # calculate total number of coins
              n = sum(m) + sum(a) + sum(s) + s[0]
              # output solution
              printf("{n} coins [t={t}: m={m} a={a} -> x={x} y={y} s={s}]")
      

      Solution: The total number of coins is: 3001.

      In the Saturn Skyjacker there were 331× 10p + 330× 2p + 330× 1p = 4300p.

      In the Mars Marauder there were 391× 10p + 390× 1p = 4300p.

      In the Aries Axman there were 614× 5p + 615× 2p = 4300p.

      So in total there were 3001 coins totalling £129.

      Like

    • Frits's avatar

      Frits 2:48 pm on 19 December 2023 Permalink | Reply

       
      # coins (pennies) for Saturn Skyjacker, the Mars Marauder and the Aries Axeman
      cs = [{1, 2, 10}, {1, 10}, {2, 5}]
      
      # total number of pounds is less than 100
      for t in range(100, 10000, 100):
        # remainder by sum of coin denominations should be equal to one of the 
        # coin denominations or equal to the sum of all coin denominations but one
        if all((t % sum(c)) in c | {sum(c) - x for x in c} for c in cs):
          print("answer:", sum(len(c) * (t // sum(c)) + 
                (1 if (t % sum(c)) in c else len(c) - 1) for c in cs))  
      

      Like

    • GeoffR's avatar

      GeoffR 8:36 pm on 27 December 2023 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Saturn Skyjacker coins are 1p, 2p and 10p
      var 1..9999:SS;   % maximum Saturn Skyjacker total(p)
      
      % Assumed UB for SS, MM and AA coin numbers
      var 1..1000:s10; var 1..1000:s2; var 1..1000:s1; 
      
      % Two coins hae the same number, differing fron the other denomination by 1
      constraint (s10 == s2 /\ abs(s10 - s1) == 1)
      \/ (s10 == s1 /\ abs(s10 - s2) == 1)
      \/ (s1 == s2 /\ abs(s1 - s10) == 1);
      
      constraint s1*1 + s2*2 + s10*10 == SS;
      constraint SS div 100 < 100 /\ SS mod 100 == 0;
      
      % Mars Marauder coins 1p and 10p
      var 1..9999:MM; % maximum Mars Marauder total(p)
      var 1..1000:m1; var 1..1000:m10;
      
      % Mars Marauder coin numbers differ by 1
      constraint abs(m1 - m10) == 1;
      constraint MM = m1 * 1  + m10 * 10;
      constraint MM div 100 < 100 /\ MM mod 100 == 0;
      
      % Aries Axem coins are 2p and 5p
      var 1..9999:AA; % maximum Aries Axem  total(p)
      var 1..1000:a2; var 1..1000:a5;
      
      % Aries Axem coin numbers differ by 1
      constraint abs(a2 - a5) == 1;
      constraint AA = a2 * 2  + a5 * 5;
      constraint AA div 100 < 100 /\ AA mod 100 == 0;
      
      % Total amount from three machines is the same
      constraint MM == SS /\ MM == AA;
      
      % Total number of coins
      var 1..7000:tot_coins == s1 + s2 + s10 + m1 + m10 + a2 + a5;
      
      solve satisfy;
      
      output[" Total number of coins = " ++ show(tot_coins) ++ "\n" ++
      " Saturn coins are s1, s2, s10 = " ++ show([s1, s2, s10] ) ++ "\n" ++
      " Mars coins m1 and m10 = " ++ show([m1, m10]) ++ "\n" ++
      " Aries coins are a2 and a5 = " ++ show([a2, a5]) ++ "\n" ++
      " Total value in each machine = £" ++ show(SS div 100) ];
      
      %  Total number of coins = 3001
      %  Saturn coins are s1, s2, s10 = [330, 330, 331]
      %  Mars coins m1 and m10 = [390, 391]
      %  Aries coins are a2 and a5 = [615, 614]
      %  Total value in each machine = £43
      % ----------
      % ==========
      % Finished in 506msec.
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:27 pm on 18 December 2023 Permalink | Reply  

    Unplanned outage 

    Apologies for the unavailability of S2T2 between 16th December 2023 and 18th December 2023. The site was accidentally suspended by WordPress.com’s automated systems, but has now been reinstated.

    Normal service will now resume.

     
    • NigelR's avatar

      NigelR 5:18 pm on 19 December 2023 Permalink | Reply

      Great to see you back, Jim, and thanks for all the time you put into this site – I’d be lost without it!

      Like

  • Unknown's avatar

    Jim Randell 4:44 pm on 15 December 2023 Permalink | Reply
    Tags:   

    Teaser 3195: Garden divide 

    From The Sunday Times, 17th December 2023 [link] [link]

    I have a triangular-shaped garden, the sides of which are an exact number of feet long. To improve its usefulness, I’ve decided to partition it by building a straight fence from one corner to the centre of the opposite side. The length of this fence is exactly 51 feet and the side it attaches to is now 26 feet long each side of the fence.

    What, in ascending order, are the lengths of the other two sides of my garden?

    [teaser3195]

     
    • Jim Randell's avatar

      Jim Randell 5:05 pm on 15 December 2023 Permalink | Reply

      We can use the cosine rule to calculate the missing sides in terms of the cosine of their opposite angles, which are supplementary (i.e. their sum is 180°).

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

      Run: [ @replit ]

      from enigma import (irange, sq, is_square, printf)
      
      # the fence is length: a
      # and divides the base of the triangle into: b + b = 2b
      (a, b) = (51, 26)
      
      # by the cosine rule we have:
      #
      # x^2 = a^2 + b^2 - 2.a.b.cos(theta)
      # y^2 = a^2 + b^2 + 2.a.b.cos(theta)
      
      # consider increasing squares for x
      z = sq(a) + sq(b)
      for x in irange(a - b + 1, a + b - 1):
        # calculate the 2.a.b.cos(theta) term
        t = z - sq(x)
        # calculate y
        y = is_square(z + t)
        if y is None or y < x: continue
        # output solution
        printf("x={x} y={y}")
      

      Solution: The other two sides of the garden are: 35ft and 73ft.

      Like

      • Frits's avatar

        Frits 6:18 pm on 15 December 2023 Permalink | Reply

         
        # get 2 squares that sum up to n (with minimum m)
        # https://stackoverflow.com/questions/72093402/sum-of-two-squares-in-python
        def sum_of_two_squares(n, m):
          i = m
          j = int(n ** (1/2))
        
          while i < j:
            x = i * i + j * j
            if x == n:
              yield i, j
        
            if x < n:
              i += 1
            else:
              j -= 1
              
        # by the cosine rule we have:
        #
        # x^2 = a^2 + b^2 - 2.a.b.cos(t)
        # y^2 = a^2 + b^2 + 2.a.b.cos(t)
        
        # so x^2 + y^2 = 2 * (a^2 + b^2)
              
        (a, b) = (51, 26)      
        
        for xy in sum_of_two_squares(2 * (a**2 + b**2), b):
          print("answer:", xy)  
        

        Like

        • Jim Randell's avatar

          Jim Randell 8:33 pm on 15 December 2023 Permalink | Reply

          In fact there is a [[ sum_of_squares() ]] function already in enigma.py.

          from enigma import (sum_of_squares, sq, printf)
          
          # the fence is length: a
          # and divides the base of the triangle into: b + b = 2b
          (a, b) = (51, 26)
          
          # x^2 + y^2 = 2(a^2 + b^2)
          for (x, y) in sum_of_squares(2 * (sq(a) + sq(b)), 2):
            if a - b < x and y < a + b:
              printf("x={x} y={y}")
          

          Like

    • GeoffR's avatar

      GeoffR 10:43 am on 16 December 2023 Permalink | Reply

      # Central fence length (a) and given half side of triangle (b)
      a, b = 51, 26 
      
      # Other two sides of triangle are x and y
      # Cosine rule gives:  x^2 + y^2 = 2 * (a^2 + b^2)
      
      # Max side of other two triangle sides < 51 + 26 i.e. < 77
      # Make x the smallest unknown triangle side
      for x in range(5, 77):
        for y in range(x+1, 77):
          # triangle side constraints
          if not x < (a + b):continue
          if not y < (a + b):continue
          if x * x + y * y == 2 * a**2 + 2 * b**2:
             print(f"Other two sides of garden are {x}ft. and {y}ft.")
      
      

      Without the triangle constraints there is another integer solution to the equation x^2 + y^2 = 2 * (a^2 + b^2). This is x = 25 and y = 77. This would make the overall triangle sides (25,52,77), which is not possible, so this cannot be the teaser answer.

      Like

  • Unknown's avatar

    Jim Randell 8:38 am on 14 December 2023 Permalink | Reply
    Tags: ,   

    Teaser 2665: Milky ways 

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

    Each evening Pat drives his herd of cows into the milking parlour. The cows are ear-tagged 1, 2, 3, … and the parlour is divided into stalls also numbered 1, 2, 3, …, with one stall for each cow. The cows file in and choose empty stalls at random until all the stalls are full. Pat has noticed that very often it happens that no cow occupies a stall with the same number as her tag. Pat worked out the number of different ways this could happen, and also the number of ways that at least one cow could be in a stall with the same number as herself. The two answers were less than a million and Pat noticed that the sum of the digits in each was the same.

    How many cows are in the herd?

    [teaser2665]

     
    • Jim Randell's avatar

      Jim Randell 8:39 am on 14 December 2023 Permalink | Reply

      (See also: Teaser 2995, Teaser 2779).

      This Python program runs in 59ms. (Internal runtime is 139µs).

      Run: [ @replit ]

      from enigma import (irange, inf, factorial, subfactorial, dsum, printf)
      
      # consider 3 or more cows
      for k in irange(3, inf):
        # calculate the number of possible arrangements
        n = factorial(k)
        # calculate the number of derangements
        d = subfactorial(k)
        # and the number of arrangements that are not derangements
        r = n - d
        if not (max(d, r) < 1000000): break
        # calculate digit sum
        if dsum(d) == dsum(r):
          # output solution
          printf("k={k}: n={n} d={d} r={r}")
      

      Solution: There were 7 cows in the herd.

      The cows can be arranged in factorial(7) = 5040 different ways.

      Of these subfactorial(7) = 1854 are derangements.

      And the remaining 3186 are not derangements, so at least one cow must have the same number as the stall it is in.

      And dsum(1854) = dsum(3186) = 18.


      If we allow numbers over 1 million, then there are further solutions at k = 46, 52, 94, 115, 124, 274, 313, 346, …

      Like

  • Unknown's avatar

    Jim Randell 3:47 pm on 12 December 2023 Permalink | Reply
    Tags:   

    Teaser 2658: Different views 

    From The Sunday Times, 1st September 2013 [link] [link]

    Oliver arranged six [identical standard] dice in a neat pile with three in the bottom row, two in the middle row and one at the top. The faces of these dice were digits rather than the corresponding number of dots. Looking down on them, Beth saw that the five partially-visible tops of the dice contained different digits. In the three rows at the front she saw 1-digit, 2-digit and 3-digit primes, whereas from the back she saw three perfect squares. On the left, working down the three sides, she saw a 3-digit square whereas on the right, again working down, she saw a 3-digit prime.

    What was this 3-digit prime?

    [teaser2658]

     
    • Jim Randell's avatar

      Jim Randell 3:47 pm on 12 December 2023 Permalink | Reply

      We need to make some additional assumptions about the dice in order to arrive at a unique solution.

      I assumed that the dice are “standard”, i.e. each has the digits 1-6 on, and they are arranged such that opposite faces sum to 7, and the numbers 1, 2, 3 are arranged anti-clockwise around one of the vertices.

      I used the [[ Cube() ]] class (originally written for Teaser 2835) to generate all possible rotations of a standard die, and then used the [[ SubstitutedExpression ]] solver from the enigma.py library to solve the puzzle.

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

      #! python3 -m enigma -rr
      
      #            C
      #         +-----+
      #      B S| K/R |V D
      #      +--+--+--+--+
      #   A T| I/Q | J/P |W E
      #   +--+--+--+--+--+--+
      #  U| F/N | G/M | H/L |X
      #   +-----+-----+-----+
      #
      #  top   = ABCDE
      #  front = FGH, IJ, K
      #  back  = LMN, PQ, R
      #  left  = STU
      #  right = VWX [= answer]
      
      SubstitutedExpression
      
      --digits="1-6"
      
      # visible top faces are all different
      # faces of each dice are different
      --distinct="ABCDE,AFNU,BIQT,CKRSV,DJPW,EHLX,GM"
      
      # visible front faces form primes
      "is_prime(FGH)"
      "is_prime(IJ)"
      "is_prime(K)"
      
      # visible back faces form squares
      "is_square(LMN)"
      "is_square(PQ)"
      "is_square(R)"
      
      # left (top to bottom) forms a 3-digit square
      "is_square(STU)"
      
      # right (top to bottom) forms a 3-digit prime
      "is_prime(VWX)"
      
      # answer is the 3 digit prime on the right
      --answer="VWX"
      
      # additional checks for standard dice
      --code="from cube import Cube"
      --code="dice = set()"
      --code="dice.update(r.faces for r in Cube(faces=(1, 6, 2, 5, 3, 4)).rotations())" # standard die
      --code="is_die = lambda *fs: any(all(x == 0 or x == y for (x, y) in zip(fs, die)) for die in dice)"
      
      # perform check on the six (partial) dice
      "is_die(A, 0, F, N, U, 0)"
      "is_die(B, 0, I, Q, T, 0)"
      "is_die(C, 0, K, R, S, V)"
      "is_die(D, 0, J, P, 0, W)"
      "is_die(E, 0, H, L, 0, X)"
      "is_die(0, 0, G, M, 0, 0)"
      
      --template="(A, 0, F, N, U, 0) (B, 0, I, Q, T, 0) (C, 0, K, R, S, V) (D, 0, J, P, 0, W), (E, 0, H, L, 0, X) (0, 0, G, M, 0, 0)"
      --solution=""
      --verbose="THA"
      --denest
      

      Solution: The prime when the pile is viewed from the right is: 523.

      From the top the faces form: 31642 (all digits different).

      From the front the faces form: 3, 31, 251 (prime numbers).

      From the back the faces form: 4, 64, 625 (square numbers).

      From the left (top-to-bottom) the faces form: 256 (a square number).

      From the right (top-to-bottom) the faces form: 523 (a prime number).


      The problem can also be solved with six identical dice where the numbers 1, 2, 3 are arranged clockwise around one of the vertices (i.e. “mirror image” dice), and we get the same result.

      But if we are allowed to mix these two types of dice then we can get an additional answer of 653.

      And without the additional constraints (i.e. allowing dice where the digits 1-6 appear in any pattern) we can find many possible solutions.

      Like

    • Frits's avatar

      Frits 2:03 pm on 13 December 2023 Permalink | Reply

       
      from itertools import product
      
      # get rid of numbers with invalid digits 0,7,8 and 9 or 
      # with digits occuring more than once
      cleanup = lambda s: {x for x in s if len(set(str(x))) == len(str(x)) and
                           not any(d in str(x) for d in '7890')}
      oppsides = lambda n: [n, str(7 - int(n))]      
      
      # given two dice faces anti-clockwise at a vertex, find the third
      # face anti-clockwise at this vertex (western die if same is true)
      def die_third_face(first, second, same=False):
        # credit: B. Gladman
        if second in (first, 7 - first):
          raise ValueError
        t, f = min(first, 7 - first), min(second, 7 - second)
        c1 = ((f - t) % 3 == (1 if same else 2))
        c2 = (first < 4) == (second < 4)
        return 6 - t - f if c1 == c2 else t + f + 1
      
      # determine valid primes up to 1000
      P = {3, 5, 7}
      P |= {x for x in range(11, 100, 2) if all(x % p for p in P)}
      P |= {2} | {x for x in range(101, 1000, 2) if all(x % p for p in P)}
      P = cleanup(P)
      
      # determine valid squares up to 1000
      sq = cleanup(x * x for x in range(1, 32))
      sq3 = [str(x) for x in sq if x > 99]
      pr3 = [str(x) for x in P if x > 99]
      
      # valid prime/square combinations
      cands = {ln: [(p, s) for s in sq  
               if len(st := str(s)) == ln and 
               (p := (7 * int('111'[:ln]) - int(st[::-1]))) in P and
               all(len(set(str(x))) == len(st) for x in (s, p))] for ln in (1, 2, 3)}         
      
      # check all possible combinations
      for (pt, st), (pm, sm), (pb, sb) in product(*cands.values()):
        # filter 3-digit squares to have different digits from front and back faces
        for lft in [s for s in sq3 if all(s[i] not in 
                   oppsides(str((pt, pm, pb)[i])[0]) for i in range(3))]:
          # filter 3-digit primes to have correct hundreds digit
          rghts = [x for x in pr3 if x[0] == str(7 - int(lft[0]))]
          # filter 3-digit primes to have different digits from front and back faces
          for rght in [r for r in rghts if all(r[i] not in 
                       oppsides(str((st, sm, sb)[i])[0]) for i in range(3))]:
            # all visible top faces (left to right) could be seen to be different
            tp1 = die_third_face(int(lft) % 10, pb // 100)
            tp2 = die_third_face((int(lft) % 100) // 10, pm // 10)
            tp3 = die_third_face(pt % 10, int(rght) // 100)
            tp4 = die_third_face(pm % 10, (int(rght) % 100) // 10)
            tp5 = die_third_face(pb % 10, int(rght) % 10)
            if len({tp1, tp2, tp3, tp4, tp5}) == 5:
              print("answer:", rght)  
      

      Like

  • Unknown's avatar

    Jim Randell 4:37 pm on 8 December 2023 Permalink | Reply
    Tags:   

    Teaser 3194: A proper lesson 

    From The Sunday Times, 10th December 2023 [link] [link]

    A maths teacher wrote a sequential list of numbers 1, 2, 3, 4, … on the blackboard and asked her pupils to find a pair of positive fractions adding up to 1. The pair was to have the two numerators and two denominators consisting of four different numbers from her list. They found all possible pairs.

    She then told them to group two of the pairs that used eight different numbers from her list, which was just long enough to enable them to do this. The class found all possible groups. One of the groups contained some fractions that were not used by any other group.

    Which of the teacher’s numbers were not used by that group?

    [teaser3194]

     
    • Jim Randell's avatar

      Jim Randell 4:56 pm on 8 December 2023 Permalink | Reply

      This Python program considers increasing maximum values written on the blackboard, and collects new pairs of fractions as they become available, until it is possible to form groups consisting of 2 pairs that use 8 different numbers in the fractions.

      It then examines the groups found to determine which of them contain fractions that only occur in that group, and these groups provide the answer.

      It runs in 53ms. (Internal runtime is 519µs).

      Run: [ @replit ]

      from enigma import (
        irange, inf, fraction, multiset, subsets, flatten, diff, chunk, join, sprintf as f, printf
      )
      
      # format a list of numbers as fraction sums
      fmt = lambda ns: join((f("{a}/{b} + {c}/{d} = 1") for (a, b, c, d) in chunk(ns, 4)), sep="; ")
      
      # collect possible pairs of fractions a/b + c/d = 1
      ps = list()
      # consider increasing 1..n values
      for n in irange(4, inf):
        np = len(ps)
        # add in a/b, c/n pairs
        for c in irange(1, n - 1):
          (a, b) = (p, q) = fraction(1, 1, -c, n)
          while b < n:
            ns = (a, b, c, n)
            if len(set(ns)) == 4:
              ps.append(ns)
            a += p
            b += q
        if n < 8 or len(ps) == np: continue
      
        # find possible groups, and count occurrences of fractions
        (gs, m) = (list(), multiset())
        for ns in subsets(ps, size=2, fn=flatten):
          if len(set(ns)) == 8:
            printf("[n={n}: {ns}]", ns=fmt(ns))
            gs.append(ns)
            m.update_from_seq(chunk(ns, 2))
        if not gs: continue
      
        # find groups that contain fractions that only occur in one group
        for ns in gs:
          if any(m.count(x) == 1 for x in chunk(ns, 2)):
            # output solution
            ans = diff(irange(1, n), ns)
            printf("unused = {ans} [{ns}]", ans=join(ans, sep=", ", enc="()"), ns=fmt(ns))
      
        # we only need the smallest n that forms groups
        break
      

      Solution: The unused numbers are 7 and 8.


      The teacher wrote the numbers 1 .. 10 on the board.

      This gives 17 possible pairs of fractions that sum to 1:

      1/2 + 3/6 = 1
      2/4 + 3/6 = 1
      1/3 + 4/6 = 1
      3/4 + 2/8 = 1
      1/2 + 4/8 = 1
      3/6 + 4/8 = 1
      1/4 + 6/8 = 1
      4/6 + 3/9 = 1
      1/3 + 6/9 = 1
      4/5 + 2/10 = 1
      3/5 + 4/10 = 1
      1/2 + 5/10 = 1
      2/4 + 5/10 = 1
      3/6 + 5/10 = 1
      4/8 + 5/10 = 1
      2/5 + 6/10 = 1
      1/5 + 8/10 = 1

      Note that fractions do not have to be in their lowest terms, so 1/2 and 3/6 are considered to be different fractions (with the same value).

      These can be formed into 9 groups that use 8 distinct numbers in the fractions:

      1/2 + 3/6 = 1; 4/8 + 5/10 = 1
      2/4 + 3/6 = 1; 1/5 + 8/10 = 1
      1/2 + 4/8 = 1; 3/6 + 5/10 = 1
      3/6 + 4/8 = 1; 1/2 + 5/10 = 1
      4/6 + 3/9 = 1; 1/2 + 5/10 = 1
      4/6 + 3/9 = 1; 1/5 + 8/10 = 1
      1/3 + 6/9 = 1; 4/5 + 2/10 = 1 [*]
      1/3 + 6/9 = 1; 2/4 + 5/10 = 1
      1/3 + 6/9 = 1; 4/8 + 5/10 = 1

      And it is not possible to form any groups using 1 .. 8 or 1 .. 9, so 1 .. 10 is the smallest set of initial numbers on the blackboard.

      Of the fractions used in these groups, only 4/5 and 2/10 appear in only one group [*], and this group is:

      1/3 + 6/9 = 1; 4/5 + 2/10 = 1

      And so the 2 numbers on the blackboard that are not used in any of these four fractions are 7 and 8.

      Like

    • Frits's avatar

      Frits 10:50 am on 9 December 2023 Permalink | Reply

       
      # numbers 1, 2, 3, ..., n, we need 2 groups with in total 8 different numbers
      n = 8   
      while True:
        # determine numbers (a, b, c, d) do that a/b + c/d = 1 with c > a
        abcd = set((a, b, c, d)
                   for a in range(1, n // 2 + n % 2)
                   for b in range(a + 1, n + 1)
                   for c in range(a + 1, n + 1)
                   if c != b and \
                   not (dm := divmod(b * c, b - a))[1] and \
                   c < (d := dm[0]) <= n and d != b)
        
        # find 2 groups of <abcd> entries that use 8 different numbers among them
        two_groups = [(s1[:2], s1[2:], s2[:2], s2[2:]) for s1 in abcd for s2 in abcd
                      if s1[0] < s2[0] and len(set(s1 + s2)) == 8]  
        
        if not two_groups: 
          n += 1   # try numbers 1, 2, 3, ..., n
          continue
        
        # one of the groups contained some fractions 
        # that were not used by any other group
        all_fractions = [f for g2 in two_groups for f in g2]
        unique_fractions = {f for f in all_fractions if all_fractions.count(f) == 1}
        
        for g2 in two_groups:
          # does a fraction in <g2> only occur in our <g2>?
          if any(f for f in g2 if f in unique_fractions):
            used = set(y for x in g2 for y in x)
            print(f"unused numbers: "
                  f"{[i for i in range(1, n + 1) if i not in used]}")
                  
        break  
      

      Like

      • Frits's avatar

        Frits 12:36 pm on 9 December 2023 Permalink | Reply

        Slower but more compact.

           
        from itertools import permutations
        n = 8 
        while True:
          # determine different numbers (a, b, c, d, e, f, g, h) so that 
          # a/b + c/d = 1 and e/f + g/h = 1 with c > a, g > e and a < e
          abcdefgh = [((a, b), (c, d), (e, f), (g, h)) 
                       for a, b, c, d, e, f, g, h in permutations(range(1, n + 1), 8)
                       if a < b and c < d and e < f and g < h and 
                       a < c and e < g and a < e and 
                       a * d + b * c == b * d and e * h + f * g == f * h]
          
          if not abcdefgh: 
            n += 1   # try numbers 1, 2, 3, ... for a higher n
            continue
          
          # one of the groups contained some fractions 
          # that were not used by any other group
          all_fractions = [fr for f4 in abcdefgh for fr in f4]
          unique_fractions = {f for f in all_fractions if all_fractions.count(f) == 1}
          
          for f4 in abcdefgh:
            # does a fraction in <f4> not occur in any other <abcdefg> entry?
            if any(f for f in f4 if f in unique_fractions):
              used = set(y for x in f4 for y in x)
              print(f"unused numbers: "
                    f"{[i for i in range(1, n + 1) if i not in used]}")
                    
          break  # we are done
        

        Like

  • Unknown's avatar

    Jim Randell 10:58 am on 5 December 2023 Permalink | Reply
    Tags:   

    Teaser 2657: Puzzling book 

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

    George and Martha have a book of puzzles numbered from 1 to 30. The solutions are also numbered from 1 to 30, but a solution number is not necessarily the same as the number of the puzzle. George and Martha have solved some of the puzzles. If you look at the number of the puzzle and the number of the solution, then the sum of the two is a perfect power of the difference of the two. George has added up the numbers of the solved puzzles and got a three-figure total. Martha has added up the numbers of the solutions of the solved puzzles and got a higher three-figure total. In fact her total used the same nonzero digits as George’s, but in a different order.

    What (in increasing order) are the numbers of the solved puzzles?

    [teaser2657]

     
    • Jim Randell's avatar

      Jim Randell 10:59 am on 5 December 2023 Permalink | Reply

      This Python program generates possible (puzzle-number, solution-number) pairs, and then searches for a collection of these pairs that satisfies the requirements.

      It runs in 66ms. (Internal runtime is 11ms).

      Run: [ @replit ]

      from enigma import (irange, subsets, is_power_of, unpack, nsplit, printf)
      
      # find puzzles <ps>, solutions <ss> from <pairs>, sum(<ps>) = <tp>, sum(<ss>) = <ts>
      def solve(pairs, ps=[], ss=[], tp=0, ts=0):
        # is this a valid set of pairs?
        if 99 < tp < ts < 1000:
          (dp, ds) = (nsplit(tp), nsplit(ts))
          if not (0 in dp or 0 in ds or sorted(dp) != sorted(ds)):
            # output solution
            printf("G={tp} M={ts} -> ps={ps} ss={ss}")
        if pairs:
          # try adding in the next pair
          (p, s) = pairs[0]
          (tp_, ts_) = (tp + p, ts + s)
          if tp_ < 999 and ts_ < 1000 and p not in ps and s not in ss:
            solve(pairs[1:], ps + [p], ss + [s], tp_, ts_)
          # without the next pair
          solve(pairs[1:], ps, ss, tp, ts)
      
      # consider possible puzzle/solution numbers
      fn = unpack(lambda p, s: is_power_of(p + s, abs(p - s)) is not None)
      pairs = list(filter(fn, subsets(irange(1, 30), size=2, select='M')))
      solve(pairs)
      

      Solution: The solved puzzles are: 1, 3, 6, 7, 9, 10, 12, 15, 21, 28.

      The (puzzle-number, solution-number) pairs are:

      (1, 3) → 1 + 3 = 4 = (3 – 1)^2
      (3, 5) → 3 + 5 = 8 = (5 – 3)^3
      (6, 10) → 6 + 10 = 16 = (10 – 6)^2
      (7, 9) → 7 + 9 = 16 = (9 – 7)^4
      (9, 7) → 9 + 7 = 16 = (9 – 7)^4
      (10, 6) → 10 + 6 = 16 = (10 – 6)^2
      (12, 15) → 12 + 15 = 27 = (15 – 12)^3
      (15, 17) → 15 + 17 = 32 = (17 – 15)^5
      (21, 28) → 21 + 28 = 49 = (28 – 21)^2
      (28, 21) → 28 + 21 = 49 = (28 – 21)^2

      The sum of the puzzle numbers is 112 (George’s total).

      And the sum of the solution numbers is 121 (Martha’s total).

      Like

  • Unknown's avatar

    Jim Randell 4:48 pm on 1 December 2023 Permalink | Reply
    Tags:   

    Teaser 3193: Balanced education 

    From The Sunday Times, 3rd December 2023 [link] [link]

    George and Martha are headmaster and secretary of Millimix School.

    There are 1000 pupils divided into 37 classes of at least 27 pupils each; each class has at least 13 members of each sex. Thus with 27 × 37 = 999, one class has an extra pupil. The classes are numbered 1-37.

    Martha noted that, taking the class with the extra pupil, and adding its class number to the number of girls in that class and a power of two, she would arrive at the square of the class number. Furthermore, the class number equalled the number of classes in which the girls outnumbered the boys.

    How many boys are in the school?

    [teaser3193]

     
    • Jim Randell's avatar

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

      Each class has either 27 or 28 pupils.

      For 27 the boy/girl split is either (13, 14) or (14, 13).

      For 28 the split is one of: (14, 14) (13, 15) (15, 13).

      The following Python program runs in 59ms. (Internal runtime is 163µs).

      Run: [ @replit ]

      from enigma import (irange, sq, is_power_of, printf)
      
      # consider possible class numbers for the class with 28 pupils
      for n in irange(1, 37):
        # consider the number of girls in the class
        for f in [13, 14, 15]:
          # find the necessary power of 2
          p2 = sq(n) - n - f
          k = is_power_of(p2, 2)
          if k is None: continue
          # calculate total number of boys and girls in the school
          m = 28 - f
          n1 = (n - 1 if f > m else n)
          tm = m + n1 * 13 + (36 - n1) * 14
          tf = f + n1 * 14 + (36 - n1) * 13
          # output solution
          printf("n={n} f={f} -> p2=2^{k} => boys={tm} girls={tf}")
      

      Solution: There are 512 boys in the school.

      Class #6 has 28 pupils, it has 14 boys and 14 girls, and we have:

      6 + 14 + 2^4 = 6^2

      Of the remaining 36 classes, there are 6 with 14 girls and 13 boys, and 30 with 14 boys and 13 girls.

      So the total number of boys in the school is:

      14 + 6×13 + 30×14 = 512

      And the total number of girls is:

      14 + 6×14 + 30×13 = 488

      Like

      • Jim Randell's avatar

        Jim Randell 2:53 pm on 3 December 2023 Permalink | Reply

        An alternative version using the observation (made by GeoffR) that:

        n² − n = n(n − 1) = f + 2^k

        is the product of 2 consecutive integers, so must be even.

        The following Python program has an internal runtime of 56µs.

        Run: [ @replit ]

        from enigma import (irange, sqrtc, printf)
        
        # consider powers of 2
        for k in irange(0, 10):
          p = 2**k
          # consider number of girls in the largest class (such that f + p is even)
          for f in ([13, 15] if p % 2 else [14]):
            # calculate n, such that n(n - 1) = f + p
            x = f + p
            n = sqrtc(x)
            if n > 37: continue
            if n * (n - 1) == x:
              # calculate total number of boys and girls in the school
              m = 28 - f
              n1 = (n - 1 if f > m else n)
              tm = m + n1 * 13 + (36 - n1) * 14
              tf = f + n1 * 14 + (36 - n1) * 13
              # output solution
              printf("k={k} n={n} f={f} => boys={tm} girls={tf}")
        

        Like

    • GeoffR's avatar

      GeoffR 11:57 am on 2 December 2023 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
       
      var 13..15:g; % number of girls in class with extra pupil
      var 1..18:xg; % number of classes with more girls than boys
      var 13..540:tot_g; % total girls in the school - UB = 36*15
      var 13..540:tot_b; % total boys in the school
      var 1..37:c; % class number with extra pupil
      var 1..10:x; % a power of 2
      
      constraint c + g + pow(2,x) = c*c;
      constraint xg == c;
      constraint tot_g == g + xg*14 + (36 - xg)*13;
      constraint tot_b == 1000 - tot_g;
      
      solve satisfy;
      output ["Total boys in school = " ++ show(tot_b)];
      

      Like

    • GeoffR's avatar

      GeoffR 1:46 pm on 2 December 2023 Permalink | Reply

      
      for g in range(13, 16):   # girls in class with extra pupil
        for c in range(1, 38):  # class number with extra pupil
          for x in range(1, 11):
            # number of classes with more girls than boys
            #... equals class number with extra pupil
            if g + c + 2**x == c*c:
              tot_g = g + c*14 + (36 - c)*13
              tot_b = 1000 - tot_g
              print(f"Total boys in school = {tot_b}.")
      

      Like

      • Jim Randell's avatar

        Jim Randell 11:00 am on 3 December 2023 Permalink | Reply

        @GeoffR: Would this give the right answer if there were 15 girls in the largest class?

        Like

    • GeoffR's avatar

      GeoffR 12:10 pm on 3 December 2023 Permalink | Reply

      @JimR: I think the following logic shows that g must be even and of value 14, if my logic is OK.

      The teaser requirements can be expressed as:
      c + g + 2^x = c*c, which reduces to: c*(c – 1) = g + 2^x

      where:
      g = number of girls in class with extra pupil, and c = class number with extra pupil.
      Also x is an integer power of 2.

      Analysis:
      Looking at the equation, it can be seen that:
      1) c * (c-1) will always be of even parity for consecutive integers.
      2) 2^x will always be even , except for 2^0 = 1, which does not give a viable c value.
      3) The RHS of the equation must therefore be even to be of equal parity to the LHS.
      4) This means that both g and 2^x must both be even to maintain parity.
      5) g must be one of (13, 14 or 15), leading to g = 14

      Like

    • GeoffR's avatar

      GeoffR 9:10 am on 5 December 2023 Permalink | Reply

      Using girls = 14 for girls in class with extra pupil (see previous posting) for a one-liner,
      or a two-liner for better readability.

      
      print('Boys =',[1000 - (14 + c * 14 + (36 - c)* 13) for x in range(1, 10)
       for c in range(2, 37) if c * (c - 1) == 14 + 2**x].pop())
      

      Like

  • Unknown's avatar

    Jim Randell 9:18 am on 30 November 2023 Permalink | Reply
    Tags:   

    Brainteaser 1449: Magic! 

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

    This is a magic square containing all the numbers from 1 to 16 once each. As is always the case with magic squares, all rows, columns, and full-length diagonals add up to the same sum (but all our shorter diagonals add to less than that sum).

    Fill in the missing numbers.

    [teaser1449]

     
    • Jim Randell's avatar

      Jim Randell 9:19 am on 30 November 2023 Permalink | Reply

      If we consider the four rows that make up the square, each row sums to the magic constant k, and between all 4 rows each number is included exactly once. So:

      4k = sum(1 .. 16)
      ⇒ k = 34

      We can now use the [[ SubstitutedExpression ]] solver from the enigma.py library to fill out the remaining squares.

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

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      #  A B C D
      #  E F G H
      #  I J K L
      #  M N P Q
      
      # digits 1-16 are used:
      --base="17"
      --digits="1-16"
      
      # rows
      "A + B + C + D == 34"
      "E + F + G + H == 34"
      "I + J + K + L == 34"
      "M + N + P + Q == 34"
      
      # columns
      "A + E + I + M == 34"
      "B + F + J + N == 34"
      "C + G + K + P == 34"
      "D + H + L + Q == 34"
      
      # diagonals
      "A + F + K + Q == 34"
      "D + G + J + M == 34"
      
      # short diagonals
      "B + G + L < 34"
      "C + F + I < 34"
      "E + J + P < 34"
      "H + K + N < 34"
      
      # given values
      "A == 9"
      "J == 13"
      "N == 12"
      "Q == 14"
      
      --template=""
      --verbose="AT"
      

      Solution: Here is the completed square:

      Like

    • NigelR's avatar

      NigelR 6:22 pm on 30 November 2023 Permalink | Reply

      Hi Jim
      I agree your solution, but I’m not sure how you reached it with your short diagonal condition of
      “E + J + P < 34" (which isn't satisfied by the solution). Shouldn't it be E+J+O?

      Like

      • NigelR's avatar

        NigelR 6:25 pm on 30 November 2023 Permalink | Reply

        Apologies: Just realised that your notation didn’t include ‘O’!!

        Like

      • Jim Randell's avatar

        Jim Randell 7:55 pm on 30 November 2023 Permalink | Reply

        @NigelR: I often skip O as a variable, as it is easily confused with 0 (zero).

        Like

    • GeoffR's avatar

      GeoffR 7:02 pm on 1 December 2023 Permalink | Reply

      #  A B C D
      #  E F G H
      #  I J K L
      #  M N P Q
      
      from itertools import product, permutations
      from enigma import all_different
      
      # Given grid values
      A, J, N, Q = 9, 13, 12, 14
      
      # Find remaining 12 digits
      digits = set(range(1,17)).difference({9, 13, 12, 14 }) 
      
      # 1st row of grid
      for B, C, D in product(digits, repeat=3):
        if not all_different(A, B, C, D):continue
        if A + B + C + D != 34:continue
        R1 = list(digits.difference({B, C, D}))
        
        # 2nd row of grid
        for E, F, G, H in product(R1, repeat=4):
          if not all_different(E, F, G, H):continue
          if E + F + G + H != 34:continue
          R2 = set(R1).difference({E, F, G, H})
        
          # 3rd row of grid
          for I, K, L in product(R2, repeat=3):
            if not all_different(I, J, K, L):continue
            if I + J + K + L != 34:continue
            if B + G + L >= 34:continue
            if C + F + I >= 34:continue
            
            # only M and P are missing from the 4th row of grid
            R3 = list(set(R2).difference({I, K, L}))
            for M, P in permutations(R3, 2):
              # columns
              if A + E + I + M != 34:continue
              if B + F + J + N != 34:continue
              if C + G + K + P != 34:continue
              if D + H + L + Q != 34:continue
              # diagonals
              if E + J + P >= 34:continue
              if H + K + N >= 34:continue
              if A + F + K + Q != 34:continue
              if D + J + G + M != 34:continue
              
              print('A, B, C, D = ', A,B,C,D)
              print('E, F, G, H = ', E,F,G,H)
              print('I, J, K, L = ', I,J,K,L)
              print('M, N, P, Q = ', M,N,P,Q)
              exit() # only 1 solution needed
      
      # A, B, C, D =  9 6 15 4
      # E, F, G, H =  16 3 10 5
      # I, J, K, L =  2 13 8 11
      # M, N, P, Q =  7 12 1 14
      
      
      
      

      Like

    • Hugo's avatar

      Hugo 10:01 am on 2 December 2023 Permalink | Reply

      I found a second solution. Did I do something wrong?

      9 2 15 8
      6 7 10 11
      16 13 4 1
      3 12 5 14

      Like

      • Jim Randell's avatar

        Jim Randell 11:03 am on 2 December 2023 Permalink | Reply

        Yes, the shorter diagonals have to sum less than 34.

        You have 16 + 7 + 15 = 38 in your diagram.

        Like

        • Hugo's avatar

          Hugo 11:16 am on 2 December 2023 Permalink | Reply

          Aha! I misread the condition as “not equal to that sum”.

          Like

  • Unknown's avatar

    Jim Randell 9:27 am on 28 November 2023 Permalink | Reply
    Tags:   

    Teaser 2648: Painted cubes 

    From The Sunday Times, 23rd June 2013 [link] [link]

    Oliver found some painted cubes in the loft. These cubes had edges whose lengths were whole numbers of centimetres. After choosing some cubes whose edge lengths were consecutive, Oliver proceeded to cut them into “mini-cubes” of side one centimetre. Of course, some of these mini-cubes were partially painted and some were not painted at all.

    On counting up the mini-cubes, Oliver noticed that exactly half of them were not painted at all.

    How many mini-cubes were not painted at all?

    [teaser2648]

     
    • Jim Randell's avatar

      Jim Randell 9:28 am on 28 November 2023 Permalink | Reply

      I assume the large cubes are painted on all faces.

      When a cube with side n cm is cut into 1 cm cubelets, there will be (n − 2)³ cubelets (or 0 for n ≤ 2) that were hidden in the interior, and so have all faces unpainted. And the remaining cubelets will have at least one face painted.

      n = 1 → 1 painted, 0 unpainted
      n = 2 → 8 painted, 0 unpainted
      n = 3 → 26 painted, 1 unpainted
      n = 4 → 56 painted, 8 unpainted

      This Python program assembles a list of painted/unpainted cubelets for increasing sizes of cubes (up to 50 cm) and looks for a collection of consecutively sized cubes that has the same number of painted and unpainted cubes.

      It runs in 59ms. (Internal runtime is 3.8ms).

      Run: [ @replit ]

      from enigma import (irange, cb, unzip, printf)
      
      # record painted/unpainted cubelets
      cs = dict()
      
      # consider the size of the largest cube
      for n in irange(1, 50):
        # calculate painted/unpainted cubelets
        u = (0 if n < 3 else cb(n - 2))
        cs[n] = (cb(n) - u, u)
      
        # choose the number of large cubes selected k..n
        for k in irange(1, n - 1):
          # calculate total painted/unpainted cubelets
          (tp, tu) = map(sum, unzip(cs[i] for i in irange(k, n)))
          if tp == tu:
            # output solution
            printf("cubes {k}..{n} -> {tp} painted + {tu} unpainted")
      

      Solution: There 3024 unpainted cubelets.

      The cubes are sizes 4 to 12.

      So the total number of cubelets is:

      4³ + … + 12³ = 6048

      And the total number of unpainted cubelets is:

      2³ + … + 10³ = 3024

      So exactly half the cubelets are unpainted.


      With some analysis we can construct a more efficient program:

      Assuming the cubes are sizes k .. n, then the total number of cubelets is:

      T = k³ + … + n³

      And the number of unpainted cubelets is:

      U = (k − 2)³ + … + (n − 2)³

      And:

      T = 2U

      The sum of the first n cube numbers is given by [@wikipedia]:

      ST[n] = T[n]² = (n(n + 1)/2)²

      So we have:

      ST[n] − ST[k − 1] = 2 ST[n − 2] − 2 ST[k − 3]

      or:

      ST[n] − 2 ST[n − 2] = ST[k − 1] − 2 ST[k − 3]

      which is of the form:

      f[n − 2] = f[k − 3]
      where:
      f[x] = ST[x + 2] − 2 ST[x]

      So we can calculate values for f[x] until we find two values that have the same result.

      Say:

      f[x] = f[y]
      where x > y

      Then we have:

      n = x + 2
      k = y + 3

      This Python program runs in 59ms. (Internal runtime is 319µs).

      Run: [ @replit ]

      from enigma import (irange, sq, tri, printf)
      
      # square triangular numbers
      ST = lambda x: sq(tri(x))
      
      # calculate the value of f for increasing x
      d = dict()
      for x in irange(1, 48):
        # calculate f(x)
        v = ST(x + 2) - 2 * ST(x)
        printf("[{x} -> {v}]")
        # have we seen this value before
        y = d.get(v)
        if y:
          # corresponding n, k values
          (n, k) = (x + 2, y + 3)
          # output solution
          printf("cubes {k} .. {n} -> {u} unpainted", u=ST(x) - ST(y))
          break
        # record the value
        d[v] = x
      

      Like

  • Unknown's avatar

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

    Teaser 3192: In formation technology 

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

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

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

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

    [teaser3192]

     
    • Jim Randell's avatar

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

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

      Run: [ @replit ]

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

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

      The full list of formations is:

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

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

      Like

      • Frits's avatar

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

        @Jim,

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

        Like

        • Jim Randell's avatar

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

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

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

          And welcome back, it has been a while.

          Like

  • Unknown's avatar

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

    Teaser 2654: Square cut 

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

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

    How many centimetres wide was the original piece of paper?

    [teaser2654]

     
    • Jim Randell's avatar

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

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

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

      Run: [ @replit ]

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 2670: Answers on a postcard 

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

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

    What number is represented by POSTCARD?

    [teaser2670]

     
    • Jim Randell's avatar

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

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

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

      Run: [ @replit ]

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

      Solution: POSTCARD = 94267830.

      The numbers are:

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

      Like

    • GeoffR's avatar

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

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

      Like

  • Unknown's avatar

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

    Teaser 3191: The budgie’s extra ration 

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

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

    What is the square of that ratio?

    [teaser3191]

     
    • Jim Randell's avatar

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

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

      Here’s a numerical solution:

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

      Run: [ @replit ]

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

      Solution: The square of the ratio is 2.

      i.e. the ratio itself is √2.


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

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

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

      The line AB intersects the diameter at:

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

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

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

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

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

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

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

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

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

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

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

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

      Like

  • Unknown's avatar

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

    Brain-Teaser 422: Telephone number 

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

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

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

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

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

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

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

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

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

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

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

    What was Fred’s telephone number?

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

    [teaser422]

     
    • Jim Randell's avatar

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

      This puzzle is straightforward to solve programatically.

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

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

      Run: [ @replit ]

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

      Solution: Fred’s telephone number is 35742.

      And we have:

      35 + 7 = 42
      35742 = 138 × 259

      Like

    • GeoffR's avatar

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

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

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

      Like

  • Unknown's avatar

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

    Teaser 2652: Square meals 

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

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

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

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

    [teaser2652]

     
    • Jim Randell's avatar

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

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

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

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

      Run: [ @replit ]

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

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

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

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

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

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

      Like

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
Reply
e
Edit
o
Show/Hide comments
t
Go to top
l
Go to login
h
Show/Hide help
shift + esc
Cancel
Design a site like this with WordPress.com
Get started