Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 4:45 pm on 13 August 2021 Permalink | Reply
    Tags:   

    Teaser 3073: Snookered 

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

    The playing surface of a snooker table is a twelve-foot by six-foot rectangle. A ball is placed at P on the bottom cushion (which is six feet wide) and hit so it bounces off the left cushion, right cushion and into the top-left pocket.

    Now the ball is replaced at P and hit so it bounces off the left cushion, top cushion and into the bottom right pocket, after travelling 30% further than the first shot took. The ball always comes away from the cushion at the same angle that it hits the cushion.

    How far did the ball travel on the second shot?

    [teaser3073]

     
    • Jim Randell's avatar

      Jim Randell 5:07 pm on 13 August 2021 Permalink | Reply

      (See also: Teaser 1917).

      With a suitable diagram this puzzle reduces to solving a quadratic equation.

      By reflecting the table along its sides we can convert the paths of the ball into straight lines, whose length can be easily calculated. (For the purposes of this puzzle the ball and the pockets can be considered to be points of negligible size).

      We see that the 1st distance A is given by:

      A² = (12)² + (12 + x)²

      And the second distance B by:

      B² = (24)² + (6 + x)²

      And they are related by:

      B = (13/10)A
      10B = 13A
      100B² = 169A²

      This gives us a quadratic equation in x, which we can solve to find the required answer (= B).

      Run: [ @replit ]

      from enigma import (quadratic, sq, hypot, printf)
      
      # dimensions of the table
      (X, Y) = (6, 12)
      
      # B = (P/Q) A
      # (P^2)(A^2) = (Q^2)(B^2)
      (P2, Q2) = (sq(13), sq(10))
      
      # calculate the coefficients of the quadratic: a.x^2 + b.x + c = 0
      a = P2 - Q2
      b = P2 * 2 * Y - Q2 * 2 * X
      c = P2 * 2 * sq(Y) - Q2 * (sq(2 * Y) + sq(X))
      printf("[({a:+d})x^2 ({b:+d})x ({c:+d})]")
      
      # and solve the equation to determine x
      for x in quadratic(a, b, c, domain='F'):
        if 0 < x < X:
          # calculate the paths
          A = hypot(12, 12 + x)
          B = hypot(24, 6 + x)
          printf("A={A:g} B={B:g} [x={x:g}]")
      

      Solution: On the second shot the ball travelled 26 feet.

      The required equation is:

      69x² + 2856x − 12528 = 0

      This factorises as:

      3(x − 4)(23x + 1044) = 0

      From which the value of x is obvious, and the value of B can then be calculated.

      We find:

      x = 4
      A = 20
      B = 26

      Like

      • Frits's avatar

        Frits 6:25 pm on 13 August 2021 Permalink | Reply

        @Jim, I verified your quadratic solution. It directly follows from the diagram.

        Like

      • Jim Randell's avatar

        Jim Randell 11:51 am on 16 August 2021 Permalink | Reply

        Or finding the answer numerically:

        Run: [ @replit ]

        from enigma import (hypot, fdiv, find_value, printf)
        
        # calculate paths A and B
        A = lambda x: hypot(12, 12 + x)
        B = lambda x: hypot(24, 6 + x)
        
        # find an x value that gives B/A = 1.3
        x = find_value((lambda x: fdiv(B(x), A(x))), 1.3, 0, 6).v
        printf("A={A:g} B={B:g} [x={x:g}]", A=A(x), B=B(x))
        

        Like

  • Unknown's avatar

    Jim Randell 10:35 am on 12 August 2021 Permalink | Reply
    Tags: by: James Fitzgibbon   

    Brain-Teaser 22: [Beauty competition] 

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

    Our beauty competition (said Alice) was rather a fiasco, as the judge failed to turn up. None of the other men would take on the job, so we decided to run it ourselves. Each of us was allowed twelve votes to share out among the competitors, including herself, and zeros were barred. In the result each of us got twelve votes, but that wasn’t the only oddity. The twelve votes were never made up the same way twice, whether given or received.

    I made the least difference between the competitors and, unlike the others, I did not put myself on top. I gave Beryl an odd number of votes, but Chloe gave four odd numbers. Beryl let only two other girls share top place with her, but Diana, the cat, gave herself the most possible votes.

    How did we vote?

    This puzzle was originally published with no title.

    I originally couldn’t find this puzzle in The Sunday Times digital archive, but I think that is because it is quite a poor scan. But it was possible to transcribe it, so here it is, almost exactly 60 years after it was originally published.

    [teaser22]

     
    • Jim Randell's avatar

      Jim Randell 10:37 am on 12 August 2021 Permalink | Reply

      We know there are (at least) 4 contestants. And we quite quickly find (by considering marks awarded by B) that exactly 4 contestants is not possible, so there must be at least one more we are not told about.

      If we suppose there are k contestants, and consider constructing a k × k table, where the rows give the marks awarded, and the columns the marks received. Then each row and column must be derived from a unique distribution of the 12 marks.

      The puzzle states that D gave herself the the highest possible marks. This would be achieved by giving everyone else a score of 1. However this seems to contradict the fact the the distribution of marks in the rows and columns are all different, as if D gave herself the highest possible mark, then the other entries in D’s row and column must all be 1’s, giving a row and a column with the same distribution.

      Instead I suggest we adopt: “The highest number of marks awarded were by D, to herself”.

      Now, if we consider the number of decompositions of 12 into k positive integers, then there must be a unique arrangement for each of the rows and columns, i.e. at least 2k decompositions. And since we can’t use the decomposition with (k − 1) 1’s, we need there to be at least (2k + 1) decompositions. For k = 6 it turns out there are only 11 decompositions, so k = 5 is the only possible number of contestants.

      With that in mind this Python 3 program is implemented to solve the k = 5 case (although it also shows other values are not possible). It runs in 406ms.

      Run: [ @replit ]

      from enigma import (irange, inf, cproduct, subsets, group, all_different, ordered, printf)
      
      # attempt to solve the puzzle for k contestants, decompositions = <ds>
      def solve(k, ds):
      
        # group the decompositions by span
        d = group(ds, by=(lambda s: max(s) - min(s)))
      
        # A's row has minimal span
        rAs = d[min(d.keys())]
      
        # B's has 3 sharing max marks
        rBs = list(s for s in ds if s.count(max(s)) == 3)
      
        # C's has exactly 4 odd numbers
        rCs = list(s for s in ds if sum(x % 2 for x in s) == 4)
      
        # choose rows (unordered) for A, B, C
        for (rA, rB, rC) in cproduct([rAs, rBs, rCs]):
          if not all_different(rA, rB, rC): continue
          # max points awarded (so far)
          (mA, mB, mC) = (max(r) for r in (rA, rB, rC))
      
          # D's row contains a (single) max value greater than mA, mB, mC
          mABC = max(mA, mB, mC)
          for rD in ds:
            if rD in (rA, rB, rC): continue
            mD = max(rD)
            if not (mD > mABC and rD.count(mD) == 1): continue
      
            # solve for the remaining rows
            if k == 5:
              solve5(ds, rA, rB, rC, rD, mA, mB, mC, mD)
            else:
              assert False, "Not reached"
      
      # use multiset permutations (to remove duplication)
      permutations = lambda s: subsets(s, size=len, select="mP")
      
      # solve for k = 5
      def solve5(ds, rA, rB, rC, rD, mA, mB, mC, mD):
        # choose an ordering for A, st A [0] is not max, and B [1] is odd
        for A in permutations(rA):
          if not (A[1] % 2 == 1 and A[0] < mA): continue
      
          # choose an ordering for B, st B [1] is max
          for B in permutations(rB):
            if not (B[1] == mB): continue
      
            # chose an ordering for C, st C [2] is max
            for C in permutations(rC):
              if not (C[2] == mC): continue
      
              # chose an ordering for D, st D [3] is max
              for D in permutations(rD):
                if not (D[3] == mD): continue
      
                # construct row E
                E = tuple(12 - sum(x) for x in zip(A, B, C, D))
                # all values are between 0 and mD (exclusive)
                if not all(0 < x < mD for x in E): continue
                # and E [4] is max
                if not (E[4] == max(E)): continue
                # check E is a valid decomposition
                rE = ordered(*E)
                if not (rE in ds and rE not in (rA, rB, rC, rD)): continue
      
                # calculate the column distributions
                cds = list(ordered(*x) for x in zip(A, B, C, D, E))
                # check all distributions are distinct
                if not all_different(rA, rB, rC, rD, rE, *cds): continue
      
                # output solution
                printf("k=5: A={A} B={B} C={C} D={D} E={E}")
      
      # decompose total <t> into <k> numbers, minimum <m>
      def decompose(t, k, m=1, ns=[]):
        if k == 1:
          if not (t < m):
            yield tuple(ns + [t])
        else:
          k_ = k - 1
          for n in irange(m, t - k_ * m):
            yield from decompose(t - n, k_, n, ns + [n])
      
      # try possible k values
      for k in irange(4, inf):
        # consider the number of decompositions for the rows/columns
        ds = list(ns for ns in decompose(12, k) if ns.count(1) < k - 1)
        printf("[k={k}: {n} decompostions]", n=len(ds))
        if len(ds) < 2 * k: break
        # solve the puzzle
        solve(k, ds)
      

      Solution: The marks awarded are as follows:

      Like

  • Unknown's avatar

    Jim Randell 9:54 am on 10 August 2021 Permalink | Reply
    Tags:   

    Teaser 2808: Hot stuff 

    From The Sunday Times, 17th July 2016 [link] [link]

    George and Martha are dabbling in the world of high temperature physics. George was measuring the temperature of a molten metal and wrote down the result. He thought this was in degrees Centigrade and so he converted it to Fahrenheit. However, Martha pointed out that the original result was already in degrees Fahrenheit and so George converted it to Centigrade. Martha wrote down the original result and each of George’s two calculated answers. She noted that they were all four-figure numbers and that their sum was palindromic.

    What was the original four-figure result?

    [teaser2808]

     
    • Jim Randell's avatar

      Jim Randell 9:57 am on 10 August 2021 Permalink | Reply

      This Python program considers possible 4-digit numbers for the first reading, and then looks for viable second and third numbers that give a palindromic sum. It runs in 55ms.

      Run: [ @replit ]

      from enigma import (div, irange, is_npalindrome, printf)
      
      # convert centigrade (celsius) to fahrenheit
      c2f = lambda n: div(9 * n + 160, 5)
      
      # convert fahrenheit to centigrade (celsius)
      f2c = lambda n: div(5 * n - 160, 9)
      
      # original number (a 4-digit number)
      for n1 in irange(1000, 9999):
      
        # second number
        n2 = c2f(n1)
        if n2 is None or n2 < 1000 or n2 > 9999: continue
      
        # third number
        n3 = f2c(n1)
        if n3 is None or n3 < 1000 or n3 > 9999: continue
      
        # look for a palindromic sum
        t = n1 + n2 + n3
        if is_npalindrome(t):
          # output solution
          printf("n1={n1} n2={n2} n3={n3} t={t}")
      

      Solution: The original reading was 3155.

      And:

      3155 °C = 5711 °F
      3155 °F = 1735 °C
      3155 + 5711 + 1735 = 10601


      With analysis we can restrict the numbers considered for n1, and simplify the calculations.

      We see n1 must be in the range [1832, 5537], and must be a multiple of 5, and must equal 5 (mod 9).

      Run: [ @replit ]

      from enigma import (irange, is_npalindrome, printf)
      
      # all three numbers have 4 digits
      for k in irange(41, 122):
        # original number
        n1 = 45 * k + 5
        # second number
        n2 = 81 * k + 41
        # third number
        n3 = 25 * k - 15
        # look for a palindromic sum
        t = n1 + n2 + n3
        if is_npalindrome(t):
          # output solution
          printf("n1={n1} n2={n2} n3={n3} t={t}")
      

      Like

    • GeoffR's avatar

      GeoffR 11:55 am on 10 August 2021 Permalink | Reply

      Checking for the palindromic sum was a bit lengthy in MiniZinc, as I could not be sure if this total was four or five digits long.

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      % T1 = original temperature, T2 in F, T3 in C
      var 1000..9999:T1; var 1000..9999:T2; var 1000..9999:T3;
      
      var 1000..99999: T4; % total of three temperatures
      
      constraint 5 * (T2 - 32) == 9 * T1;  % T2 = C to F from T1
      
      constraint 9 * T3 == 5 * (T1 - 32);  % T3 = F to C from T1
      
      constraint T4 == T1 + T2 + T3;
      
      % check for a 4-digit or 5-digit palindrome sum for T4
      var 1..9:a; var 0..9:b; var 0..9:c; var 0..9:d; var 0..9:e;
      
      constraint (T4 < 10000 /\ a == T4 div 1000 /\ b == T4 div 100 mod 10
      /\ c == T4 div 10 mod 10 /\ d == T4 mod 10 /\ a == d /\ b == c)
      \/
      (T4 > 10000 /\ a == T4 div 10000 /\ b == T4 div 1000 mod 10
      /\ c == T4 div 100 mod 10 /\ d == T4 div 10 mod 10 /\ e  == T4 mod 10
      /\ a == e /\ b == d);
      
      solve satisfy;
      
      output ["Original Temperature = " ++ show(T1) ++ "\n"
      ++ "Conversion Temperatures = " ++ show(T2) ++ ", " ++ show(T3)
      ++ "\n" ++ "Palindromic sum of three temperatures = " ++ show(T4) ];
      
      % Original Temperature = 3155
      % Conversion Temperatures = 5711, 1735
      % Palindromic sum of three temperatures = 10601
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 5:23 pm on 6 August 2021 Permalink | Reply
    Tags:   

    Teaser 3072: Dial M for Marriage 

    From The Sunday Times, 8th August 2021 [link] [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's avatar

      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's avatar

        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's avatar

          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's avatar

            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's avatar

            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's avatar

      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's avatar

      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's avatar

      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

  • Unknown's avatar

    Jim Randell 10:19 am on 5 August 2021 Permalink | Reply
    Tags:   

    Teaser 2809: This and that 

    From The Sunday Times, 24th July 2016 [link] [link]

    I have written down a list of eleven numbers and then consistently replaced digits by letters, with different letters for different digits. In this way the list becomes:

    SO
    DO
    WHAT
    NOW
    ADD
    AND
    SEND
    IN
    THIS
    AND
    THAT

    The grand total of these eleven numbers is a four-figure number.

    Numerically, what is THIS + THAT?

    [teaser2809]

     
    • Jim Randell's avatar

      Jim Randell 10:19 am on 5 August 2021 Permalink | Reply

      We can find the answer using the [[ SubstitutedExpression ]] solver from the enigma.py library.

      The following run file executes in 893ms:

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      "SO + DO + WHAT + NOW + ADD + AND + SEND + IN + THIS + AND + THAT < 10000"
      
      --answer="THIS + THAT"
      

      Solution: THIS + THAT = 2123.

      The result of the sum is 9998.


      To gain a bit more speed we can use the technique implemented in [[ SubstitutedExpression.split_sum() ]], splitting the sum into separate columns with carries between. (See: Teaser 2792).

      Except here we may have to deal with multi digit carries (as there are 11 summands — although with a bit of analysis we can see that single digit carries would be sufficient in this case).

      The following run-file executes in 226ms.

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --symbols="ADEHINOSTWabcdewxyz"
      --distinct="ADEHINOSTW"
      --invalid="0,ADINSTWw"
      --invalid="2-9,ac"
      
      # "SO + DO + WHAT + NOW + ADD + AND + SEND + IN + THIS + AND + THAT = wxyz"
      
      "        W +             S +     T +     T + c + e = w"
      "        H + N + A + A + E +     H + A + H + a + d = ex"
      "S + D + A + O + D + N + N + I + I + N + A + b     = cdy"
      "O + O + T + W + D + D + D + N + S + D + T         =  abz"
      
      --template="(SO + DO + WHAT + NOW + ADD + AND + SEND + IN + THIS + AND + THAT = wxyz)"
      --answer="THIS + THAT"
      

      Like

      • Frits's avatar

        Frits 4:41 pm on 5 August 2021 Permalink | Reply

        To gain more speed in the first program you can also add:

        “W + S + 2 * T < 10"

        For more on this puzzle see:

        [https://brg.me.uk/?page_id=4022]

        Like

      • Jim Randell's avatar

        Jim Randell 5:32 pm on 5 August 2021 Permalink | Reply

        Adding a second level of truncation (without worrying about the exact values of the carries) brings the run time down to 80ms. And there is no need for a third level.

        Run: [ @replit ]

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        --invalid="0,ADINSTW"
        
        "W + S + T + T < 10"
        "WH + N + A + A + SE + TH + A + TH < 100"
        "SO + DO + WHAT + NOW + ADD + AND + SEND + IN + THIS + AND + THAT < 10000"
        
        --template="(SO + DO + WHAT + NOW + ADD + AND + SEND + IN + THIS + AND + THAT < 10000)"
        --answer="THIS + THAT"
        

        Like

    • GeoffR's avatar

      GeoffR 12:29 pm on 5 August 2021 Permalink | Reply

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      % Digits in main 11 numbers
      var 0..9:S; var 0..9:O; var 0..9:D; var 0..9:W;var 0..9:H;
      var 0..9:A; var 0..9:T; var 0..9:E; var 0..9:N;var 0..9:I;
      
      % Carry digits for sum of 11 numbers
      var 1..7:c1; var 1..7:c2; var 1..7:c3;
      
      % Total of 11 numbers
      var 1..9: a; var 0..9: b; var 0..9: c; var 0..9: d;
      var 1000..9999: total == 1000*a + 100*b + 10*c + d;
      
      constraint S > 0 /\ D > 0 /\ W > 0 /\ N > 0 /\ A > 0 /\ I > 0 /\ T > 0;
      constraint all_different([S, O, D, W, H, A, T, E, N, I]);
      
      % Main sum
      %     S O
      %     D O
      % W H A T
      %   N O W
      %   A D D
      %   A N D
      % S E N D
      %     I N
      % T H I S
      %   A N D 
      % T H A T
      %--------
      % a b c d
      %--------
      
      % Adding 4 columns, with carry digits, starting at right hand column
      constraint c1 == (O + O + T + W + D + D + D + N + S + D + T) div 10
      /\ d == (O + O + T + W + D + D + D + N + S + D + T) mod 10;
      
      constraint c2 == (c1 + S + D + A + O + D + N + N + I + I + N + A) div 10
      /\ c == (c1 + S + D + A + O + D + N + N + I + I + N + A) mod 10;
      
      constraint c3 == (c2 + H + N + A + A + E + H + A + H) div 10
      /\ b == (c2 + H + N + A +A +  E + H + A + H) mod 10;
      
      constraint a == c3  + W + S + T + T;
      
      % Sum 2 -  THIS + THAT
      var 0..1:c4; var 0..2:c5; var 0..2:c6;
      var 1..9: e; var 0..9: f; var 0..9: g; var 0..9: h;
      var 1000..9999: efgh = 1000*e + 100*f + 10*g + h;
      
      %  T H I S
      %  T H A T
      %  -------
      %  e f g h
      %---------
      
      constraint c4 == (S + T) div 10 /\ h == (S + T ) mod 10;
      
      constraint c5 == (c4 + I + A) div 10 /\  g == (c4 + I + A) mod 10;
      
      constraint c6 == (c5 + H + H) div 10 /\ f == (c5 + H + H) mod 10;
      
      constraint e == c6 + T +  T;
      
      constraint efgh == 1000*e + 100*f + 10*g + h;
      
      solve satisfy;
      
      output ["Total of 11 numbers = " ++ show(total ) ++ "\n" 
      ++ "THIS + THAT = " ++ show(efgh) ];
      
      % Total of 11 numbers = 9998
      % THIS + THAT = 2123
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:20 am on 3 August 2021 Permalink | Reply
    Tags:   

    Teaser 2807: Pentagons 

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

    I have taken five identical straight rods and joined each to the next by a hinge to make a flexible ring. With this ring I can make lots of different pentagonal shapes, and in particular I can make lots of pentagons with area equal to a whole number of square centimetres. The largest whole-numbered area achievable is a two-figure number, and the smallest whole-numbered area achievable is another two-figure number. In fact these two numbers use the same two digits but in the reverse order.

    What is the largest whole-numbered area achievable?

    [teaser2807]

     
    • Jim Randell's avatar

      Jim Randell 9:21 am on 3 August 2021 Permalink | Reply

      The maximal area is achieved with a cyclic pentagon. If the sides have length x the area is given by:

      Amax(x) = (x² / 4) √(5(5 + 2√5))

      If this value is not an integer, the maximum integer-valued area will be achieved by a slight deformation of the pentagon to give the floor of this value.

      I believe the minimal area is achieved with a degenerate pentagon where two of the sides are collapsed to give an equilateral triangle with a protruding spike.

      Amin(x) = (x² / 4) √3

      Again, if this value is not an integer, the minimum integer-valued area will be achieved by opening up the collapsed spike slightly (to give an actual pentagon), and increase the area to the ceiling of this value.

      This Python program considers possible Amax and Amin values (each 2 digits, one the reverse of the other), and calculates the range of x values that would give these values. If there are any common values then we have a viable solution. It runs in 50ms.

      Run: [ @replit ]

      from enigma import (irange, nreverse, sqrt, fdiv, printf)
      
      # area multipliers
      maxA = fdiv(sqrt(5 * (5 + 2 * sqrt(5))), 4)
      minA = fdiv(sqrt(3), 4)
      
      # consider 2 digit maximal area
      for M in irange(10, 99):
        m = nreverse(M)
        if not (9 < m < M): continue
      
        # calculate range of x values based on M
        xmin1 = sqrt(M, maxA)
        xmax1 = sqrt(M + 1, maxA)
      
        # calculate range of x value based on m
        xmin2 = sqrt(m - 1, minA)
        xmax2 = sqrt(m, minA)
      
        # intersect the intervals
        xmin = max(xmin1, xmin2)
        xmax = min(xmax1, xmax2)
        if not (xmin < xmax): continue
        printf("Amax={M} Amin={m} -> x = [{xmin}, {xmax}]")
      

      Solution: The largest whole-number area achievable is 61 cm².

      And the smallest whole-number area achievable is 16 cm².

      The length of the rods is between 5.995 cm and 6.003 cm, so (although not mentioned in the puzzle) an integer length (of 6 cm) for the rods could be intended.

      For 6 cm rods we have:

      Amax ≈ 61.937 cm²
      Amin ≈ 15.588 cm²

      Like

    • Frits's avatar

      Frits 5:03 pm on 4 August 2021 Permalink | Reply

      Using Jim’s code as a base but avoiding square root calculations.
      The internal run time is 400 nanoseconds (with PyPy).

       
      # "x^2" related multipliers
      bigMult = 4 / (5 * (5 + 2 * 5**.5))**.5
      smallMult = 4 / 3**.5
      
      # consider 2 digit maximal area
      for t in range(2, 10):
        for u in range(1, t):
          M = 10 * t + u
          m = u * 10 + t
        
          # calculate range of x^2 values
          x2max1 = (M + 1) * bigMult
          x2min2 = (m - 1) * smallMult
      
          # leave inner loop if already a max is smaller than a min
          # as x2min2 grows faster than x2max1 for subsequent entries
          if x2max1 < x2min2: break
      
          x2min1 = x2max1 - bigMult
          x2max2 = x2min2 + smallMult
      
          # intersect the intervals
          x2min = max(x2min1, x2min2)
          x2max = min(x2max1, x2max2)
          if not(x2min < x2max): continue
          print(f"answer: {M} cm2")
      

      Like

  • Unknown's avatar

    Jim Randell 9:38 pm on 31 July 2021 Permalink | Reply
    Tags: by: Oliver Tailby   

    Teaser 3071: Three-cornered problem 

    From The Sunday Times, 1st August 2021 [link] [link]

    I have a set of equal-sized equilateral triangular tiles, white on one face and black on the back. On the white side of each tile I have written a number in each corner. Overall the numbers run from 1 to my age (which is less than thirty). If you picture any such tile then it occurs exactly once in my set (for example, there is just one tile containing the numbers 1, 1 and 2; but there are two tiles containing the numbers 1, 2 and 3).

    The number of tiles that contain at least one even number is even.

    The number of tiles that contain at least one odd number is odd.

    The number of tiles that contain at least one multiple of four is a multiple of four.

    How old am I?

    [teaser3071]

     
    • Jim Randell's avatar

      Jim Randell 10:01 pm on 31 July 2021 Permalink | Reply

      This Python program constructs the possible triangular tiles for increasing ages, and keeps count of the number that satisfy the given conditions. It runs in 73ms.

      from enigma import (irange, subsets, uniq, printf)
      
      # count triangles with ...
      evens = 0  # ... at least one even number
      odds = 0  # ... at least one odd number
      mul4s = 0  # ... at least one multiple of 4
      
      # canonical form (by rotation)
      def canonical(a, b, c):
        return min((a, b, c), (b, c, a), (c, a, b))
      
      # consider triangles with largest number n
      for n in irange(1, 29):
        # consider rotationally distinct triangles
        for tri in uniq(canonical(n, a, b) for (a, b) in subsets(irange(1, n), size=2, select="M")):
          # increase the counts
          if any(x % 2 == 0 for x in tri): evens += 1
          if any(x % 2 == 1 for x in tri): odds += 1
          if any(x % 4 == 0 for x in tri): mul4s += 1
      
        # output solution
        if evens % 2 == 0 and odds % 2 == 1 and mul4s % 4 == 0:
          printf("n={n} [evens={evens} odds={odds} mul4s={mul4s}]")
      

      Solution: Your age is 17.

      The total number of triangular tiles for any particular age is given by OEIS A006527 [link]:

      a(n) = n(n² + 2) / 3


      There is the possibility of a solution at age = 1. In this case there is only 1 tile in the set — (1, 1, 1). But the setter might be considering that the numbers of tiles in each category is non-zero (also 1 is a little young to be setting such a puzzle). In any case, we are told that there are two (1, 2, 3) tiles, so we know the age must be at least 3.

      In general we find solutions to the puzzle at ages of the form (16k + 1).

      Like

      • Frits's avatar

        Frits 1:23 pm on 2 August 2021 Permalink | Reply

        Similar.

         
        # consider rotationally distinct triangles containing number n
        def triangles(n):
          for x in range(n, 0, -1):
            for y in range(x, 0, -1): 
              yield [n, x, y]
          for x in range(n - 2, 0, -1):    
            for y in range(n - 1, x, -1): 
              yield [n, x, y]
         
        odds, evens, mul4s = 0, 0, 0
        
        print("age evens  odds mlt4s")
        for age in range(1, 30):
          for tri in triangles(age):
            
            if any(x % 2 == 0 for x in tri): evens += 1
            if any(x % 2 for x in tri): odds += 1
            if any(x % 4 == 0 for x in tri): mul4s += 1
          
          if mul4s % 4 == 0 and evens % 2 == 0 and odds % 2:
            print(f"{age:>3} {evens:>5} {odds:>5} {mul4s:>5}") 
        

        Like

  • Unknown's avatar

    Jim Randell 9:08 am on 29 July 2021 Permalink | Reply
    Tags:   

    Teaser 2806: Jack’s jigsaw 

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

    I made Jack a jigsaw. I started with a rectangle of card that I had cut from an A4 sheet and then I cut the rectangle into pieces. There were some square pieces of sizes (in centimetres) 1×1, 2×2, 3×3, … with the largest having side equal to Jack’s age. The remaining pieces were rectangles of sizes 1×2, 2×3, 3×4, … with the largest length amongst them again being Jack’s age. Then Jack succeeded in putting them together again to form a rectangle, but the perimeter of his rectangle was smaller than the perimeter of my original rectangle.

    What were the lengths of the sides of my original rectangle?

    [teaser2806]

     
    • Jim Randell's avatar

      Jim Randell 9:09 am on 29 July 2021 Permalink | Reply

      The original rectangle is cut from an A4 sheet of card (dimensions = 21.0cm × 29.7cm).

      The maximum square that we can make would be 21cm × 21cm, which puts an upper limit on Jack’s age. However the area of an A4 sheet places a smaller upper limit on Jack’s age, as the sum of the area of the pieces for age 10 would be 715cm² – larger than the area of a single A4 sheet.

      This Python program considers possible ages, and calculates the total area of the rectangular pieces. It then looks for possible dimensions and perimeters for packed rectangles. And looks for two of these that could be the setters and Jack’s rectangles. It then uses the rectpack.py library (see: Enigma 1491, Teaser 2650, Teaser 3069) to attempt to pack the pieces into the required rectangle.

      It runs in 1.94s.

      Run: [ @replit ]

      from enigma import irange, divisors_pairs, cached, printf
      from rectpack import pack_tight, output_grid, by_area
      
      # how to pack a set of rectangles
      pack = lambda x, y, rs: pack_tight(x, y, by_area(rs))
      
      # look for a packing
      @cached
      def check(n, x, y):
        # collect the pieces
        ps = list((k, k) for k in irange(1, n))
        ps += list((k - 1, k) for k in irange(2, n))
      
        # attempt a packing
        printf("[n = {n} -> pack {x} x {y} ...]")
        for s in pack(x, y, ps):
          output_grid(x, y, s)
          return True
        printf("[-> no packing]")
        return False
      
      # total area
      t = 0
      for n in irange(1, 21):
        t += n * (2 * n - 1)
        if t > 624: break
      
        printf("[n={n}; t={t}]")
      
        # find possible dimensions and perimeter for the packed rectangles
        rs = dict(((a, b), 2 * (a + b)) for (a, b) in divisors_pairs(t) if not(n > a))
      
        # consider the setter's rectangle
        for ((a1, b1), p1) in rs.items():
          # it must fit on an A4 sheet
          if a1 > 21 or b1 > 29: continue
      
          # and look for another rectangle, with a smaller perimeter
          for ((a2, b2), p2) in rs.items():
            if not(p2 < p1): continue
      
            # check packing is possible
            if check(n, a1, b1) and check(n, a2, b2):
              # output solution
              printf("n={n}: {a1}x{b1} (p={p1}) -> {a2}x{b2} (p={p2}) [*** SOLUTION ***]")
      

      Solution: The original rectangle measured 12cm × 21cm.

      Here is a diagram of the two packings (12×21 → 14×18):

      There will be many more different packings, as any sub-rectangle consisting of two (or more) pieces can be flipped round. (In fact there are 5156 different packings for the 12×21 rectangle, and 1307 for the 14×18 rectangle).


      The program assumes that the original rectangle was cut parallel to the sides of the A4 sheet, so the smaller side must be no more than 21cm and the longer side no more than 29cm. But rectangles with a dimension larger than these could potentially be made by cutting a rectangle not aligned with the sides. (e.g. We could cut a 7×30 rectangle obliquely from a sheet of A4).

      So there are potentially two possible ages, listed here along with possible rectangles (and perimeters):

      n=7: 7×36 (86), 9×28 (74), 12×21 (66), 14×18 (64)
      n=9: 15×35 (100), 21×25 (92)

      It is not possible to pack the 7×36 or 9×28 rectangles, so there are only 2 viable solutions:

      n=7: 12×21 (66) → 14×18 (64)
      n=9: 15×35 (100) → 21×25 (92)

      In the n=7 case the 12×21 rectangle can easily be cut from an A4 sheet (with a single cut), so this is a viable solution. (Note that as it is possible to cut the 9×28 rectangle from an A4 sheet, we need to show that it cannot be packed in order to rule out multiple solutions).

      In the n=9 case the 15×35 rectangle cannot be cut from an A4 sheet (even obliquely), so this is not a viable solution. (Although the 21×25 rectangle could be cut from an A4 sheet (again with a single cut), the puzzle requires that the rectangle with the larger perimeter be cut out from the A4 sheet).

      Like

      • Frits's avatar

        Frits 2:48 pm on 30 July 2021 Permalink | Reply

        @Jim,

        Checking combination 9 x 28 takes a long time.

        Running time will be under half a second (with CPython) if you add the following to function check (or incorporate it in rectpack.py):

         
        # skip if stacked wide pieces exceed height
        if sum(min(a, b) for a, b in ps if 2 * min(a, b) > x) > y: 
          return False
        

        Like

        • Jim Randell's avatar

          Jim Randell 11:42 am on 31 July 2021 Permalink | Reply

          @Frits: Thanks for the suggestion.

          I’ve added a [[ pack() ]] function to rectpack.py that does a couple of quick checks before starting the packing.

          (And I also added [[ pack_uniq() ]] which generates solutions that are symmetrically distinct, see: Teaser 3069).

          Like

  • Unknown's avatar

    Jim Randell 9:14 am on 27 July 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 817: Fish in the swim 

    From The Sunday Times, 20th March 1977 [link]

    The goldfish and the shubunkin swim in straight lines and at the same speed, and they always make right-angled turns when they do turn.

    From a point on the edge of the Round Lake (a perfect circle, of course) the goldfish swam due north and the shubunkin swam in a direction somewhere between north and east.

    After an hour the goldfish reached the edge of the lake and turned right; simultaneously the shubunkin turned right.

    Half an hour later the shubunkin reached the edge of the lake, and 15 minutes after that the goldfish again reached a point on the edge of the lake.

    The shubunkin, having rested for 15 minutes, wants the longest possible swim now without reaching a point on the edge of the lake.

    What course should the fish steer?

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

    [teaser817]

     
    • Jim Randell's avatar

      Jim Randell 9:14 am on 27 July 2021 Permalink | Reply

      The goldfish starts at A, swims due north to B (in 60 minutes), and then due east to C (in 45 minutes).

      If we use units of distance corresponding to how far a fish can travel in 15 minutes we get AB = 4, BC = 3.

      So, ABC is a (3, 4, 5) triangle, hence AC is a diameter of lake, and so the lake has radius 5/2.

      In 60 minutes, the shubunkin swam from A to X (AX = 4), and in 30 minutes swam from X to Y (XY = 2).

      The subunkin then wishes to make the longest possible straight line journey, which is the diameter of the lake YZ.

      We need to determine the direction of YZ (which is the same as the direction OZ).

      Labelling the angle BAC as φ and COZ as θ then the direction OZ (amount west of north) is given by:

      d = θ − φ

      From triangle ABC we have:

      cos(φ) = 4/5

      And using the cosine rule in triangle AOY to determine the angle AOY = θ, we have:

      4² + 2² = 2(5/2)² − 2(5/2)(5/2)cos(θ)
      cos(θ) = −3/5

      Hence the direction OZ is:

      d = acos(−3/5) − acos(4/5)

      And:

      d = acos(−3/5) − acos(4/5)
      d = asin(3/5) + asin(4/5)
      d = φ + (90° − φ)
      d = 90°

      Solution: The shubunkin should head due west.

      So a more accurate representation of the situation is:

      Where OXY is a (3/2, 4/2, 5/2) triangle.

      Like

  • Unknown's avatar

    Jim Randell 8:46 am on 25 July 2021 Permalink | Reply
    Tags: by: C. Chittock   

    Brain-Teaser 41: Selecting a team 

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

    The Cricket Selection Committee of the State of Bonga have chosen 13 players — six batsman, five bowlers, an all-rounder and a wicketkeeper — for their Test Match against Donga, the final choice on the day of the match being left to the captain.

    The captain decides that the two to be omitted must be a batsman and a bowler, a batsman and the all-rounder, or a bowler and the all-rounder: but, distrusting his judgment of individuals, he consults the Chief Magician, who surprisingly assures him that if the ages of the eleven total 282, the team will be invincible.

    A check of the players’ ages shows that three (two bowlers and the all-rounder) are aged 24: the ages of the remainder are all different, ranging from 21 to 31, the captain, aged 31, being the oldest. The ages of the three remaining bowlers total 83.

    The captain discovers that there is only one possible choice which will comply with the Magician’s advice, and is relieved to find that he is not under the necessity of standing down himself.

    What is the wicketkeeper’s age?

    [teaser41]

     
    • Jim Randell's avatar

      Jim Randell 8:47 am on 25 July 2021 Permalink | Reply

      There are 3 players who are 24, and 1 of each of the other ages from 21 to 31.

      So, the total for all 13 players being 334.

      We are looking to select 11 players with a total of 282. So we need to discard 2 players, whose ages sum to 52.

      This Python program runs in 51ms.

      Run: [ @replit ]

      from itertools import product
      from enigma import irange, diff, subsets, join, printf
      
      # the squad is:
      #  [A] 1 all-rounder (24)
      #  [BCDEF] 5 bowlers, 2 @ (24), remaining 3 have combined age of 83
      #  [GHIJKL] 6 batsmen
      #  [W] 1 wicket keeper
      allrs = 'A'
      bowls = 'BCDEF'
      bats = 'GHIJKL'
      
      # unallocated ages = 21 - 31, except 24
      ages = diff(irange(21, 31), [24])
      
      # choose the ages of the 3 bowlers that sum to 83
      for bs in subsets(ages, size=3, fn=list):
        if sum(bs) != 83: continue
      
        # choose the age of the wicket keeper
        for wk in ages:
          if wk in bs: continue
      
          # map names to ages
          m = dict(A=24, B=24, C=24, W=wk)
          m.update(zip(diff(bowls, m.keys()), bs)) # remaining bowlers
          m.update(zip(bats, diff(ages, bs + [wk]))) # batsmen
      
          # look for excluded combinations that add up to 334 - 282 = 52
          exclude = lambda xs, ys: list((x, y) for (x, y) in product(xs, ys) if m[x] + m[y] == 52)
          xs = exclude(bats, bowls) + exclude(bats, allrs) + exclude(bowls, allrs)
      
          # there is only one possible set of excluded players
          if len(xs) == 1:
            xs = xs[0]
            # and they don't include the captain (age = 31)
            if any(m[x] == 31 for x in xs): continue
      
            # output solution
            printf("wk = {wk} [excluded = {xs}]", xs=join(sorted(xs), sep=" "))
      

      Solution: The wicket-keeper is 23.

      A bowler (28) and the all-rounder (24) are dropped from the team.

      The ages of squad are:

      all-rounder: (24)
      bowlers: 24, 24, 26, (28), 29
      batsmen: 21, 22, 25, 27, 30, 31
      wicket-keeper: 23

      Like

  • Unknown's avatar

    Jim Randell 4:59 pm on 23 July 2021 Permalink | Reply
    Tags:   

    Teaser 3070: Film binge 

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

    I’m going to have a day binge-watching films. My shortlist is:

    Quaver, starring Amerton, Dunino, Emstrey, Fairwater and Joyford;
    Rathripe, starring Amerton, Codrington, Fairwater, Glanton and Ingleby;
    Statecraft, starring Amerton, Codrington, Dunino, Hendy and Ingleby;
    Transponder, starring Codrington, Dunino, Emstrey, Hendy and Ingleby;
    Underpassion, starring Blankney, Emstrey, Fairwater, Hendy and Ingleby;
    Vexatious, starring Amerton, Blankney, Dunino, Emstrey and Joyford;
    Wergild, starring Blankney, Fairwater, Glanton, Hendy and Joyford;
    X-axis, starring Blankney, Codrington, Fairwater, Glanton and Ingleby;
    Yarborough, starring Blankney, Dunino, Glanton, Hendy and Joyford;
    Zeuxis, starring Amerton, Codrington, Emstrey, Glanton and Joyford.

    I dislike Ingleby and Joyford, so I don’t want either of them in more than two films; but I want to see each of the others in at least two films.

    Which are the films I should watch?

    [teaser3070]

     
    • Jim Randell's avatar

      Jim Randell 5:17 pm on 23 July 2021 Permalink | Reply

      A directed search would let us prune away subsets with too much I and J in, but on a problem of this size we can just examine all possible subsets of films to see which collections meet the criteria.

      The following Python program runs in 67ms.

      Run: [ @replit ]

      from enigma import subsets, union, multiset, join, printf
      
      # the films and the stars
      films = {
        'Q': 'ADEFJ', 'R': 'ACFGI', 'S': 'ACDHI', 'T': 'CDEHI', 'U': 'BEFHI',
        'V': 'ABDEJ', 'W': 'BFGHJ', 'X': 'BCFGI', 'Y': 'BDGHJ', 'Z': 'ACEGJ',
      }
      stars = union(films.values())
      
      # selection criteria:
      # I, J in no more than 2 films, otherwise in at least 2 films
      fn = lambda k, n: (n <= 2 if k in 'IJ' else n >= 2)
      
      # choose films
      for ks in subsets(films.keys(), min_size=2):
        # collect the stars
        ss = multiset.from_seq(*(films[k] for k in ks))
        # perform the check
        if all(fn(k, ss.count(k)) for k in stars):
          printf("films = {ks}", ks=join(ks, sep=' '))
      

      Solution: The chosen films are: Rathripe, Transponder, Vexatious, Wergild.

      For this collection, each of the stars (including I and J) are in exactly 2 of the films. And this is the only collection that meets the required condition.

      Like

      • Jim Randell's avatar

        Jim Randell 1:04 pm on 26 July 2021 Permalink | Reply

        The particular set of films used in the puzzle allow us to make some shortcuts:

        As exactly one of I or J is in each of the films, we can see that we can be looking for no more than 4 films (2 with I and 2 with J). And each of the films has exactly 4 of the other stars in.

        So to see at least 2 films involving each of the other stars we are going to need to see at least 8×2/4 = 4 films. So of the 20 stars involved in each of the 4 films, each must appear twice.

        Hence we need to look for exactly 4 films, so we could just pass [[ size=4 ]] at line 15 of my original program. Or, more efficiently, we could consider combinations of 2 films involving I and 2 films involving J.

        from enigma import subsets, join, printf
        
        # the films and the stars
        filmsI = {
          #     ABCDEFGHIJ
          'R':  1010011010,
          'S':  1011000110,
          'T':    11100110,
          'U':   100110110,
          'X':   110011010,
        }
        
        filmsJ = {
          #     ABCDEFGHIJ
          'Q':  1001110001,
          'V':  1101100001,
          'W':   100011101,
          'Y':   101001101,
          'Z':  1010101001,
        }
        
        # collect sums for pairs of films for I and J
        pairs = lambda d: dict((d[x] + d[y], (x, y)) for (x, y) in subsets(d.keys(), size=2))
        (psI, psJ) = (pairs(filmsI), pairs(filmsJ))
        
        # consider a pair of films for I ...
        target = 2222222222
        for (sI, fsI) in psI.items():
          # ... with a corresponding value for J
          fsJ = psJ.get(target - sI, None)
          if fsJ is not None:
            printf("films = {fs}", fs=join(sorted(fsI + fsJ), sep=' '))
        

        Technically this program will find a solution, but the selection of films in this particular puzzle have been chosen so there is a unique subset that give the required answer.

        Note: Placing leading zeroes in the film values does not work in Python 3 ([[ 0011100110 ]] is not a valid integer literal, although [[ int('0011100110') ]] does work). In Python 2.7 [[ 0011100110 ]] is a valid integer literal, but is interpreted as an octal (base 8) number, so although the program does run, it will not give the correct answer. (Which is presumably why this was made an error in Python 3).

        To allow leading zeroes we could do everything in octal, by prefixing with 0o (or in hexadecimal, using the 0x prefix – there is no corresponding 0d prefix for decimal), including the target value 2222222222.

        Like

  • Unknown's avatar

    Jim Randell 6:42 am on 22 July 2021 Permalink | Reply
    Tags:   

    Teaser 2805: Greek urns 

    From The Sunday Times, 26th June 2016 [link] [link]

    I have three Greek urns. I took some balls (consisting of an odd number of red balls and some black balls) and I placed one or more ball in the first urn, one or more in the second, and the rest in the third. If you chose an urn at random and then a ball at random from that urn, then overall there would be a 50 per cent chance of getting a red ball.

    Then I moved some black balls from one of the urns to another. In this new situation, if you chose an urn and then a ball there was a 75 per cent chance of getting a red. In fact, with this set of balls and urns it would be impossible to get a higher percentage than that.

    How many red balls and how many black balls were there?

    [teaser2805]

     
    • Jim Randell's avatar

      Jim Randell 6:43 am on 22 July 2021 Permalink | Reply

      The following Python program runs in 126ms.

      Run: [ @replit ]

      from enigma import Rational, irange, inf, printf
      
      Q = Rational()
      
      # return ((r1, b1), (r2, b2), (r3, b3)) from R red, B black balls
      # as (P1, P2) where
      # P1 = configuration with P = 1/2
      # P2 = configuration with P = 3/4
      def solve(R, B):
        # record probabilities: P1 = 1/2, P2 = 3/4, P3 > 3/4
        (P1, P2) = (list(), list())
      
        # place n1 balls in urn 1
        T = R + B
        for n1 in irange(1, T - 2):
          # how many are red?
          for r1 in irange(0, min(R, n1)):
            # and the rest are black
            b1 = n1 - r1
            # probability of choosing a red ball from urn 1
            p1 = Q(r1, n1)
      
            # place n2 balls in urn 2 (n2 <= n1)
            for n2 in irange(1, min(n1, T - n1)):
              # how many are red?
              for r2 in irange(0, min(R - r1, n2)):
                # and the rest are black
                b2 = n2 - r2
                # probability of choosing a red ball from urn 2
                p2 = Q(r2, n2)
      
                # and urn 3 (n3 <= n2)
                n3 = T - n1 - n2
                if n3 > n2: continue
                r3 = R - r1 - r2
                b3 = n3 - r3
                if b3 < 0: continue
                p3 = (0 if n3 == 0 else Q(r3, n3))
      
                # total probability of drawing a red ball
                p = Q(p1 + p2 + p3, 3)
                if p == Q(1, 2):
                  P1.append(((r1, b1), (r2, b2), (r3, b3)))
                elif p == Q(3, 4):
                  P2.append(((r1, b1), (r2, b2), (r3, b3)))
                elif p > Q(3, 4):
                  return
      
        # look for a viable pair from P1 x P2
        for ((r1, b1), (r2, b2), (r3, b3)) in P1:
          for ((r1_, b1_), (r2_, b2_), (r3_, b3_)) in P2:
            # the number of red balls in each urn must be the same
            if not(r1_ == r1 and r2_ == r2 and r3 == r3_): continue
            # and one of the sets of blacks is the same
            if not(b1 == b1_ or b2 == b2_ or b3 == b3_): continue
            # viable configuration
            yield (((r1, b1), (r2, b2), (r3, b3)), ((r1_, b1_), (r2_, b2_), (r3_, b3_)))
      
      # find the first viable red/black configuration
      def run():
        # total number of balls
        for T in irange(2, inf):
          # number of red balls is odd
          for R in irange(1, T - 1, step=2):
            # and the rest are black
            B = T - R
      
            # look for solutions with R reds, B blacks
            n = 0
            for (((r1, b1), (r2, b2), (r3, b3)), ((r1_, b1_), (r2_, b2_), (r3_, b3_))) in solve(R, B):
              printf("({r1}r + {b1}b) ({r2}r + {b2}b) ({r3}r + {b3}b) -> ({r1_}r + {b1_}b) ({r2_}r + {b2_}b) ({r3_}r + {b3_}b)")
              n += 1
      
            if n > 0:
              printf("R={R} B={B} [T={T}; {n} configurations]")
              return
      
      run()
      

      Solution: There were 7 red balls, and 15 black balls.

      Possible configurations are:

      A: (5r + 10b) → P(red) = 1/3
      B: (1r + 5b) → P(red) = 1/6
      C: (1r + 0b) → P(red) = 1
      Total P(red) = (1/3 + 1/6 + 1) / 3 = 1/2

      Move 5b from B to A:

      A: (5r + 15b) → P(red) = 1/4
      B: (1r + 0b) → P(red) = 1
      C: (1r + 0b) → P(red) = 1
      Total P(red) = (1/4 + 1 + 1) / 3 = 3/4

      A: (5r + 9b) → P(red) = 5/14
      B: (1r + 6b) → P(red) = 1/7
      C: (1r + 0b) → P(red) = 1
      Total P(red) = (5/14 + 1/7 + 1) / 3 = 1/2

      Move 6b from B to A:

      A: (5r + 15b) → P(red) = 1/4
      B: (1r + 0b) → P(red) = 1
      C: (1r + 0b) → P(red) = 1
      Total P(red) = (1/4 + 1 + 1) / 3 = 3/4


      The program stops after the first red/black configuration it finds, but we can let it look for further solutions, but it gets a bit slow for T > 100 (and doesn’t find any further solutions).

      Assuming the maximum probability of choosing a red ball for R red balls and B black balls is given by the following configuration:

      ((R − 2)r + (B)b) (1r + 0b) (1r + 0b)

      (so we require R ≥ 2).

      Then for the overall probability of choosing a red to be 3/4 we have:

      (1/3)(((R − 2) / (R + B − 2)) + (1/1) + (1/1)) = 3/4
      (R − 2) / (R + B − 2) = 1/4
      4R − 8 = R + B − 2
      B = 3R − 6

      Which means for any R value, there is only one corresponding B value.

      So the final configuration is:

      ((R − 2)r + (3R − 6)b) (1r + 0b) (1r + 0b)

      All the black balls are in one urn, so we can suppose x of them were moved from an urn containing (1r + xb), making the original configuration:

      ((R − 2)r + (3R − 6 − x)b) (1r + xb) (1r + 0b)

      And the overall probability of choosing a red is 1/2, so:

      (1/3)(((R − 2) / (4R − 8 − x)) + (1 / (1 + x)) + 1) = 1/2
      ((R − 2) / (4R − 8 − x)) + (1 / (1 + x)) = 1/2
      (2R − 4)(1 + x) + 2(4R − 8 − x) = (1 + x)(4R − 8 − x)
      10R − 6x + 2Rx − 20 = 4R − 8 − x + 4Rx − 8x − x²
      x² + (3 − 2R)x + (6R − 12) = 0

      So for any R value, there are at most 2 viable x values.

      This allows us to quickly check for higher solutions (but we don’t find any):

      from enigma import irange, quadratic, printf
      
      for R in irange(3, 1000, step=2):
        B = 3 * R - 6
        # look for x values
        for x in quadratic(1, 3 - 2 * R, 6 * R - 12, domain="Z"):
          if x > 0:
            printf("R={R} B={B}; x={x}")
      

      Continuing with the analysis, we can show there are no higher solutions.

      R is an odd integer, say R = 2k + 3, for some non-negative integer k. Then:

      x² + (3 − 2(2k + 3))x + (6(2k + 3) − 12) = 0
      x² − (4k + 3)x + (12k + 6) = 0

      And using the quadratic formula, we see this has solutions:

      2x = (4k + 3) ± sqrt((4k − 3)² − 24)

      Both sides are integers, so: (4k − 3)² − 24 must be an odd perfect square, say (2z + 1)²:

      (4k − 3)² − 24 = (2z + 1)²
      (4k − 3)² − (2z + 1)² = 24
      (4k + 2z − 2)(4k − 2z − 4) = 24

      The LHS is the product of 2 integers, (a, b) that give 24.

      4k + 2z − 2 = a
      4k − 2z − 4 = b
      ⇒ k = (a + b + 6) / 8

      So by looking at the finite set of pairs of integers whose product is 24, we can determine all possible values of k, and hence of R, B, x, and verify that the solutions found above are the only possible solutions.

      from enigma import divisors_pairs, div, quadratic, printf
      
      # find sum of integer pairs that multiply to x
      def pairs(n):
        for (a, b) in divisors_pairs(n):
          s = a + b
          yield s
          yield -s
      
      # find possible values for k
      for s in pairs(24):
        k = div(s + 6, 8)
        if k is None or k < 0: continue
        R = 2 * k + 3
        B = 3 * R - 6
        # and corresponding values for x
        for x in quadratic(1, 3 - 2 * R, 6 * R - 12, domain="Z"):
          if x > 0:
            printf("R={R} B={B}; x={x} [k={k} s={s}]")
      

      Like

      • Frits's avatar

        Frits 11:02 am on 22 July 2021 Permalink | Reply

        @Jim, Interesting.

        We can speed up the general program by (among others):

         
        1 - do the n3 checks earlier (lines 32-34)
        2 - demand n3 > 0 and all r1, r2, r3 > 0 as otherwise a 75 per cent chance is impossible by moving black balls.
        3 - let T start from 6 as (1, 3), (1, 0), (1, 0) is the first viable option to get chance 75 per cent.
        

        Like

        • Jim Randell's avatar

          Jim Randell 3:03 pm on 22 July 2021 Permalink | Reply

          Yes. It’s more efficient to use a [[ decompose() ]] type function to distribute the balls.

          The following Python program only take 75ms, and can check up to T = 128 in a few minutes.

          Run: [ @replit ]

          from enigma import Rational, irange, inf, printf
          
          Q = Rational()
          
          # decompose t into k ascending numbers
          def decompose(t, k, m=1, ns=[]):
            if k == 1:
              if not(t < m):
                yield ns + [t]
            else:
              k_ = k - 1
              for n in irange(m, t - k_ * m):
                yield from decompose(t - n, k_, n, ns + [n])
          
          # return ((r1, b1), (r2, b2), (r3, b3)) from R red, B black balls as (P1, P2) where
          # P1 = configurations with P = 1/2
          # P2 = configurations with P = 3/4
          def solve(R, B):
            # record probabilities: P1 = 1/2, P2 = 3/4, P3 > 3/4
            (P1, P2) = (list(), list())
          
            # choose a distribution for the number of balls in each urn
            for (n3, n2, n1) in decompose(R + B, 3):
              # how many red balls in urn1?
              for r1 in irange(0, min(R, n1)):
                # and the rest are black
                b1 = n1 - r1
                # probability of choosing a red ball from urn 1
                p1 = Q(r1, n1)
          
                # how many red balls in urn2?
                for r2 in irange(0, min(R - r1, n2)):
                  # and the rest are black
                  b2 = n2 - r2
                  # and urn 3 (n3 <= n2)
                  r3 = R - r1 - r2
                  b3 = B - b1 - b2
                  if b3 < 0: continue
                  # probabilities of choosing a red ball from urn 2 and urn 3
                  p2 = Q(r2, n2)
                  p3 = (0 if n3 == 0 else Q(r3, n3))
          
                  # total probability of drawing a red ball
                  p = Q(p1 + p2 + p3, 3)
                  if p == Q(1, 2):
                    P1.append(((r1, b1), (r2, b2), (r3, b3)))
                  elif p == Q(3, 4):
                    P2.append(((r1, b1), (r2, b2), (r3, b3)))
                  elif p > Q(3, 4):
                    return
          
            # look for a viable pair from P1 x P2
            for ((r1, b1), (r2, b2), (r3, b3)) in P1:
              for ((r1_, b1_), (r2_, b2_), (r3_, b3_)) in P2:
                # the number of red balls in each urn must be the same
                if not(r1_ == r1 and r2_ == r2 and r3 == r3_): continue
                # and one of the sets of blacks is the same
                if not(b1 == b1_ or b2 == b2_ or b3 == b3_): continue
                # viable configuration
                yield (((r1, b1), (r2, b2), (r3, b3)), ((r1_, b1_), (r2_, b2_), (r3_, b3_)))
          
          # find the first viable red/black configuration
          def run():
            # total number of balls
            for T in irange(2, inf):
              for R in irange(1, T - 1, step=2):
                # and the rest are black
                B = T - R
          
                # look for solutions with R reds, B blacks
                n = 0
                for (((r1, b1), (r2, b2), (r3, b3)), ((r1_, b1_), (r2_, b2_), (r3_, b3_))) in solve(R, B):
                  printf("({r1}r + {b1}b) ({r2}r + {b2}b) ({r3}r + {b3}b) -> ({r1_}r + {b1_}b) ({r2_}r + {b2_}b) ({r3_}r + {b3_}b)")
                  n += 1
          
                if n > 0:
                  printf("R={R} B={B} [T={T}; {n} configurations]")
                  return
          
          run()
          

          Like

    • Frits's avatar

      Frits 5:21 pm on 22 July 2021 Permalink | Reply

      The following Python program runs in 1 second for numbers 0-19 and 100ms for numbers 0-9 (with PyPy).

      from enigma import SubstitutedExpression
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [# (red, black) balls: (AB, GH), (CD, IJ), (EF, KL))
         
         # (AB / (AB + GH) + CD / (CD + IJ) + EF / (EF + KL)) / 3 = 0.50
         #
         "2 * (AB * (CD + IJ) * (EF + KL) + \
               CD * (AB + GH) * (EF + KL) + \
               EF * (AB + GH) * (CD + IJ)) == 3 * (AB + GH) * (CD + IJ) * (EF + KL)",
        
         "AB > 0",
         "CD > 0",
         "EF > 0",
         # odd number of red balls
         "(AB + CD + EF) % 2",
         # force only one red if there are no blacks
         "KL > 0 or EF == 1",
         
         # (AB / (AB + GH - XY) + CD / (CD + IJ + XY) + EF / (EF + KL)) / 3 = 0.75  
         # move XY black balls form one urn to another
         "0 < XY <= GH",
         "4 * (AB * (CD + IJ + XY) * (EF + KL) + \
               CD * (AB + GH - XY) * (EF + KL) + \
               EF * (AB + GH - XY) * (CD + IJ + XY)) == \
               9 * (AB + GH - XY) * (CD + IJ + XY) * (EF + KL)",
         # no higher chance than 0.75    
         "not any(4 * (AB * (CD + IJ + x) * (EF + KL) + \
                  CD * (AB + GH - x) * (EF + KL) + \
                  EF * (AB + GH - x) * (CD + IJ + x)) > \
                  9 * (AB + GH - x) * (CD + IJ + x) * (EF + KL) \
                  for x in range(1, GH + 1))",      
        ],
      
        answer="AB + CD + EF + GH + IJ + KL, AB + CD + EF, \
        ((AB, GH), (CD, IJ), (EF, KL)), ((AB, GH - XY), (CD, IJ + XY), (EF, KL))",
        accumulate=min,
        verbose=0,
        # checks numbers 0-19
        d2i=dict([(k, "ACEGIKX") for k in range(2, 10)]),
        distinct="",   # allow variables with same values
      )
      
      
      # solve the puzzle
      r = p.run()
      
      # print answer
      ans = list(r.accumulate)
      print(f"smallest solution: {ans[1]} red balls and "
            f"{ans[0] - ans[1]} black balls")
      

      Like

  • Unknown's avatar

    Jim Randell 10:08 am on 20 July 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 811: When rules were rules … 

    From The Sunday Times, 6th February 1977 [link]

    No organisation can be efficient without clear-cut rules setting out exactly the rewards and the responsibilities of those who have the honour to be members of the team.

    I came across the other day a copy of the rules which, as the Managing Director of Our Factory, I had put on the Society’s Notice Board for all to see and understand. But this was many years ago when the pound was worth something, and when Rules were obeyed.

    There were five employees in the Factory then, and their names were Alf, Bert, Charlie, Duggie and Ernie. Their jobs, not necessarily respectively, were those of Door-Opener, Door-Shutter, Door-Knob-Polisher, Bottle-Washer and Welfare Officer. The notice which I put up read as follows:

    RULES:
    1. Charlie is to get 10% more than the worst paid of you all.
    2. Alf is to be paid more than Duggie.
    3. The Bottle-Washer is to get 5% more than 10% less than Bert.
    4. Duggie is to get either £1 more or £1 less than Ernie.
    5. The Door-Opener’s wages are to be an odd multiple of 10p.
    6. Ernie is to get 20% more than £1 less than the Door-Knob-Polisher.
    7. The Door-Shutter is to be the best paid of you all.
    8. Your wages are all to be different and each one is to be a multiple of 10p.
    9. No one is to get more than £20 or less than £10 per week.

    What were their weekly wages?

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

    [teaser811]

     
    • Jim Randell's avatar

      Jim Randell 10:09 am on 20 July 2021 Permalink | Reply

      Working in units of 10p means the wages are in the range 100 – 200.

      This Python program runs in 54ms.

      Run: [ @replit ]

      from enigma import irange, div, subsets, int2base, printf
      
      # possible values
      values = irange(100, 200)
      
      # find possible (x, f(x)) pairs
      def pairs(f):
        for x in values:
          y = f(x)
          if y is not None and y in values:
            yield (x, y)
      
      # consider BW/B pairs (BW = 1.05 * 0.9 * B)
      for (BW, B) in pairs(lambda x: div(200 * x, 21 * 9)):
      
        # consider DKP/E pairs (E = 1.2 * (DKP - 10))
        for (DKP, E) in pairs(lambda x: div(6 * (x - 10), 5)):
      
          # consider D values
          for D in (E - 10, E + 10):
            if D not in values: continue
      
            # consider A values (A > D)
            for A in values:
              if not(A > D): continue
              C = div(11 * min(B, D, E), 10)
              if C is None or C not in values: continue
              # the set of wages
              ws = set([A, B, C, D, E])
              if len(ws) != 5: continue
              # remaining jobs
              js = ws.difference([BW, DKP])
              if len(js) != 3: continue
              for (DS, DO, WO) in subsets(js, size=len, select="P"):
                # check DS, DO values
                if not(DO % 2 == 1 and DS == max([A, B, C, D, E])): continue
                # jobs: map: wage -> job
                jobs = dict(zip([BW, DKP, DS, DO, WO], "BW DKP DS DO WO".split()))
      
                # output solution
                fmt = lambda x: int2base(x * 10, group=2, sep='.')
                printf("A={A} ({j})", A=fmt(A), j=jobs[A])
                printf("B={B} ({j})", B=fmt(B), j=jobs[B])
                printf("C={C} ({j})", C=fmt(C), j=jobs[C])
                printf("D={D} ({j})", D=fmt(D), j=jobs[D])
                printf("E={E} ({j})", E=fmt(E), j=jobs[E])
                printf()
      

      Solution: The wages per week are: Alf = £18.90; Bert = £20.00; Charlie = £12.10; Duggie = £11.00; Ernie = £12.00.

      And their jobs:

      Alf = Bottle-Washer
      Bert = Door-Shutter
      Charlie = Door-Opener
      Duggie = Door-Knob-Polisher
      Ernie = Welfare Officer

      Like

    • Frits's avatar

      Frits 4:00 pm on 21 July 2021 Permalink | Reply

      Manual solution.

      # Consider amounts in 10p.
      
      # Ernie = 1.2 * (Door-Knob-Polisher - 10)                              (rule 6)
      # Ernie = 108 + 6 * YZ,     YZ  less than 16  
      # Ernie must be even so also Duggie must be even                       (rule 4)
      
      # Bottle-Washer = 1.05 * 0.9 * Bert, 189 * Bert = 200 * Bottle-Washer  (rule 3)
      # 189 and 200 are coprime so Bottle-Washer = 189, Bert = 200
      # Bottle-Washer is odd so not Bert, Duggie or Ernie
      # Charlie has to be a multiple of 11                                  (rule 11)
      # so --- Bottle-Washer must be Alf ---, Alf = 189
      # so --- Door-Shutter must be Bert ---                                 (rule 7)
      
      # Door-Opener's wages are to be an odd multiple of 10p                 (rule 5)
      # Alf and Bert have diffeent jobs, Duggie and Ernie are even
      # so --- Charlie is the Door-Opener ---
      
      # Ernie is to get 20% more than £1 less than the Door-Knob-Polisher    (rule 6)
      # three jobs already have been assigned
      # so --- Duggie is the Door-Knob-Polisher ---
      # so --- Ernie is the Welfare Officer ---
      # Ernie is paid more than Door-Knob-Polisher
      # so worst paid has to be Duggie 
      # so Ernie = Duggie + 10                                               (rule 4)
      # Door-Knob-Polisher = Ernie / 1.2 + 10 = 5 / 6 * (108 + 6 * YZ) + 10 =
      # 5 * YZ + 100 so YZ must be even (as Duggie is even)
      
      # Charlie = 1.1 * Duggie = 1.1 * (Ernie - 10)                    (rule 1 and 4)
      # so Ernie must be a multiple of 10
      # so YZ must be 2 or 12    (Ernie = 108 + 6 * YZ and YZ is even)
      # so (Charlie, Duggie, Ernie) must be resp. (121, 110, 120) or (187, 170, 180)
      # only in first case rule 6 is obeyed
      
      # so Alf = £18.90, Bert = £20, Charlie = £12.10, Duggie = £11 and Ernie = £12
      

      Like

  • Unknown's avatar

    Jim Randell 10:38 am on 18 July 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 40: Christmas greetings 

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

    The greetings above quite literally sum up my festive feelings, for they are, in fact, two simple addition sums in which each  of which each digit has been replaced by an “Adventitious” letter. You are to replace each letter by the digit it stands for.

    Having found the value of a letter in one of the sums, you  must not assume that the letter necessarily retains that same  value in the other sum. The two examples are quite  independent apart from the one condition that the difference between the sum of the digits in MERRY and the sum of the digits in XMAS must be made as small as possible.

    As I have already said — A Very Very Merry Xmas To One And All.

    [teaser40]

     
    • Jim Randell's avatar

      Jim Randell 10:40 am on 18 July 2021 Permalink | Reply

      The following Python program uses the [[ SubstitutedSum ]] solver from the enigma.py library to solve the alphametic sums, and then finds minimal distance solutions.

      It runs in 76ms.

      Run: [ @replit ]

      from itertools import product
      from enigma import SubstitutedSum, group, unpack, printf
      
      # sum 1: group solutions by M+E+R+R+Y
      p1 = SubstitutedSum(["A", "VERY", "VERY"], "MERRY")
      s1s = group(p1.solve(verbose=0), by=(lambda s: sum(s[x] for x in 'MERRY')))
      
      # sum 2: group solutions by X+M+A+S
      p2 = SubstitutedSum(["XMAS", "TOONE"], "ANDALL")
      s2s = group(p2.solve(verbose=0), by=(lambda s: sum(s[x] for x in 'XMAS')))
      
      # choose the pair with the minimal distance
      (k1, k2) = min(product(s1s.keys(), s2s.keys()), key=unpack(lambda x, y: abs(x - y)))
      
      # output solutions
      for (s1, s2) in product(s1s[k1], s2s[k2]):
        printf("(1) {s1}; (2) {s2}",
          s1=p1.substitute(s1, p1.text),
          s2=p2.substitute(s2, p2.text),
        )
      

      Solution: (i) 8 + 7492 + 7492 = 14992; (ii) 7618 + 95504 = 103122.


      The first alphametic sum has 2 solutions:

      2 + 7498 + 7498 = 14998 | M+E+R+R+Y = 31
      8 + 7492 + 7492 = 14992 | M+E+R+R+Y = 25

      The second alphametic sum has 2 solutions:

      7614 + 95508 = 103122 | X+M+A+S = 18
      7618 + 95504 = 103122 | X+M+A+S = 22

      And the minimum distance is between M+E+R+R+Y = 25, and X + M + A + S = 22.

      Like

    • GeoffR's avatar

      GeoffR 4:58 pm on 18 July 2021 Permalink | Reply

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      % 1st sum:  A + VERY + VERY == MERRY
      var 1..9:A; var 1..9:V; var 0..9:E;
      var 1..9:R; var 0..9:Y; var 1..9:M;
      
      constraint all_different([A, V, E, R, Y, M]);
      
      var 1000..9999:VERY = 1000*V + 100*E + 10*R + Y;
      var 10000..99999:MERRY = 10000*M + 1000*E + 110*R + Y;
      
      constraint A + VERY + VERY == MERRY;
      
      % 2nd Sum:  XMAS + TOONE = ANDALL
      % using lower case letters to be different from 1st equation
      var 1..9:x; var 0..9:m; var 1..9:a; var 0..9:s; var 1..9:t;
      var 0..9:o; var 0..9:n; var 0..9:e; var 0..9:d; var 0..9:l;
      
      constraint all_different ([x, m, a, s, t, o, n, e, d,l]);
      
      var 1000..9999:xmas = 1000*x + 100*m + 10*a + s;
      var 10000..99999:toone = 10000*t + 1100*o + 10*n + e;
      var 100000..999999:andall = 100000*a + 10000*n + 1000*d + 100*a + 11*l;
      
      constraint xmas + toone == andall;
      
      % Minimise difference in sum of digits between MERRY and XMAS
      solve minimize(abs(M + E + R + R + Y - x - m - a - s));
      
      output ["Sum 1 is " ++ show(A) ++ " + " ++ show(VERY) ++ " + " 
      ++ show(VERY) ++ " = " ++ show(MERRY) ++ " " ++ "\n" ++ 
      "Sum 2 is " ++show(xmas) ++ " + " ++ 
      show(toone) ++ " = " ++ show(andall)
      ++ "\n" ++ "Difference of sum of digits between MERRY and XMAS  = "   
      ++ show(M + E + R + R + Y - x - m - a - s) ];
      
      % Sum 1 is 8 + 7492 + 7492 = 14992 
      % Sum 2 is 7614 + 95508 = 103122
      % Difference of sum of digits between MERRY and XMAS  = 7
      % ----------
      % Sum 1 is 8 + 7492 + 7492 = 14992 
      % Sum 2 is 7618 + 95504 = 103122
      % Difference of sum of digits between MERRY and XMAS  = 3  << answer
      % ----------
      % ==========
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:56 pm on 16 July 2021 Permalink | Reply
    Tags:   

    Teaser 3069: Fit for purpose 

    From The Sunday Times, 18th July 2021 [link] [link]

    George and Martha bought a new toy for their son Clark. It consisted of a rectangular plastic tray with dimensions 15×16cm and eight plastic rectangles with dimensions 1×2cm, 2×3cm, 3×4cm, 4×5cm, 5×6cm, 6×7cm, 7×8cm and 8×9cm. The rectangles had to be placed inside the tray without any gaps or overlaps. Clark found every possible solution and he noticed that the number of different solutions which could not be rotated or reflected to look like any of the others was the same as his age in years.

    How old was Clark?

    [teaser3069]

     
    • Jim Randell's avatar

      Jim Randell 5:19 pm on 16 July 2021 Permalink | Reply

      I reused the code from rectpack.py, originally written for Enigma 1491, and also used in Teaser 2650.

      Each solution also appears rotated, reflected, and rotated and reflected, so the total number of solutions found is 4× the answer.

      The following Python program runs in 199ms.

      Run: [ @replit ]

      from enigma import (irange, ediv, printf)
      from rectpack import (pack, output_grid)
      
      # width/height of the tray
      (X, Y) = (15, 16)
      
      # rectangles
      rs = list((x, x + 1) for x in irange(1, 8))
      
      # count the solutions
      n = 0
      for (i, s) in enumerate(pack(X, Y, rs), start=1):
        printf("{i}: {s}")
        output_grid(X, Y, s, end="")
        n += 1
      
      printf("age = {x}", x=ediv(n, 4))
      

      Solution: Clark was 10.

      The 10 solutions arise due to the following 2 packings:

      The first has 3 (coloured) sub-rectangles that can be flipped over, giving 2×2×2 = 8 packings.

      The second has only 1 sub-rectangle, so gives 2 packings.

      Like

      • Jim Randell's avatar

        Jim Randell 6:32 pm on 16 July 2021 Permalink | Reply

        And here is an alternate version that calculates a canonical form for each solution, and counts the number of different canonical forms.

        Run: [ @replit ]

        from enigma import (irange, printf)
        from rectpack import (pack, output_grid)
        
        # width/height of the tray
        (X, Y) = (15, 16)
        
        # rectangles
        rs = list((x, x + 1) for x in irange(1, 8))
        
        # rotation / reflection
        rotate = lambda s: tuple(sorted((X - x - w, Y - y - h, w, h) for (x, y, w, h) in s))
        reflect = lambda s: tuple(sorted((X - x - w, y, w, h) for (x, y, w, h) in s))
        
        # canonical form of s
        def canonical(s):
          s = tuple(sorted(s))
          r = rotate(s)
          return min(s, r, reflect(s), reflect(r))
        
        # count the canonical forms
        ss = list()
        for (i, s) in enumerate(pack(X, Y, rs), start=1):
          r = canonical(s)
          if r not in ss:
            ss.append(r)    
            printf("{i} -> {x}", x=len(ss))
            output_grid(X, Y, s)
          else:
            printf("{i} -> {x}", x=ss.index(r) + 1)
          printf()
        
        printf("age = {x}", x=len(ss))
        

        Like

    • Frits's avatar

      Frits 12:31 pm on 19 July 2021 Permalink | Reply

      With analysis to place the biggest piece in top left corner.

      This program ran in 181ms.

       
      from itertools import product
      
      (X, Y) = (15, 16)        # width/height of the tray
      M = 9                    # longest side of rectangle
      
      # return grid coordinates (top left, bottom right) where piece w x h will fit
      def grid_coords(grid, w, h):
        res = []
        for i in range(M, X + M):
          for j in range(M, Y + M):
            if grid[i][j] == 0:
              # horizontal and vertical
              for k in range(2):
                if all(grid[x][y] == 0 for x in range(i, i + h)
                                       for y in range(j, j + w)):
                 res.append([(i - M, j - M), (i - M + h - 1, j - M + w - 1)])
      
                (w, h) = (h, w)   # mirror
        return res       
      
      # print grid without borders   
      def grid_print(grid):
        for i, g in enumerate(grid):
          if M <= i < Y + M - 1:
            print(f"{g[M:-M]}")   
        print()   
      
      # fit k pieces into empty spots in the grid   
      def fit(r, k, s):
        if k == 1:
          # check if last piece fits
          coords = grid_coords(s, r[0][0], r[0][1])
          if coords:
            (tl, rb) = (coords[0][0], coords[0][1])
            for (i, j) in product(range(tl[0], rb[0] + 1), range(tl[1], rb[1] + 1)):
              s[i + M][j + M] = k   
            yield [r.copy() for r in s]       # return copy !!
            # backtrack
            for (i, j) in product(range(tl[0], rb[0] + 1), range(tl[1], rb[1] + 1)):
              s[i + M][j + M] = 0
        else:
          (w, h) = r[0]   # pick first remaining piece from list
          for tl, rb in grid_coords(s, w, h):
            if k == N:
              # only allow the biggest piece 9x8 mostly in the top left quadrant
              # in order to prevent rotated or reflected versions
              #
              # if biggest piece is not in the top left corner (not touching (0, 0)):
              # - if biggest piece lies vertical then placing the 8x7 piece
              #   results in a 9x1 stroke --> impossible
              # - if biggest piece lies horizontal there is a stroke 8xa to the left
              #   and a stroke 8xb to the left (where a + b = 7)
              #   strokes of 8x1, 8x2 or 8x3 are impossible to fill --> impossible
            
              if tl != (0, 0): continue
           
            # update piece in the grid s
            for (i, j) in product(range(tl[0], rb[0] + 1), range(tl[1], rb[1] + 1)):
              s[i + M][j + M] = k   
          
            yield from fit(r[1:], k - 1, s)  
            # backtrack
            for (i, j) in product(range(tl[0], rb[0] + 1), range(tl[1], rb[1] + 1)):
              s[i + M][j + M] = 0
             
           
      # rectangles (biggest first)
      rs = list((x, x + 1) for x in range(8, 0, -1))
      N = len(rs)
      
      # initialize grid with borders of size M
      grid = [[9 for i in range(Y + 2 * M)] for j in range(X + 2 * M)]
      # initialize inner grid to 0
      for i in range(X):
        for j in range(Y):
          grid[i + M][j + M] = 0
             
      # generate solutions
      sols = list(fit(rs, N, grid))   
      
      print("Clark was", len(sols), "years old. \n\nexample:\n")
      grid_print(sols[0])
      

      Like

  • Unknown's avatar

    Jim Randell 10:02 am on 15 July 2021 Permalink | Reply
    Tags:   

    Teaser 2801: End of season 

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

    In this football league each team plays each of the others once (with three points for a win and one for a draw). The end-of-season table has teams in decreasing order of points (ties being determined by goals scored). Here are some of the entries from three rows of the table, but with digits consistently replaced by letters.

    How many teams are in the league, and what was the score when Villa played Wanderers?

    [teaser2801]

     
    • Jim Randell's avatar

      Jim Randell 10:03 am on 15 July 2021 Permalink | Reply

      If there are n teams in the league, then each team plays (n − 1) matches. And we know at the end of the season Villa has played (E + A + S) matches.

      Villa has T points and Wanderers have T draws (each worth 1 point), so they must have no wins, otherwise they would have more points than Villa. So Wanderers also have T points, so Villa must have scored more goals (E > M).

      Villa won E matches and also has E goals for, so each win must be 1-0 (and each draw 0-0).

      The match between Villa and Wanderers must be either a win for Villa (1-0) or a draw (0-0) (as Wanderers have no wins).

      This Python program uses the [[ SubstitutedExpression ]] solver from the enigma.py library to solve the constraints inherent in the table, and then checks if it is possible for the Villa vs. Wanderers match to be a win (for Villa) or a draw.

      It runs in 55ms.

      Run: [ @replit ]

      from enigma import SubstitutedExpression, printf
      
      # alphametic constraints inherent in the table
      p = SubstitutedExpression([
          # points
          "3 * E + A = T", # points for Villa
          "3 * M + Y >= T", # United has the most points
          # number of matches (= E + A + S)
          "E + A + S >= M + Y",
          "E + A + S == T + I", # W must have 0 wins
          # goals
          "E > M", # W must have conceded more goals than they scored
          "R > S > 0",
          "E > I > 0",
        ],
        answer="(A, E, I, M, R, S, T, Y)",
        verbose=0,
      )                         
      
      # find candidate values
      for (s, (A, E, I, M, R, S, T, Y)) in p.solve():
        # with n teams, each team plays n - 1 matches
        n = E + A + S + 1
        printf("n={n} [A={A} E={E} I={I} M={M} R={R} S={S} T={T} Y={Y}]")
        # V vs W = 1-0 ?
        if E > 0 and I > 0: printf("-> V vs W = 1-0 (win)")
        # V vs W = 0-0 ?
        if A > 0 and T > 0: printf("-> V vs W = 0-0 (draw)")
      

      Solution: There were 11 teams in the league. Villa beat Wanderers 1-0.


      The assignment of letters to digits is:

      A = 0, E = 3, I = 1, M = 2, R = 8, S = 7, T = 9
      Y = 4, 5, 6

      We could use the [[ Football ]] helper class from the enigma.py to investigate scorelines in the V vs. W match, but we see from the solutions to the alphametic constraints that A = 0, so V has no drawn matches, hence the score must be a 1-0 win to V.

      Like

      • Frits's avatar

        Frits 2:02 pm on 15 July 2021 Permalink | Reply

        @Jim,

        The condition on line 26 seems to be unnecessary as it is already one of the expressions in SubstitutedExpression.

        Conditions on line 26 and line 28 regarding V vs W are not immediately clear to me (without explanation)

        Like

        • Jim Randell's avatar

          Jim Randell 2:37 pm on 15 July 2021 Permalink | Reply

          @Frits: We’ve already determined that V vs W match must either be a 1-0 win for V or a 0-0 draw.

          In order for the match to be a win, V must have >0 wins (E > 0) and W must have >0 losses (I > 0).

          Similarly, in order for the the match to be a draw V and W must have >0 draws (A > 0 ∧ T > 0).

          Like

          • Frits's avatar

            Frits 3:07 pm on 15 July 2021 Permalink | Reply

            @Jim, you have explained if win/draw for V-W then p/q. This doesn’t necessarily mean if p/q then win/draw for V-W.

            Like

          • Jim Randell's avatar

            Jim Randell 3:33 pm on 15 July 2021 Permalink | Reply

            In each of the three cases the only possibility is that the match is a win for V (a draw is not possible, as in all cases A = 0).

            Therefore the the match is necessarily a win for V.

            Like

  • Unknown's avatar

    Jim Randell 10:27 am on 13 July 2021 Permalink | Reply
    Tags:   

    Brain-Teaser 808: Placing the top ten 

    From The Sunday Times, 16th January 1977 [link]

    Last week’s Top Ten consisted of the following performers:

    1. Adda
    2. Bay Leaf Rissolers
    3. Cate Gooseberry
    4. Demi-Sour
    5. Englebert Smith
    6. Fatherhood of Man
    7. Gee-Bees
    8. How
    9. It
    10. John Revolter

    This week the same ten were in the charts, but all their placings were different. Five had gone up and five had gone down. Adda had the biggest single drop of all, but Fatherhood went up.

    When I looked at the products of last week’s and this week’s placings, I found that:

    (a) Gee-Bees’ product was twice Cate Gooseberry’s;
    (b) It’s was three times Demi-Sour’s;
    (c) Englebert’s was even;
    (d) The total of the products was 272.

    What was this week’s order?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 1 (1980). The puzzle text above is taken from the book, but modified to remove an error that was introduced in the rewording.

    [teaser808]

     
    • Jim Randell's avatar

      Jim Randell 10:28 am on 13 July 2021 Permalink | Reply

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

      It runs in 70ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      # if the positions are:
      # last week = 1  2  3  4  5  6  7  8  9  10
      # this week = A  B  C  D  E  F  G  H  I  J
      
      SubstitutedExpression
      
      --base=11
      --digits="1-10"
      
      # no-one kept their spot
      --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"
      --invalid="10,J"
      
      # "5 went up, 5 went down"
      "sum([A > 1, B > 2, C > 3, D > 4, E > 5, F > 6, G > 7, H > 8, I > 9, J > 10]) == 5"
      
      # "A had the biggest drop of all"
      "A - 1 > max(B - 2, C - 3, D - 4, E - 5, F - 6, G - 7, H - 8, I - 9, J - 10)"
      
      # "F went up"
      "F < 6"
      
      # "G's product was 2 times C's" [not: 3 times]
      "7 * G == 2 * 3 * C"
      
      # "I's product was 3 times D's"
      "9 * I == 3 * 4 * D"
      
      # "E's product was even"
      "(5 * E) % 2 == 0"
      
      # "the total of the products was 272"
      "1 * A + 2 * B + 3 * C + 4 * D + 5 * E + 6 * F + 7 * G + 8 * H + 9 * I + 10 * J == 272"
      
      # [optional]
      --template=""
      

      Solution: (1) John Revolter; (2) Fatherhood of Man; (3) Demi-Sour; (4) It; (5) Bay Leaf Rissolers; (6) Gee-Bees; (7) Cate Gooseberry; (8) Englebert Smith; (9) Adda; (10) How.

      Like

      • Frits's avatar

        Frits 11:54 am on 13 July 2021 Permalink | Reply

        @Jim, “A had the biggest drop of all” : shouldn’t you use the less equal sign in the formula?

        Like

        • Jim Randell's avatar

          Jim Randell 12:02 pm on 13 July 2021 Permalink | Reply

          @Frits: I don’t think so. It would allow another act to have an equally big drop.

          Like

          • Frits's avatar

            Frits 12:09 pm on 13 July 2021 Permalink | Reply

            @Jim: Semantics again. I thought “of all” would make A stand out.

            Like

          • Jim Randell's avatar

            Jim Randell 12:54 pm on 13 July 2021 Permalink | Reply

            I’ve changed it not use not. I want A’s drop to be larger than anyone else’s.

            Like

    • Frits's avatar

      Frits 2:38 pm on 13 July 2021 Permalink | Reply

      Easy to find: C=7, G=6 and D=3, I=4

      A+2B+5E+6F+8H+10J = 161 so A is odd (as E is even).
      So A=9 (A=5 has same single drop as C).

      Ups: G, D, I, F and J so B, E and H must be down.
      So H=10, E=8 and B=5.

      6F + 10J = 22 so F=2 and J=1

      Like

    • GeoffR's avatar

      GeoffR 3:49 pm on 27 July 2021 Permalink | Reply

      I found three constraints, which were correct, but not essential for the single solution.

      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..10:A; var 1..10:B; var 1..10:C; var 1..10:D; var 1..10:E;
      var 1..10:F; var 1..10:G; var 1..10:H; var 1..10:I; var 1..10:J;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I,J]);
      
      % All their placings were different
      constraint A != 1 /\ B != 2 /\ C != 3 /\ D != 4 /\ E != 5
      /\ F != 6 /\ G != 7 /\ G != 8 /\ H != 9 /\ I != 9 /\ J != 10;
      
      % A had the biggest drop of all
      % *** This constraint was correct but not needed for the single solution ***
      % constraint A - 1 > max([B-2, C-3, D-4, E-5, F-6, G-7, H-8, I-9, J-10]);
       
      % F went up
      % *** This constraint was correct but not needed for the single solution ***
      % constraint F < 6;
       
      % G's product was 2 times C's 
      constraint 7 * G == 2 * 3 * C;
       
      % I's product was 3 times D's
      constraint 9 * I == 3 * 4 * D;
       
      % E's product was even
      % *** This constraint was correct but not needed for the single solution ***
      % constraint (5 * E) mod 2 == 0;
      
      % The total of the products was 272.
      constraint 1 * A + 2 * B + 3 * C + 4 * D + 5 * E + 6 
      * F + 7 * G + 8 * H + 9 * I + 10 * J == 272;
      
      % 5 went up, 5 went down
      constraint sum([A > 1, B > 2, C > 3, D > 4, E > 5, F > 6, 
      G > 7, H > 8, I > 9, J > 10]) == 5; 
       
      solve satisfy;
      
      output ["This week’s order is [A,B,C,D,E,F,G,H,I,J] = " 
      ++ show([A,B,C,D,E,F,G,H,I,J]) ];
      
      % [A, B, C, D, E, F, G, H,  I, J] = 
      % [9, 5, 7, 3, 8, 2, 6, 10, 4, 1]
      % ----------
      % ==========
      
      
      
      

      Any more offers on who the 1977 Top Ten performers were meant to be?
      Here is my assessment:

      1. Adda means Abba
      2. Bay Leaf Rissolers means Bay City Rollers
      3. Cate Gooseberry means Kate Bush
      4. Demi-Sour ?
      5. Englebert Smith means Englebert Humperdink
      6. Fatherhood of Man means Brotherhood of Man
      7. Gee-Bees means Bee Gees
      8. How ?
      9. It ?
      10. John Revolter means John Travolta

      Like

      • Jim Randell's avatar

        Jim Randell 7:25 pm on 27 July 2021 Permalink | Reply

        I thought “Demi-Sour” could be “The Sweet” (or possibly “Demis Roussos”), but I didn’t come up with anything for “How” or “It”.

        Like

    • GeoffR's avatar

      GeoffR 8:19 pm on 27 July 2021 Permalink | Reply

      Yes , ” Demis Roussos” sounds good to me for “Demi-Sour”.

      Maybe “How” could be “The Who” ? Just “It” left.

      Like

    • Jim Randell's avatar

      Jim Randell 4:56 pm on 28 July 2021 Permalink | Reply

      I looked through the charts for 1975 and 1976, and “The Who” had singles in the charts then.

      But nothing leapt out at me for “It”. The closest was “If” by “Telly Sevalas”, but that’s the track name rather than the artist name.

      Although, now I realise the names were made for the rewording of the puzzle in the book, so they could be refer to artists in the charts after the puzzle was in published in the newspaper.

      Like

      • Jim Randell's avatar

        Jim Randell 12:56 pm on 30 July 2021 Permalink | Reply

        In late 1977 – 1979 “Chic” were in the charts.

        Both “chic” and “it” are synonyms for “fashionable”.

        Like

    • GeoffR's avatar

      GeoffR 8:22 am on 31 July 2021 Permalink | Reply

      Yes, that looks a good fit.

      The only 1977 track I found was:
      https://www.discogs.com/Kate-Taylor-Its-In-His-Kiss-The-Shoop-Shoop-Song/release/5413526
      (later made famous by Cher, of course)

      Like

  • Unknown's avatar

    Jim Randell 11:23 am on 11 July 2021 Permalink | Reply
    Tags: by: P. B. Chapman   

    Brain-Teaser 39: [Simultaneous departures] 

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

    Two train services run from my station day and night. Each train of each service is scheduled to leave a whole number of minutes after the previous one. I remarked to the station-master on the closeness of the two afternoon trains, 12:17 and 12:18. “Actually”, he replied, “there are two morning trains which leave simultaneously”.

    At what time does this happen?

    This puzzle was originally published with no title.

    [teaser39]

     
    • Jim Randell's avatar

      Jim Randell 11:24 am on 11 July 2021 Permalink | Reply

      I think there is some ambiguity in this puzzle.

      I supposed that there was only time per day that there was a train for each service leaving simultaneously, and that time was in the morning (i.e. between 00:00 and 11:59).

      And also that the interval between trains on the same service must be at least 10 minutes (in order to make trains departing 1 minute apart an occurrence of note). Originally I assumed at least 5 minutes, but this gave me multiple solutions.

      There are 24 × 60 = 1440 minutes in a day so the frequencies of the services must divide this.

      This Python program runs in 59ms.

      Run: [ @replit ]

      from enigma import (irange, divisors, intersect, subsets, peek, printf)
      
      # times for a service, given a reference time <t> and frequency <d>
      times = lambda t, d: set(x % 1440 for x in irange(t, t + 1439, step=d))
      
      # consider possible frequencies for the two services
      ds = (d for d in divisors(1440) if 9 < d < 1440)
      for (dA, dB) in subsets(ds, size=2, select="M"):
        # look for simultaneous times in both services
        ts = intersect([times(737, dA), times(738, dB)])
        # there should be only 1
        if len(ts) == 1:
          # and it should be in the morning
          (h, m) = divmod(peek(ts), 60)
          if h < 12:
            printf("A: {dA}; B: {dB} -> {ts} {h:02d}:{m:02d}")
      

      Solution: The two services leave simultaneously at 08:33.


      The timetable of the trains are as follows:

      A: (32m) … 08:33 | 09:05 | 09:37 | 10:09 | 10:41 | 11:13 | 11:45 | 12:17 …
      B: (45m) … 08:33 | 09:18 | 10:03 | 10:48 | 11:33 | 12:18 …

      My alternatives (with frequencies less than 10m) are:

      A: (5m) … 02:42 … 12:17 …
      B: (4h48m) … 02:42 | 07:30 | 12:18 …

      A: (9m) … 01:38 … 12:17 …
      B: (2h40m) … 01:38 | 04:18 | 06:58 | 09:38 | 12:18 …

      Like

      • Frits's avatar

        Frits 12:38 pm on 11 July 2021 Permalink | Reply

        @Jim, have you considered processing only the first 2 characters of the subsets select parameter? Then you can use select=”Product” instead of select=”M”.

        I first thought was a tuple but it turns out to be a generator.

        Using

        ds = list(d for d in divisors(1440) if 9 < d < 1440)
        

        you can still print the content of without emptying it.

        —————

        I think 04:02 also is a solution, intersection of (dA=45, dB=16) contains 242 and 962 which is valid as 962 >= 720.

        from enigma import SubstitutedExpression, divisors
        
        # the alphametic puzzle
        p = SubstitutedExpression(
          [
          "UVW in divisors(1440) and 9 < UVW < 1440",
          "XYZ in divisors(1440) and 9 < XYZ < 1440",
          
          #"737 - DE * UVW == 738 - FG * XYZ",
          
          "div(1 +  DE * UVW, XYZ) = FG",
          "737 - DE * UVW > 0",
          
          ],
          answer="(UVW, XYZ), (DE, FG), 737 - DE * UVW",
          d2i="",
          distinct="",   # allow variables with same values
          #reorder=0,
          verbose=0,
        )
        
        # print answers
        answs = sorted(y for _, y in p.solve())
        for a in answs: print(a)
        

        Like

    • Hugh Casement's avatar

      Hugh Casement 3:14 pm on 11 July 2021 Permalink | Reply

      The use of the definite article in “the two afternoon trains” rather implies that there are no others until evening, though we’re not told when afternoon ends and evening begins. Better, I feel, would have been “the first two trains after noon”, when we would know that the interval is more than 16 minutes, thus excluding a lot of potential solutions.

      Possibly that’s what the setter wrote, but it was altered by an editor without consultation — there’s been a history of that over the years.

      Like

  • Unknown's avatar

    Jim Randell 4:06 pm on 9 July 2021 Permalink | Reply
    Tags:   

    Teaser 3068: Valued playwrights 

    From The Sunday Times, 11th July 2021 [link] [link]

    I have given each letter of the alphabet a different whole-number value from 1 to 26. For example, P=4, L=8, A=3 and Y=24. With my numbers I can work out the value of any word by adding up the values of its letters, for example the word PLAY has a value of 39.

    It turns out that the playwrights

    BECKETT
    FRAYN
    PIRANDELLO
    RATTIGAN
    SHAKESPEARE
    SHAW

    all have the same prime value.

    Also COWARD, PINERO and STOPPARD have prime values ….

    …what are those three prime numbers?

    [teaser3068]

     
    • Jim Randell's avatar

      Jim Randell 4:15 pm on 9 July 2021 Permalink | Reply

      (See also: Teaser 2884).

      Using the [[ SubstitutedExpression ]] solver from the enigma.py library.

      The following run file executes in 355ms.

      Run: [ @replit ]

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      --base=27
      --digits="1-26"
      
      --assign="P,4"
      --assign="L,8"
      --assign="A,3"
      --assign="Y,24"
      
      # equal to SHAW (prime)
      "K + E + S + P + E + A + R + E = W" # SHAKESPEARE
      "S + H + W - F - R - Y = N" # FRAYN
      "S + H + W - P - I - R - N - E - L - L - O = D" # PIRANDELLO
      "S + H + W - R - T - T - I - A - N = G" # RATTIGAN
      "S + H + A + W - E - C - K - E - T - T = B" # BECKETT
      "is_prime(S + H + A + W)"
      
      # COWARD, PINERO, STOPPARD are also prime
      "is_prime(C + O + W + A + R + D)"
      "is_prime(P + I + N + E + R + O)"
      "is_prime(S + T + O + P + P + A + R + D)"
      
      --answer="(C + O + W + A + R + D, P + I + N + E + R + O, S + T + O + P + P + A + R + D)"
      
      # [optional]
      --template=""
      --reorder=0
      

      Solution: COWARD = 67; PINERO = 31; STOPPARD = 53.

      Like

      • Frits's avatar

        Frits 9:35 pm on 9 July 2021 Permalink | Reply

        @Jim, I don’t see why P has to be 4, etc (it says “for example”).

        Like

        • Jim Randell's avatar

          Jim Randell 9:47 pm on 9 July 2021 Permalink | Reply

          @Frits: The “for example” tells us some, but not all, of the assignments.

          If it had said: “if, for example, P=4, L=8, A=3 and Y=24, then PLAY would have the value 39″, that would be different.

          But without the “example” values, there are many possible answers.

          Like

    • GeoffR's avatar

      GeoffR 8:07 pm on 9 July 2021 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..26:A;   var 1..26:B;   var 1..26:C;   var 1..26:D;
      var 1..26:E;   var 1..26:F;   var 1..26:G;   var 1..26:H;   
      var 1..26:I;   var 1..26:J;   var 1..26:K;   var 1..26:L;
      var 1..26:M;   var 1..26:N;   var 1..26:O;   var 1..26:P;   
      var 1..26:Q;   var 1..26:R;   var 1..26:S;   var 1..26:T;   
      var 1..26:U;   var 1..26:V;   var 1..26:W;   var 1..26:X;   
      var 1..26:Y;   var 1..26:Z;
      
      constraint P == 4 /\ L == 8 /\ A == 3 /\ Y == 24;
      
      constraint all_different ([A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z]);
      
      predicate is_prime(var int: x) = x > 1 /\ 
      forall(i in 2..1 + ceil(sqrt(int2float(ub(x))))) ((i < x) -> (x mod i > 0));
      
      % BECKETT, FRAYN, PIRANDELLO, RATTIGAN, SHAKESPEARE, SHAW have same prime value
      constraint is_prime(S + H + A + W);
      constraint S + H + A + W == B + E + C + K + E + T + T;
      constraint S + H + A + W == F + R + A + Y + N;
      constraint S + H + A + W == P + I + R + A + N + D + E + L + L + O;
      constraint S + H + A + W == R + A + T + T + I + G + A + N;
      constraint S + H + A + W == S + H + A + K + E + S + P + E + A + R + E;
      
      % COWARD, PINERO and STOPPARD have prime values
      constraint is_prime(C + O + W + A + R + D);
      constraint is_prime(P + I + N + E + R + O);
      constraint is_prime(S + T + O + P + P + A + R + D);
      
      solve satisfy;
      
      output [ "COWARD = " ++ show(C + O + W + A + R + D) 
      ++ ", PINERO = " ++ show(P + I + N + E + R + O) 
      ++ ", STOPPARD = " ++ show(S + T + O + P + P + A + R + D)
      ++ ", SHAW = " ++ show(S + H + A + W) ];
      
      
      

      Like

    • Frits's avatar

      Frits 5:19 pm on 10 July 2021 Permalink | Reply

      Jim’s code with some extra analysis.

      The following run file executes in approx 15ms.

      #!/usr/bin/env python3 -m enigma -r
       
      SubstitutedExpression
       
      --base=27
      --digits="1-26"
       
      --assign="P,4"
      --assign="L,8"
      --assign="A,3"
      --assign="Y,24"
      
      # W = KESPEARE has 6 distinct characters --> W >= 21 + 2
      --invalid="1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22,W"
      # SHAW >= 47 --> S + H + W >= 44 --> H >= 9
      --invalid="1|2|3|4|5|6|7|8,H"
      # RATTIGAN in {47, 53, 59, 61} --> RTTIGN <= 55 -- > T <= 18
      --invalid="19|20|21|22|23|24|25|26,T"
      # PIRANDELLO in {47, 53, 59, 61} --> IRNDEO <= 38 -- all I,R,N,D,E,O <= 17
      --invalid="18|19|20|21|22|23|24|25|26,INDO"
      # W >= 23 so all letters in KESPEARE have to be smaller than 10, E is 1 or 2
      --invalid="10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26,RKS"
      --invalid="3|4|5|6|7|8|910|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26,E"
      
       
      # equal to SHAW (prime)
      "W - (K + E + S + P + E + A + E) = R"           # SHAKESPEARE
      "S + H + W - F - R - Y = N"                     # FRAYN
      "S + H + W - P - I - R - N - E - L - L - D = O" # PIRANDELLO
      "S + H + W - R - T - T - I - A - N = G"         # RATTIGAN
      "S + H + A + W - E - C - K - E - T - T = B"     # BECKETT
      
      # SHAW >= 45 as PIRANDELLO has 9 distinct characters
      # SHAW <= 63 as S + A <= 12
      "S + H + A + W in {47, 53, 59, 61}"     # is_prime(S + H + A + W)
      
       
      # COWARD, PINERO, STOPPARD are also prime
      # PINERO + ADLL = PIRANDELLO = SHAW -- > PINERO = SHAW - ADLL = SHW - DLL
      "is_prime(S + H + W - D - L - L)"    # is_prime(P + I + N + E + R + O)
      "is_prime(C + O + W + A + R + D)"
      "is_prime(S + T + O + P + P + A + R + D)"
       
      --answer="(C + O + W + A + R + D, 
       P + I + N + E + R + O, 
       S + T + O + P + P + A + R + D)"
       
      # [optional]
      --template=""
      #--reorder=0
      

      Like

    • GeoffR's avatar

      GeoffR 9:57 am on 11 July 2021 Permalink | Reply

      An excellent run-time.

      I do not know if it is possible with SubstitutedExpression, but an irange statement would give shorter code and look much neater than the many consecutive integers in the –invalid statements:

      e.g.
      –invalid = “irange(1,22), W” and
      –invalid = “irange(10,26), RKS” , and other examples.

      Is there a typo in line 23 for E ? ….|8|910|11|12| ….

      Like

      • Jim Randell's avatar

        Jim Randell 10:16 am on 11 July 2021 Permalink | Reply

        Yes. The [[ --invalid ]] parameters can be written more concisely:

        --invalid="1-22,W"
        --invalid="1-8,H"
        --invalid="19-26,T"
        --invalid="18-26,INDO"
        --invalid="10-26,RKS"
        --invalid="3-26,E"
        

        Like

    • Frits's avatar

      Frits 11:26 am on 11 July 2021 Permalink | Reply

      Some more analysis, leading to D being odd.

      My stored version of [ https://s2t2.home.blog/2020/08/13/teaser-2705-in-the-pub/ ] contained:

      --invalid="2|3|4|5|6|7|8|9,abcd"
      

      Later Jim updated it to

      --invalid="2-9,abcd"
      

      2 ranges doesn’t seem to be supported.

      The following run file once executed in 8.30ms.

      #!/usr/bin/env python3 -m enigma -r
       
      SubstitutedExpression
       
      --base=27
      --digits="1-26"
       
      --assign="P,4"
      --assign="L,8"
      --assign="A,3"
      --assign="Y,24"
      
      # W = KESPEARE has 6 distinct characters --> W >= 21 + 2
      --invalid="1-22,W"
      # SHAW >= 59 --> SHW >= 56 --> H >= 21
      --invalid="1-20,H"
      # RATTIGAN in {59, 61} --> RTTIGN <= 55 -- > 9 < T <= 18
      --invalid="1|2|3|4|5|6|7|8|9|19|20|21|22|23|24|25|26,T"
      # PIRANDELLO in {59, 61} --> IRNDEO <= 38 -- all I,R,N,D,E,O <= 17
      # out of I,N,D,O at most 2 may be single digit (besides ERKSPLA)
      # out of I,N,D,O at most 2 may be double digit numbers --> exactly 2
      --invalid="18-26,INO"
      # D must be odd (otherwise S + H + W - D - L - L is even and not prime)
      # if D is 7 or 15 then S + H + W - D - L - L is not prime
      --invalid="2|4|6|7|8|10|12|14|15|16|18|19|20|21|22|23|24|25|26,D"
      # W >= 23 so all letters in KESPEARE have to be smaller than 10, E is 1 or 2
      --invalid="10-26,RKS"
      --invalid="3-26,E"
      # all single digit numbers are already in use (ERKSPLA and 2 out of INDO)
      --invalid="1-9,BCFG"
      
       
      # equal to SHAW (prime)
      "W - (K + E + S + P + E + A + E) = R"            # SHAKESPEARE
      "S + H + W - R - Y - N = F"                      # FRAYN
      "S + H + W - P - I - R - N - E - L - L - D = O"  # PIRANDELLO
      "S + H + W - R - T - T - I - A - N = G"          # RATTIGAN
      "S + H + A + W - E - C - K - E - T - T = B"      # BECKETT
      
      # SHAW >= 45 + 8 as PIRANDELO has 9 distinct characters
      # SHAW > 45 + 8 as PIRANDELO can't use only digits 1-9 (as S < 10)
      # SHAW <= 63 as S + A <= 12
      "S + H + A + W in {59, 61}"     # is_prime(S + H + A + W)
      
       
      # COWARD, PINERO, STOPPARD are also prime
      # PINERO + ADLL = PIRANDELLO = SHAW --> PINERO = SHAW - ADLL = SHW - DLL
      "S + H + W - D - L - L in {23, 29, 31, 37, 41}"    # is_prime(PINERO)
      "is_prime(C + O + W + A + R + D)"
      "is_prime(S + T + O + P + P + A + R + D)"
       
      --answer="(C + O + W + A + R + D, 
       P + I + N + E + R + O, 
       S + T + O + P + P + A + R + D)"
       
      # [optional]
      --template=""
      #--reorder=0
      #--verbose=256    # print generated code
      

      Like

    • Iain Turner's avatar

      Iain Turner 11:41 am on 21 July 2021 Permalink | Reply

      I wrote a python solution just using itertools rather than SubstitutedExpression:

      import itertools
       
      def isprime(x):
        #quick & dirty prime test
        if x%2==0: return(False)
        for i in range(3,1+int(x**0.5),2):
          if x%i==0:return(False)
        return(True)
       
      e=1
      p=4
      a=3
      l=8
      y=24
      count=0
      alpha26={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26}
      for w in [23,25,26]:
        rem=alpha26-{e,w,p,a,l,y}
        #shakespeare = shaw so w = kspar +3e
        #if e=1, min value of kspar is 2+3+4+5+6 = 20, so min value of w is 23
        #if e=2, min value of kspar is 19, so min w is 25
        for ksr in itertools.permutations(rem,3):
           
          k,s,r=ksr
          if w == k+e+s+p+e+a+r+e:
            rem2=rem-set(ksr)
            for prime in [51,53,59,61,67]:
              #shaw
              h=prime-a-w-s
              if h in rem2:
                rem3=rem2-{h}
                #pirandello
                #min value of pirandello is 1+2+2+3+4+5+6+7+8+9 = 47
                #max value of shaw is 26+25+8+9 = 68
                #so prime range is 51 to 67 (see above)
                 
                for indo in itertools.permutations(rem3,4):
                  xx=prime-p-e-a-r-l-l
                  if sum(indo)==xx:
                    i,n,d,o=indo
                    pinero=p+i+n+e+r+o
                    if isprime(pinero):
                      rem4=rem3-set(indo)
                      #frayn
                      for f in rem4:
                        if f+r+a+y+n==prime:
                          rem5=rem4-{f}
                          #beckett
                          for bct in itertools.permutations(rem5,3):
                            xx=prime-e-e-k
                            if sum(bct)+bct[2]==xx:
                              b,c,t=bct
                              rem6=rem5-set(bct)
                              stoppard=s+t+o+p+p+a+r+d
                              coward=c+o+w+a+r+d
                              if isprime(stoppard) and isprime(coward):
                                g=prime-r-a-t-t-i-a-n
                                #rattigan
                                if g in rem6:
                                  print(stoppard,coward,pinero,'prime:',prime,'remaining:',rem6-{g})
                                  count+=1
      print('total count',count)
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:49 pm on 21 July 2021 Permalink | Reply

        Thanks for your comment.

        I wrapped your code in [code language="python"]...[/code] tags and changed the indents to 2 spaces (so that it doesn’t disappear off the RHS).

        The [[ SubstitutedExpression ]] solver uses the expressions given to it to write a similar program, and then runs it.

        If you want to know more about how it works see “Solving Alphametics with Python, Part 2” [ link ].

        Like

      • Frits's avatar

        Frits 6:03 pm on 21 July 2021 Permalink | Reply

        Hi Iain,

        Why do you set e to 1?

        51 is not a prime number so it might lead to incorrect answers.

        Min value of pirandello can be set to 53 (2 l’s with value 8).
        Max value of shaw can be set to 26+25+3+9 = 63 (as a is 3 and s less than 10).

        Like

  • Unknown's avatar

    Jim Randell 9:09 am on 8 July 2021 Permalink | Reply
    Tags:   

    Teaser 2800: Promoting rugby 

    From The Sunday Times, 22nd May 2016 [link] [link]

    Six male rugby players and six female rugby players are helping to promote the game. The men are Eastmond, Morgan, Twelvetrees, Webber, Yarde and Youngs. The women are Allen, Clarke, Croker, McGilchrist, McLean and Thompson. The men have paired off with the women and one pair has gone to each of the counties East Sussex, Hampshire, Isle of Wight, Norfolk, Suffolk and Surrey. For each pair, if you look at the name of the man, the name of the woman and the name of their county, then for any two of the three names just two different letters of the alphabet occur in both (possibly more than once).

    The men above are listed in alphabetical order: in that order, who are their female partners?

    [teaser2800]

     
    • Jim Randell's avatar

      Jim Randell 9:10 am on 8 July 2021 Permalink | Reply

      This is another puzzle by Graham Smithers that can be solved using the [[ grouping ]] functionality in the enigma.py library.

      This Python program runs in 52ms.

      Run: [ @replit ]

      from enigma import grouping
      
      male = ('Eastmond', 'Morgan', 'Twelvetrees', 'Webber', 'Yarde', 'Youngs')
      female = ('Allen', 'Clarke', 'Croker', 'McGilchrist', 'McLean', 'Thompson')
      county = ('East Sussex', 'Hampshire', 'Isle of Wight', 'Norfolk', 'Suffolk', 'Surrey')
      
      grouping.solve([male, female, county], grouping.share_letters(2))
      

      Solution: The female partners are: Clarke, Allen, Thompson, Croker, McLean, McGilchrist.

      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