Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 1:36 am on 10 November 2024 Permalink | Reply
    Tags:   

    Teaser 3242: Tynemouth Boating Lake 

    From The Sunday Times, 10th November 2024 [link] [link]

    When Tynemouth Boating Lake was being constructed, the builder hammered in stakes at two points in the ground, tied the ends of a rope to the stakes, and with a stick keeping the rope taut, marked out an ellipse as shown (not to scale). He was surprised to find that at some points on the circumference, the rope formed two different right-angled triangles, both with sides a whole number of feet long. The free length of the rope was 289 feet.

    In increasing order, what were the five lengths of the sides of the two triangles?

    See: [ Google Maps ].

    [teaser3242]

     
    • Jim Randell's avatar

      Jim Randell 2:00 am on 10 November 2024 Permalink | Reply

      The rope is attached to the posts, so two sides of each triangle are formed by the rope, and the remaining side is formed from the straight line between the two posts (the dashed line in the diagram). An alternative method of construction would be to form the rope into a loop which is placed over the posts, and then pulled tight to form a triangle where all three sides are formed by the rope, but this will construct a smaller ellipse).

      Initially I thought some of the 289 ft of rope would be used to attach it to the posts, but I think the amount of (inextensible) rope between the posts needs to be exactly 289 ft after it has been attached in order for the puzzle to have a unique solution.

      This Python program runs in 69ms. (Internal runtime is 209µs).

      from enigma import (defaultdict, pythagorean_triples, subsets, printf)
      
      # collect pythagorean triples by perimeter
      d = defaultdict(list)
      for ss in pythagorean_triples(289):
        p = sum(ss)
        if any(p - s == 289 for s in ss):
          d[p].append(ss)
      
      # consider possible perimeters
      for p in sorted(d.keys()):
        # choose two triangles (x, y, z), (u, v, w)
        for ((x, y, z), (u, v, w)) in subsets(d[p], size=2, select='P'):
          # such that the hypotenuse of one is a non-hypotenuse of the other
          if w in {x, y}:
            R = u + v  # length of the rope
            printf("R={R} w={w} -> {t1}, {t2} -> {ss}", t1=(x, y, z), t2=(u, v, w), ss=sorted([x, y, z, u, v]))
      

      Solution: The sides of the triangles are: 60, 85, 204, 221, 229 ft.

      The triangles are: (60, 221, 229) and (85, 204, 221), and the posts were 221 ft apart.

      The major and minor axes of the ellipse measure 255 and 186.23 (= 34√30) ft.

      You can see the results when some of the rope is used in the attachments by changing the condition on line 7 to [[ p - s < 289 ]].

      Like

      • Jim Randell's avatar

        Jim Randell 11:56 am on 11 November 2024 Permalink | Reply

        Only certain rope lengths will work for this problem.

        Lengths that will work are squares of the following primes, and multiples of those squares:

        7, 17, 23, 31, 41, 47, 71, 73, 79, 89, 97, …

        which are the primes that have a residue of 1 or 7 modulo 8. (See: OEIS A001132).

        In the given puzzle: 289 = 17^2.

        Like

        • GeoffR's avatar

          GeoffR 9:17 pm on 12 November 2024 Permalink | Reply

          I tested my code with the squares of 7, 23 and 97 which all worked OK:

          Five lengths = [12, 21, 28, 35, 37] – for 7 * 7 = 49

          Five lengths = [120, 184, 345, 391, 409] – for 23 * 23 = 529

          Five lengths = [1092, 1261, 8148, 8245, 8317] – for 97 * 97 = 9409

          Like

      • Jim Randell's avatar

        Jim Randell 4:51 pm on 13 November 2024 Permalink | Reply

        Here is a general solution that works by expressing the length of the rope R as:

        R = k.n^2

        (It doesn’t check that R is a multiple of p^2 for some prime p where p mod 8 = 1 or 7).

        from enigma import (irange, ihypot, prime_factor, arg, printf)
        
        # solve for rope R = k.n^2
        def solve(n, k=1):
          # solve the reduced problem R = n^2
          R = n * n
          for i in irange(1, n - 1):
            # calculate the (u, v, w) triangle (w = hypotenuse)
            u = i * n
            v = R - u
            if u > v: break
            w = ihypot(u, v)
            if w is None: continue
            # calculate the (x, w, z) triangle (z = hypotenuse)
            x = u - i * i
            z = R - x
            # return the two triangles, scaled appropriately
            yield ((k * x, k * w, k * z), (k * u, k * v, k * w))
        
        R = arg(289, 0, int)
        
        # express R as k.n^2
        k = n = 1
        for (p, e) in prime_factor(R):
          (d, r) = divmod(e, 2)
          if r > 0: k *= p
          n *= p**d
        printf("[{R} = {k} * {n}^2]")
        
        # solve the problem
        for (t1, t2) in solve(n, k):
          w = t1[1]
          ss = sorted(set(t1 + t2))
          printf("R={R} w={w} -> {t1}, {t2} -> {ss}")
        

        Like

    • Frits's avatar

      Frits 5:56 am on 10 November 2024 Permalink | Reply

      from math import isqrt
      is_square = lambda n: n == isqrt(n) ** 2
       
      L = 289
       
      # RHS triangle a^2 + b^2 = c^2 
      # where c is the distance between the two focal points with a <= b
      for a in range(1, L // 2 + 1):
        if not is_square(c2 := a**2 + (b := L - a)**2): continue
        # LHS triangle c^2 + d^2 = (L - d)^2
        if c2 % L: continue
        if (double_d := L - c2 // L) % 2: continue
        d = double_d // 2
        print("answer:", sorted([a, b, int(c2**.5), d, L - d]))
      

      Like

      • Frits's avatar

        Frits 1:03 pm on 10 November 2024 Permalink | Reply

        A variation with 4 iterations of k (or only 2 if we use the parity of L).

        from math import ceil
         
        L = 289     # 17^2
        
        # as c2 % L = 0 then c^2 = multiplier * L
        # c^2 = k^2 * L as L is a square number
        
        # a^2 + (L - a)^2 = c^2
        # a = (2.L +- sqrt(4 * L^2 - 8.L^2 + 8.c^2)) / 4
        # discriminant:  -4 * L^2 + 8 * c^2 >= 0
        # c^2 >= L**2 / 2 --> k^2 >= L / 2
        for k in range(ceil((L / 2)**.5), ceil(L**.5)):
          c2 = L * k**2   # c2 % L = 0
          
          # as L is odd then k must be odd as well (we won't use that)
          if (double_d := L - k**2) % 2: continue
          d = double_d // 2
          
          # check valid discriminant
          if (D := -4 * L**2 + 8 * c2) < 0: continue
          # a is the bigger non-hypothenuse side (using '+' in the formula)
          a, r = divmod(2 * L + D**.5, 4)
          if r: continue
          a = int(a)
          c = int((L * k**2)**.5)
          print("answer:", sorted([a, L - a, c, d, L - d]))
        

        Like

    • GeoffR's avatar

      GeoffR 10:01 am on 10 November 2024 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Two triangles are abe with 'b' as  hypotenuse and cde with 'e' as hypotenuse
      % 'e' also is the distance between the two stakes
      var 10..289:a; var 10..289:b; var 10..289:c;
      var 10..289:d; var 10..289:e;
      
      constraint all_different ([a, b, c, d, e]);
      
      constraint a + b == 289 /\ c + d == 289;
      
      constraint a*a + e*e == b*b /\ c*c + d*d == e*e;
      
      array[1..5] of var int: unsorted = [a, b, c, d, e];
      array[1..5] of var int: sorted = sort(unsorted);
      
      solve satisfy;
      
      output [
        "Five lengths in increasing order: ", show(sorted)
      ];
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 7:02 pm on 10 November 2024 Permalink | Reply

      Faster than expected with all the looping.

      def is_sq(n):
        if round(n ** 0.5) ** 2 == n:
          return True
        return False
      
      # 1st triangle is aeb with hypotenuse b
      # e is distance between the two stakes
      for a in range(10, 290):
        for e in range(a+1, 290):
          if is_sq(a ** 2 + e ** 2):
            b = int((a ** 2 + e ** 2) ** 0.5)
            if a + b != 289:continue
            
            # 2nd triangle is cde with hypotenuse e
            for c in range(10, 290):
              for d in range(c+1, 290):
                if c + d != 289:continue
                if c ** 2 + d ** 2 == e ** 2:
                  L1 = [a, b, c, d, e]
                  if len(L1) == len(set(L1)):
                    sorted_list = sorted(L1)
                    print(f"Five lengths = {sorted_list}")
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:47 am on 8 November 2024 Permalink | Reply
    Tags: ,   

    Teaser 2616: Elevenses 

    From The Sunday Times, 11th November 2012 [link] [link]

    On 11/11 it is perhaps appropriate to recall the following story. In the graphics class students were illustrating pairs of similar triangles. In Pat’s larger triangle the sides were all even whole numbers divisible by 11. In fact they were 22 times the corresponding sides of his smaller triangle. As well as this, in the smaller triangle the digits used overall in the lengths of the three sides were all different and did not include a zero. Miraculously, exactly the same was true of the larger triangle.

    What were the sides of Pat’s smaller triangle?

    [teaser2616]

     
    • Jim Randell's avatar

      Jim Randell 10:47 am on 8 November 2024 Permalink | Reply

      We can place some bounds on the sides of the triangles:

      The smaller triangle cannot have sides less than 6, as the corresponding side in the larger triangle would have repeated digits.

      And so the smallest side in the larger triangle must have at least 3 digits, and as there are only 9 digits available, each side of the larger triangle must have 3 digits, and so the sides of the smaller triangle are between 6 and 43.

      This Python program collects possible sides for the large and small triangles, and then chooses three pairs that can form viable triangles.

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

      from enigma import (irange, is_duplicate, subsets, join, printf)
      
      # check for allowable digits
      def check(*vs):
        s = join(vs)
        if '0' in s or is_duplicate(s): return False
        return True
      
      # find multiples of 22 with distinct non-zero digits
      d = dict()  # d maps n -> n / 22
      for i in irange(6, 43):
        if not check(i): continue
        j = i * 22
        if not check(j): continue
        d[j] = i
      
      # choose the two smaller sides
      for (a, b) in subsets(d.keys(), size=2):
        if not (check(a, b) and check(d[a], d[b])): continue
      
        # choose the longer side
        for c in d.keys():
          # check for viable triangle
          if not (b < c): continue
          if not (c < a + b): break
          # check the digits
          if not (check(a, b, c) and check(d[a], d[b], d[c])): continue
          # output solution
          printf("{t1} -> {t2}", t2=(a, b, c), t1=(d[a], d[b], d[c]))
      

      Solution: The sides of the smaller triangle are: 18, 26, 37.

      And the corresponding sides of the larger triangle are: 396, 572, 814.

      Like

    • Frits's avatar

      Frits 1:46 pm on 8 November 2024 Permalink | Reply

      from enigma import SubstitutedExpression
       
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          "div(ABC, 22) = PQ", 
          "A < D",
          "div(DEF, 22) = RS",
          "D < G",
          "div(GHI, 22) = TU",
       
          # allow for single digit sides of smaller triangle   
          "P == 0 or P not in {Q, R, S, T, U}",
          "R == 0 or R not in {P, Q, S, T, U}",
          "T == 0 or T not in {P, Q, R, S, U}",
          
          # viable triangle
          "TU < PQ + RS",
          
          "0 not in {A, B, C, D, E, F, G, H, I, Q, S, U}",
        ],
        answer="PQ, RS, TU",
        d2i="",
        distinct=("ABCDEFGHI","QSU"),
        verbose=0,    # use 256 to see the generated code
      ) 
      
      # print answers
      for ans in p.answers():
        print(f"answer: {ans}")
      

      Like

    • GeoffR's avatar

      GeoffR 11:16 am on 9 November 2024 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      % Smaller triangle - assumed 2 digit sides
      var 1..9:a; var 1..9:b;  var 1..9:c; var 1..9:d; var 1..9:e; var 1..9:f;
      
      constraint all_different([a, b, c, d, e, f]);
      var 12..98:ab == 10*a + b;
      var 12..98:cd == 10*c + d;
      var 12..98:ef == 10*e + f;
      constraint ab < cd /\ cd < ef;  % order sides
      
      % Larger triangle - assumued 3 digit sides
      var 1..9:A; var 1..9:B;  var 1..9:C; 
      var 1..9:D; var 1..9:E;  var 1..9:F;
      var 1..9:G; var 1..9:H;  var 1..9:I;
      
      constraint all_different([A, B, C, D, E, F, G, H, I]);  
      var 123..987:ABC == 100*A + 10*B + C;
      var 123..987:DEF == 100*D + 10*E + F;
      var 123..987:GHI == 100*G + 10*H + I;
      constraint ABC < DEF /\ DEF < GHI;  % order sides
      
      % Larger triangle has even sides and all sides are divisible by 11
      constraint sum([ABC mod 2 == 0, DEF mod 2 == 0, GHI mod 2 == 0]) == 3;
      constraint sum([ABC mod 11 == 0, DEF mod 11 == 0, GHI mod 11 == 0]) == 3;
      
      % larger triangle's sides are 22 times smaller triangle's sides
      constraint ABC == 22 * ab /\ DEF == 22 * cd /\ GHI == 22 * ef;
      
      solve satisfy;
      
      output ["Smaller Triangle Sides = " ++ show(ab) ++ ", " ++ show(cd) ++ ", " ++ show(ef)
      ++ "\n" ++ "Larger Triangle Sides = " ++ show(ABC) ++ ", " ++ show(DEF) ++ ", " ++ show(GHI)
      ];
      
      % Smaller Triangle Sides = 18, 26, 37
      % Larger Triangle Sides = 396, 572, 814
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 7:58 am on 5 November 2024 Permalink | Reply
    Tags:   

    Teaser 2608: Football logic 

    From The Sunday Times, 16th September 2012 [link] [link]

    In the logicians’ football tournament, each of three teams (captained by Alf, Bert and Charlie) plays each other once, with three points for a win and one for a draw. In working out their order at the end of the tournament, “goals scored” are used to determine the order of teams with the same points total. Each captain only knows the scores in his own team’s games. At the end I asked the captains in turn whether they knew which position they had finished in. The replies were:

    Alf: “no”;
    Bert: “no”;
    Charlie: “no”;
    Alf: “yes”.

    In which position did Charlie’s team finish? And what was the result in the game between the other two teams?

    [teaser2608]

     
    • Jim Randell's avatar

      Jim Randell 8:01 am on 5 November 2024 Permalink | Reply

      If a captain knows that they are tied on points with another team, then they cannot know which way the tie breaking will go, as they do not know how many goals the other team scored in their remaining match, so any situation where teams are tied on points will necessarily require the captain to respond that they do not know their final position.

      This Python program uses the [[ Football() ]] helper class, and the [[ filter_unique() ]] function from the enigma.py library to solve the puzzle.

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

      from enigma import (Football, filter_unique, printf)
      
      # scoring system
      football = Football(games='wdl', points=dict(w=3, d=1))
      
      # calculate positions from points, ties are indicated with '='
      def pos(pts):
        rs = [None] * len(pts)
        p = 1
        for x in sorted(set(pts), reverse=1):
          k = pts.count(x)
          v = str(p)
          if k > 1: v += '='
          for (i, y) in enumerate(pts):
            if y == x:
              rs[i] = v
          p += k
        return tuple(rs)
      
      is_tie = lambda x: x.endswith('=')
      
      # generate possible situations
      def generate():
        # consider possible game outcomes
        for (AB, AC, BC) in football.games(repeat=3):
          # table for each team
          A = football.table([AB, AC], [0, 0])
          B = football.table([AB, BC], [1, 0])
          C = football.table([AC, BC], [1, 1])
          # return (<match-outcomes>, <positions>)
          yield ((AB, AC, BC), pos([A.points, B.points, C.points]))
      
      # indices for the teams, and the matches
      (A, B, C) = (AB, AC, BC) = (0, 1, 2)
      matches = { A: (AB, AC), B: (AB, BC), C: (AC, BC) }
      
      # can team <j> deduce their finishing position knowing the outcomes of their own matches?
      # <f> = 0 if the answer is "no"; = 1 if it is "yes"
      def answer(ss, j, f):
        # which matches is team <j> involved in?
        ms = matches[j]
        fnf = lambda t: tuple(t[0][i] for i in ms)
        fng = lambda t: t[1][j]
        # find results with unique/non-unique positions
        r = filter_unique(ss, fnf, fng)
        if f == 0:
          # return results that are non-unique, or end with a tie on points
          return r.non_unique + [t for t in r.unique if is_tie(fng(t))]
        else:
          # return results that are unique, but don't end with a tie on points
          return [t for t in r.unique if not is_tie(fng(t))]
      
      # start with all possible outcomes and positions
      ss = list(generate())
      
      # A cannot deduce his finishing position
      ss = answer(ss, A, 0)
      
      # B cannot deduce his finishing position
      ss = answer(ss, B, 0)
      
      # C cannot deduce his finishing position
      ss = answer(ss, C, 0)
      
      # A can now deduce his finishing position
      ss = answer(ss, A, 1)
      
      # output solutions
      for ((AB, AC, BC), (posA, posB, posC)) in ss:
        printf("posC = {posC}; AB = {AB} [AB={AB} AC={AC} BC={BC}; posA={posA} posB={posB} posC={posC}]")
      

      Solution: Charile finished in second place. The game between Alf and Bert was a draw.

      The match outcomes are either:

      (A v B = draw; C beat A; B beat C)
      B = 1w 1d = 4 pts (1st)
      C = 1w = 3 pts (2nd)
      A = 1d = 1 pt (3rd)

      or:

      (A v B = draw; A beat C; C beat B)
      A = 1w 1d = 4 pts (1st)
      C = 1w = 3pts (2nd)
      B = 1d = 1pt (3rd)

      Like

  • Unknown's avatar

    Jim Randell 6:57 am on 3 November 2024 Permalink | Reply
    Tags:   

    Teaser 3241: Users pay 

    From The Sunday Times, 3rd November 2024 [link] [link]

    I live in a cul-de-sac which had planning permission for building on ten identical plots along the whole length of one side of the road. The developer started building, and numbered the properties, from the closed end. The plots aren’t all finished, and the cost of surfacing the whole road has to be paid by the owners of the completed properties. It was decided that the owners would only contribute to the cost of the road leading to their property, so the owner of number 1 pays for the road section in front of their property, the cost of the section outside number 2 is shared equally between numbers 1 and 2, and so on.

    My contribution is £ 1,000, while that of another homeowner is £ 3,800.

    What is my house number and how many houses have been built?

    [teaser3241]

     
    • Hugo's avatar

      Hugo 7:18 am on 3 November 2024 Permalink | Reply

      This doesn’t appear to make sense. If the houses are numbered from the closed end, one would expect the owner of no. 1 to have to pay the highest contribution, since he is furthest from the point of access to the road.

      Like

      • Jim Randell's avatar

        Jim Randell 8:16 am on 3 November 2024 Permalink | Reply

        I think the way it works is as follows:

        If 3 houses were built, then:

        road section #1 is paid for by house #1
        road section #2 is paid for by houses #1, #2 (half each)
        road sections #3..#10 are paid for by houses #1, #2, #3 (one third each)

        So house #1 pays the most, then #2 and then #3, and the entire cost of the road is covered.

        Like

    • Jim Randell's avatar

      Jim Randell 7:52 am on 3 November 2024 Permalink | Reply

      It took me a couple of attempts to work out how this puzzle is meant to work. I assumed the houses are built in numerical order starting at #1.

      This Python program runs in 70ms. (Internal runtime is 537µs).

      from enigma import (Rational, irange, subsets, printf)
      
      Q = Rational()
      
      # consider total number of houses built
      for n in irange(1, 10):
      
        # accumulate total cost (in sections) for each house
        cost = dict((i, 0) for i in irange(1, n))
        # allocate costs for each section
        for k in irange(1, 10):
          m = min(k, n)
          for i in irange(1, m):
            cost[i] += Q(1, m)
      
        # look for one cost which is 3.8x another
        for (a, b) in subsets(sorted(cost.keys()), size=2):
          if 10 * cost[a] == 38 * cost[b]:
            printf("n={n} -> a={a} b={b}")
      

      Solution: 8 houses have been built. The setters house is #7.

      The cost of the ten sections of road is distributed among the 8 houses as follows:

      #1: 1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/8 + 1/8 = 831/280
      #2: 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/8 + 1/8 = 551/280
      #3: 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/8 + 1/8 = 411/280
      #4: 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/8 + 1/8 = 953/840
      #5: 1/5 + 1/6 + 1/7 + 1/8 + 1/8 + 1/8 = 743/840
      #6: 1/6 + 1/7 + 1/8 + 1/8 + 1/8 = 115/168
      #7: 1/7 + 1/8 + 1/8 + 1/8 = 29/56
      #8: 1/8 + 1/8 + 1/8 = 3/8

      The setter (at house #7) pays £1000, so the actual costs are:

      #1: £ 5731.03
      #2: £ 3800.00
      #3: £ 2834.48
      #4: £ 2190.80
      #5: £ 1708.05
      #6: £ 1321.84
      #7: £ 1000.00
      #8: £ 724.14

      So it is the occupant of house #2 who has to pay £ 3800.

      And the total cost of the road is £ 19310.35.

      Like

  • Unknown's avatar

    Jim Randell 7:44 am on 31 October 2024 Permalink | Reply
    Tags: ,   

    Teaser 2597: Ages ago 

    From The Sunday Times, 1st July 2012 [link] [link]

    Bert’s age in years is one less than one-and-a-half times the age Alf was a whole number of years ago. Cal’s age in years is one less than one-and-a-half times the age Bert was, the same number of years ago.

    Dave’s age in years is one less than one-and-a-half times the age Cal was, again the same number of years ago. All four ages are different two-figure numbers, Cal’s age being Bert’s age with the order of the digits reversed.

    What (in alphabetical order) are their ages?

    [teaser2597]

     
    • Jim Randell's avatar

      Jim Randell 7:45 am on 31 October 2024 Permalink | Reply

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

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # ages now:
      #
      #   AB = Alf
      #   CD = Bert
      #   DC = Cal
      #   EF = Dave
      #
      # let the number of years ago be XY
      
      --distinct="CD"
      --invalid="0,ACDE"
      
      # "B's age is 1 less than 1.5 times A's age XY years ago"
      "3 * (AB - XY) == 2 * (CD + 1)"
      
      # "C's age is 1 less than 1.5 times B's age XY years ago"
      "3 * (CD - XY) == 2 * (DC + 1)"
      
      # "D's age is 1 less than 1.5 times C's age XY years ago"
      "3 * (DC - XY) == 2 * (EF + 1)"
      
      # ages are all different
      "all_different(AB, CD, DC, EF)"
      
      --template="(AB CD DC EF) (XY)"
      --solution=""
      

      Solution: The ages now are: Alf = 98; Bert = 86; Cal = 68; Dave = 41.

      And Cal’s age is the reverse of Bert’s.

      40 years ago, Alf was 58. And 1.5× this age is 87, and Bert’s current age is one less than this.

      40 years ago, Bert was 46. And 1.5× this age is 69, and Cal’s current age is one less than this.

      40 years ago, Cal was 28. And 1.5× this age is 42, and Dave’s current age is one less than this.

      Like

    • Ruud's avatar

      Ruud 8:38 am on 31 October 2024 Permalink | Reply

      Brute force:

      import itertools
      
      for a, b, d in itertools.permutations(range(10, 100), 3):
          c = int(str(b)[::-1])
          if b != c and c >= 10:
              for n in range(1, min(a, b, c, d) + 1):
                  if b == (a - n) * 1.5 - 1:
                      if c == (b - n) * 1.5 - 1:
                          if d == (c - n) * 1.5 - 1:
                              print(f"Alf={a} Bert={b} Cal={c} Dave={d} Number of years ago={n}")
      

      Liked by 1 person

    • ruudvanderham's avatar

      ruudvanderham 9:26 am on 31 October 2024 Permalink | Reply

      Quite different and much more efficient than my previous one:

      for a in range(10, 100):
          for n in range(a):
              b = (a - n) * 1.5 - 1
              c = (b - n) * 1.5 - 1
              d = (c - n) * 1.5 - 1
              if all(x == round(x) and n < x < 100 for x in (b, c, d)):
                  if len({a, b, c, d}) == 4:
                      if str(round(b)) == str(round(c))[::-1]:
                          print(a, b, c, d, n)
      

      Like

    • GeoffR's avatar

      GeoffR 9:35 am on 1 November 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % All ages are different 2-digit numbers
      var 10..99:Alf; var 10..99:Bert; var 10..99:Cal; var 10..99:Dave;
      constraint all_different([Alf, Bert, Cal, Dave]);
      
      var 1..99:Y;  % the number of years ago
      
      constraint (2 * Bert ==  (Alf - Y) * 3 - 2)
      /\ (2 * Cal  ==  (Bert - Y) * 3 - 2)
      /\ (2 * Dave ==  (Cal - Y) * 3 - 2);
      
      % Cal’s age being Bert’s age with the order of the digits reversed.
      constraint Cal div 10 == Bert mod 10 /\ Cal mod 10 == Bert div 10;
      
      solve satisfy;
      
      output ["Alf = " ++ show(Alf) ++ ", Bert = " ++ show(Bert) ++
      ", Cal = " ++ show(Cal) ++ ", Dave = " ++ show(Dave) ++ ", Y = " ++ show(Y)];
      
      % Alf = 98, Bert = 86, Cal = 68, Dave = 41, Y = 40 
      % ----------
      % ==========
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:10 am on 29 October 2024 Permalink | Reply
    Tags:   

    Teaser 2540: Extra time 

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

    George and Martha planned a holiday on the south coast. The second-class rail fare each way was a certain whole number of pounds per person and the nightly cost of an inexpensive double room, in pounds, was the same number but with digits reversed. They originally planned to stay 30 nights, but then increased that to 33. “So the total cost will go up by 10%”, said Martha. “No”, replied George, “it will go up by some other whole number percentage”.

    What is the nightly cost of a double room?

    [teaser2540]

     
    • Jim Randell's avatar

      Jim Randell 8:11 am on 29 October 2024 Permalink | Reply

      I assumed that the rail fare does not have a trailing zero, so that the room rate does not have a leading zero.

      This Python program finds the first solution where the rail fare and room rate are less than £100.

      It runs in 80ms. (Internal runtime is 188µs).

      from enigma import (irange, nrev, div, printf)
      
      # consider possible rail fares
      for F in irange(1, 99):
        # disallow trailing zeros
        if F % 10 == 0: continue
      
        # the room rate is the reverse of this
        R = nrev(F)
      
        # cost for 30 nights and 33 nights
        t30 = 4*F + 30*R
        t33 = 4*F + 33*R
      
        # t33 is a k per cent increase on t30
        r = div(100 * t33, t30)
        if r is None: continue
      
        printf("F={F} R={R} r={r}%")
        break
      

      Solution: The cost of the room was £ 54 per night.

      And so the rail fares were £ 45 per person (single).

      The cost for 30 days is:

      4× 45 + 30× 54 = 1800

      And the cost for 33 days:

      4× 45 + 33× 54 = 1962

      And we have:

      1962 / 1800 = 1.09

      So the increase is 9%.


      There are larger solutions, for example:

      F=45, R=54
      F=495, R=594
      F=4545, R=5454
      F=4995, R=5994
      F=45045, R=54054
      F=49995, R=59994
      F=450045, R=540054
      F=454545, R=545454
      F=495495, R=594594
      F=499995, R=599994

      But the room is described as “inexpensive”, so only the first of these is a viable solution.

      However, they all involve a 9% increase.

      Like

    • GeoffR's avatar

      GeoffR 12:34 pm on 29 October 2024 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; 
      var 1..99: per_cent;
      
      % Assume room cost < £100
      var 12..98: Room == 10*A + B; 
      var 12..98: Fare == 10*B + A;
      
      % Two people with outgoing and return trips = 4 fares
      var 600..6000: Cost30 == 30 * Room + 4 * Fare;
      var 660..6060: Cost33 == 33 * Room + 4 * Fare;
      
      % Calculate percentage increased room cost
      constraint (Cost33 - Cost30) * 100 ==  Cost30 * per_cent;
      
      solve satisfy;
      
      output ["Room cost = " ++ show(Room) ++ " pounds per night."
      ++ "\n" ++ "Cost percentage increase = " ++ show(per_cent) ++ " %"];
      
      % Room cost = 54 pounds per night.
      % Cost percentage increase = 9 %
      % ----------
      % ==========
      

      Like

    • Ruud's avatar

      Ruud 4:15 am on 30 October 2024 Permalink | Reply

      from istr import istr
      
      
      istr.repr_mode("int")
      for train in istr(range(10, 100)):
          room = train[::-1]
          if room[0] != 0:
              total30 = int(train * 4 + room * 30)
              total33 = int(train * 4 + room * 33)
              increase = ((total33 - total30) / total30) * 100
              if increase % 1 == 0:
                  print(f"{train=} {room=} {total30=} {total33=} {increase=:.0f}%")
      

      Like

    • Frits's avatar

      Frits 5:43 am on 3 November 2024 Permalink | Reply

      Objective was to use fewer iterations.

      from fractions import Fraction as RF
      
      # fare = 10 * f + g, room cost = 10 * g + f
      
      # t30 = 4*F + 30*R = 70*f + 304*g  
      # t33 = 4*F + 33*R = 73*f + 334*g
      
      # 100 + p = 100 * (73*f + 334*g) / (70*f + 304*g)     
      
      # integer percentages less than 10
      for p in range(9, 0, -1):
        # (7300*f + 33400*g) - (100 + p) * (70*f + 304*g) = 0
        # ((300 - p*70)*f + (3000 - 304*p)*g) = 0
        
        # calculate - f / g  (g will not be zero)
        r = RF(3000 - 304*p, 300 - p*70)
        if r > 0: continue  
        f, g = abs(r.numerator), abs(r.denominator)
        if not (0 < f < 10 and g < 10): continue 
        
        # double check
        if 100 * (73*f + 334*g) / (70*f + 304*g) - 100 != p: continue
        print(f"answer: {10 * g + f} pounds per night") 
      

      Like

  • Unknown's avatar

    Jim Randell 1:49 am on 27 October 2024 Permalink | Reply
    Tags:   

    Teaser 3240: The Case of the Green Cane of Kabul 

    From The Sunday Times, 27th October 2024 [link] [link]

    Inspector Lestrade had a warrant for the arrest of Dr Watson.

    “Pray explain,” cried Dr Watson.

    “A man was badly beaten earlier tonight near here. The victim told us that the assailant used a green cane. We all know that you received the Green Cane of Kabul for military service and are the only person allowed to carry it.”

    “What you say is true, Lestrade”, said Holmes. “However, there are several recipients of the Blue Cane of Kabul living here, and 20% of people seeing blue identify it as green while 20% of people seeing green identify it as blue. It follows that the probability that the colour of the cane used in this attack is blue is 2/3.”

    How many recipients of the Blue Cane of Kabul are there in the city?

    [teaser3240]

     
    • Jim Randell's avatar

      Jim Randell 2:13 am on 27 October 2024 Permalink | Reply

      There are two relevant cases to consider:

      (1) The attacker used a green cane, which was correctly identified as green.

      (2) The attacker used a blue cane, which was misidentified as green.

      And we need the probability of case (2) to be 2/3 (i.e. it is twice the probability of case (1)).

      The solution is easily found manually, but this Python program considers possible increasing numbers of blue cane carriers, until the required probability is found.

      from enigma import (Rational, irange, inf, compare, printf)
      
      Q = Rational()
      
      # B = number of carriers of the blue cane
      for B in irange(1, inf):
        # probability attacker used a green cane, identified as green
        pGG = Q(1, B + 1) * Q(4, 5)
        # probability attacker used a blue cane, identified as green
        pBG = Q(B, B + 1) * Q(1, 5)
        # probability that a blue cane was used in the attack
        p = Q(pBG, pGG + pBG)
        r = compare(p, Q(2, 3))
        if r == 0: printf("B={B}")
        if r > 0: break
      

      Solution: There are 8 holders of the Blue Cane.


      Manually:

      If there are B blue canes, then (assuming each cane holder is equally likely to be the attacker):

      P(attacker used blue cane) = B/(B + 1)
      P(attacker used green cane) = 1/(B + 1)

      P(attacker used blue cane, identified as green) = (B/(B + 1)) × (1/5) = pBG
      P(attacker used green cane, identified as green) = (1/(B + 1)) × (4/5) = pGG

      And we want pBG to be twice pGG.

      pBG = 2 pGG
      (B/(B + 1)) × (1/5) = 2 × (1/(B + 1)) × (4/5)
      ⇒ B = 2 × 4 = 8

      Like

  • Unknown's avatar

    Jim Randell 9:40 am on 24 October 2024 Permalink | Reply
    Tags:   

    Brainteaser 1206: A useless club 

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

    There are more canals in Birmingham than there are in Venice, but this interesting fact is a digression. I had to include it because I, George and Harry are members of the society for the Dissemination of Useless Information. We, like the other members, have membership numbers, which were issued in the order in which we joined the society (starting at 1). It has now recruited its 1000th member. I joined the Society before George, and when he joined, he, with proper acumen, noticed that his membership number had figures which were the exact reverse of mine, and that Harry’s number was equal to the difference between our two numbers. He also, as a new and therefore keen member of the Society, pointed out another useless piece of information, namely that his number was an exact multiple of Harry’s, though Harry was not amongst the first 10 members to join.

    What is my membership number?

    [teaser1206]

     
    • Jim Randell's avatar

      Jim Randell 9:40 am on 24 October 2024 Permalink | Reply

      This Python program runs in 67ms. (Internal runtime is 1.6ms).

      from enigma import (irange, nrev, printf)
      
      # consider possible numbers for I
      for I in irange(1, 1000):
        # G is the reverse of I
        G = nrev(I)
        # and H is the difference between them
        H = G - I
        # check relevant conditions
        if not (0 < I < G and 10 < H < G and G % H == 0): continue
        # output solution
        printf("I={I} G={G} H={H}")
      

      Solution: Your membership number is: 495.

      George’s is 594, Harry’s is 99, and 594 = 6 × 99.

      Liked by 1 person

    • GeoffR's avatar

      GeoffR 10:34 am on 26 October 2024 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 10..999:ME; var 10..999:G; var 10..999:H;
      var 1..9:a; var 1..9:b; var 1..9:c; var 1..9:d; var 1..9:e;
      
      % ME is assumed to be either a 2-digit number or a 3-digit number
      % for ME and George to have numbers with reverse digits
      constraint (ME > 10 /\ ME < 100 /\ a == ME div 10 /\ b == ME mod 10
      /\ G == 10*a + b)
      \/
      (ME > 100 /\ ME < 1000 /\ c == ME div 100 /\ d = ME div 10 mod 10 /\ e == ME mod 10
      /\ G == 100*e + 10*d + c);
      
      constraint H == G - ME;
      constraint ME > 10 /\ G > ME;
      constraint H > 10 /\ G > H;
      constraint G mod H == 0 /\ G div H > 0;
      
      solve satisfy;
      
      output ["My no. = " ++ show(ME) ++ ", George's no. = " ++ show(G) ++
       ", Harry's no. = " ++ show(H) ];
      
      % My no. = 495, George's no. = 594, Harry's no. = 99
      % ----------
      % ==========
      
      
      
      

      Like

    • Frits's avatar

      Frits 1:55 am on 27 October 2024 Permalink | Reply

      # difference of 2 reversed numbers is a multiple of 99 so
      # George's number is a multiple of 99
      print("answer:", ' or '.join([m for j in range(1, 11) 
            if (h := int(m := str(g := 99 * j)[::-1]) - g) > 10 and 
                h % 99 == 0 and g % h == 0]))      
      

      Like

  • Unknown's avatar

    Jim Randell 8:04 am on 22 October 2024 Permalink | Reply
    Tags:   

    Teaser 2539: Doubling up 

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

    One day at school I found myself in charge of two classes. Faced with 64 pupils and only 32 desks, I gave each child a different whole number and told them the highest number must share with the lowest, the second highest with the second lowest, etc. When they had sorted themselves into pairs, I got each child to multiply their own number by that of their desk-mate. They discovered all the products were the same. In fact I chose the original numbers so that this common product would be as low as possible.

    What was the common product?

    [teaser2539]

     
    • Jim Randell's avatar

      Jim Randell 8:04 am on 22 October 2024 Permalink | Reply

      I am assuming that the “whole numbers” were are interested in are positive integers.

      We are looking for the smallest number with at least 64 divisors (so that we can form 32 different pairs).

      Fortunately the answer is fairly small, so we can use a simple search.

      The following Python program runs in 89ms. (Internal runtime is 17ms).

      from enigma import (irange, inf, tau, arg, printf)
      
      N = arg(64, 0, int)
      
      # find the smallest number with _at least_ N divisors
      for n in irange(1, inf):
        if tau(n) >= N:
          printf("{N} divisors -> n = {n}")
          break
      

      Solution: The product was 7560.

      It turns out that 7560 (= (2^3)(3^3)(5)(7)) has exactly 64 divisors, and so here are the 32 pairs:

      >>> list(divisors_pairs(7560))
      [(1, 7560), (2, 3780), (3, 2520), (4, 1890), (5, 1512), (6, 1260),
       (7, 1080), (8, 945), (9, 840), (10, 756), (12, 630), (14, 540),
       (15, 504), (18, 420), (20, 378), (21, 360), (24, 315), (27, 280),
       (28, 270), (30, 252), (35, 216), (36, 210), (40, 189), (42, 180),
       (45, 168), (54, 140), (56, 135), (60, 126), (63, 120), (70, 108),
       (72, 105), (84, 90)]
      

      We investigated finding the smallest number with a given number of divisors in Teaser 2580, so if we want to look for the smallest number with exactly 64 divisors (which may have been the setters intention), we can find the answer a little faster.

      The following runs in 72ms. (Internal runtime is 165µs).

      from enigma import (Accumulator, primes, rev, multiply, arg, printf)
      
      # number of divisors to find
      N = arg(64, 0, int)
      
      # find smallest number with exactly k divisors
      def solve(k):
        # consider factorisations of k
        r = Accumulator(fn=min)
        for ds in primes.factorisations(k):
          # pair the largest powers with the smallest primes
          n = multiply(p**(x - 1) for (p, x) in zip(primes, rev(ds)))
          r.accumulate(n)
        return r.value
      
      n = solve(N)
      printf("{N} divisors -> n = {n}")
      assert primes.tau(n) == N
      

      Like

  • Unknown's avatar

    Jim Randell 2:20 am on 20 October 2024 Permalink | Reply
    Tags:   

    Teaser 3239: Box set 

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

    I have a shallow box whose base is 20 cm wide and a certain whole number of centimetres long. In this box there are some thin plain tiles. Most (but not all) of the tiles are rectangles, 10 cm by 20 cm, and the rest are squares of side 20 cm. They just fit snugly, jigsaw fashion, in the bottom of the box. With the box in a fixed position, I have calculated the number of different jigsaw layouts possible using all the tiles to fill the box. The answer is a three-digit perfect square.

    How long is the box, and how many of the tiles are square?

    [teaser3239]

     
    • Jim Randell's avatar

      Jim Randell 2:54 am on 20 October 2024 Permalink | Reply

      See also: BrainTwister #15.

      The two sizes of tiles are 10 cm × 20 cm and 20 cm × 20 cm, so we can work in units of decimetres and just consider 1×2 and 2×2 tiles.

      This Python program packs increasing numbers of tiles into a tray 2 units high, proceeding from left to right, and also constructs a representation of each packing.

      It runs in 96ms. (Internal runtime is 29ms).

      from enigma import (irange, inf, decompose, icount, is_square, printf)
      
      # find packings for <a> 2x1 and <b> 2x2 tiles
      def solve(a, b, ss=''):
        # are we done?
        if 0 == a == b:
          yield ss
        else:
          if a > 1:
            # place 2x 2x1 tiles horizontally
            yield from solve(a - 2, b, ss + '=')
          if a > 0:
            # place 1x 2x1 tile vertically
            yield from solve(a - 1, b, ss + '|')
          if b > 0:
            # place 1x 2x2 tile
            yield from solve(a, b - 1, ss + 'O')
      
      # consider total number of tiles
      for t in irange(3, inf):
        r = 0  # count results with up to 3 digits
        # split into 2x1 (more) and 2x2 tiles
        for (b, a) in decompose(t, 2, increasing=1, sep=1, min_v=1):
          # count the number of ways to place the tiles
          n = icount(solve(a, b))
          # look for cases where n is a 3-digit square
          if n < 1000:
            r += 1
            if n > 99 and is_square(n):
              printf("t={t}: a={a} b={b} -> {n} ways; length = {x}", x=a + 2 * b)
        # are we done?
        if r == 0: break
      

      If we don’t generate the representations of the packings we can write a faster program.

      The following program has an internal runtime of 227µs.

      from enigma import (irange, inf, decompose, cache, printf)
      
      # number of packings for <a> 2x1 and <b> 2x2 tiles
      @cache
      def P(a, b):
        # are we done?
        if 0 == a == b: return 1
        r = 0
        # place 2x 2x1 tiles horizontally
        if a > 1: r += P(a - 2, b)
        # place 1x 2x1 tile vertically
        if a > 0: r += P(a - 1, b)
        # place 1x 2x2 tile
        if b > 0: r += P(a, b - 1)
        return r
      
      # target values = 3-digit squares
      tgt = set(i * i for i in irange(10, 31))
      
      # consider total number of tiles
      for t in irange(3, inf):
        r = 0  # count results with up to 3 digits
        # split into 2x1 (more) and 2x2 tiles
        for (a, b) in decompose(t, 2, increasing=-1, sep=1, min_v=1):
          # count the number of ways to place the tiles
          n = P(a, b)
          # look for cases where n is a 3-digit square
          if n < 1000:
            r += 1
            if n > 99 and n in tgt:
              printf("t={t}: a={a} b={b} -> {n} ways; length = {x}", x=a + 2 * b)
        # are we done?
        if r == 0: break
      

      Solution: The box is 110 cm long. There are 3 square tiles.

      There are 8 tiles in total, 3 square and 5 rectangular. And they can fit into the box in 256 (= 16^2) different ways.

      See also: OEIS A038137.

      Like

    • Frits's avatar

      Frits 11:55 am on 20 October 2024 Permalink | Reply

      My original program performed weaving of the list of squares with the list of rectangles but as we are only interested in the number of combinations I have used the combinatorial C formula.

      from math import factorial as fact
      
      # number of possible combinations
      C = lambda n, r: fact(n) / (fact(r) * fact(n - r))
      
      # loop over number of tiles
      for t, _ in enumerate(iter(bool, 1), 3): # infinite loop
        below1000 = 0
        # choose number of square tiles
        for s in range(1, (t + 1) // 2):
          r = t - s  # number of rectangular tiles
          # fill box with <s> squares and <r> rectangles
          
          # suitable selection of rectangular pieces  
          rps = [["|"] * v + ["="] * (h // 2) for v in range(r + 1) 
                 if (h := (r - v)) % 2 == 0]  
          
          c = 0
          for rp in rps:
            nv = rp.count("|")
            # C(len(rp), nv) = len(set(permutations(rp)))
            c += C(len(rp) + s, s) * C(len(rp), nv)
          
          if c < 1000:     
            below1000 = 1    
            # three-digit perfect square
            if c > 99 and (round(c**.5))**2 == c:
              print(f"answer: {10 * (2 * s + r)} cm long and with {s} square tiles")
                       
        if below1000 == 0:
          break # no need to check with more tiles
      

      Like

    • Pete Good's avatar

      Pete Good 5:17 pm on 20 October 2024 Permalink | Reply

      Jim, Frits’s program produces the right answer but your answer needs to be multiplied by 10.

      Like

  • Unknown's avatar

    Jim Randell 8:43 am on 17 October 2024 Permalink | Reply
    Tags:   

    Teaser 2603: Pandigital square 

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

    I call my telephone number “pandigital”, in the sense that it is a nine-digit number using each of the digits 1 to 9. Amazingly, it is a perfect square. Furthermore, its square root is a five-digit number consisting of five consecutive digits in some order.

    It might interest you to know (although you do not need to) that my neighbour’s telephone number is also a nine-digit pandigital perfect square, but his is at least double mine.

    With a little logic and not many calculations you should be able to work out my telephone number.

    What is it?

    There are now 1100 Teaser puzzles available on the S2T2 site.

    [teaser2603]

     
    • Jim Randell's avatar

      Jim Randell 8:44 am on 17 October 2024 Permalink | Reply

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

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

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # consider phone numbers of the form ABCDEFGHI = sq(VWXYZ)
      --distinct="ABCDEFGHI,VWXYZ"
      --invalid="0,ABCDEFGHIV"
      
      # VWXYZ are consecutive digits (in some order) ...
      "is_consecutive(ordered(V, W, X, Y, Z))"
      
      # ... which give a square using each of the digits 1-9 exactly once
      "sq(VWXYZ) = ABCDEFGHI"
      
      --answer="ABCDEFGHI"
      

      Solution: The phone number is 157326849.

      Where:

      sqrt(157326849) = 12543

      There are 24 squares that can be formed from the digits 1-9 (each exactly once) that are at least twice this number.

      The smallest is: 326597184, and the largest is: 923187456.

      So we can suppose that the setter and his neighbour do not share an area code prefix in their phone numbers.


      Manually:

      A number consisting of the digits 1-9 has a digit sum of 45, and so must be divisible by 9, and so its square root must be divisible by 3.

      Using the additional information that the neighbour’s number is at least twice the setter’s number, we can see that the leading digit of the setter’s number must be 1-4, so the 5 digit root is less than 22334, which means it must start with 1 or 2, and so there are only a few possible consecutive digit sets that need to be considered, and only {1, 2, 3, 4, 5} will form numbers divisible by 3.

      So the candidates are: 12xxx, 13xxx, 14xxx, 15xxx, 21xxx. Where each xxx has 6 possibilities, which gives 30 possibilities.

      From these we can eliminate any candidates ending in 51, 52, 53, 235, 243, 245, 345, 423, 435, 532 (as the squares will include 0 or a repeated digit), which brings us down to 16 candidates.

      12 354 → 152621316
      12 534 → 157101156
      12 543 → 157326849 [*SOLUTION*]

      13 254 → 175668516
      13 425 → 180230625
      13 524 → 182898576
      13 542 → 183385764

      14 325 → 205205625
      14 523 → 210917529

      15 234 → 232074756
      15 324 → 234824976
      15 342 → 235376964
      15 432 → 238146624

      21 354 → 455993316
      21 534 → 463713156
      21 543 → 464100849

      And only one of these squares is a reordering of the digits 1-9.

      Like

      • ruudvanderham's avatar

        ruudvanderham 2:59 pm on 17 October 2024 Permalink | Reply

        Enumerating all 5 digit numbers with 5 consecutive numbers and then checking whether the square of that is pandigital:

        from istr import istr
        
        for i in range(1, 6):
            for digit5 in istr.concat(istr.permutations(range(i, i + 5))):
                square_of_digit5 = digit5 * digit5
                if len(square_of_digit5) == 9 and set(square_of_digit5) == set(istr.digits("1-9")):
                    print(digit5, square_of_digit5)
        

        Like

    • GeoffR's avatar

      GeoffR 12:44 pm on 17 October 2024 Permalink | Reply

      As Brian points out on his PuzzlingInPython site for this teaser, the first number is less than 5.10^8 so its square root is less than 22361 and the leading digit is 1 or 2. My neighbour’s telephone number is not needed for the solution.

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:A; var 1..9:B; var 1..9:C; var 1..9:D;
      var 1..9:E; var 1..9:F; var 1..9:G; var 1..9:H; var 1..9:I;
      constraint all_different([A,B,C,D,E,F,G,H,I]);
      
      % Telephone number is ABCDEFGHI
      var 100000000..500000000: tel_num == A*pow(10,8) + B*pow(10,7) + C*pow(10,6)
      + D*pow(10,5) + E*pow(10,4) + F*pow(10,3) + G*pow(10,2) + H*10 + I;
      
      % Square root of telephone number is abcde
      var 1..5:a; var 1..5:b; var 1..5:c; var 1..5:d; var 1..5:e;
      constraint all_different([a,b,c,d,e]);
      var 10000..99999: abcde ==  a*pow(10,4) + b*pow(10,3) + c*pow(10,2) + d*10 + e;
      
      constraint a ==1 \/ a == 2;
      constraint abcde * abcde == tel_num;
      
      solve satisfy;
      
      output ["Telephone number = " ++ show(tel_num) ++ "\n"
      ++ "Square root of tel. num. = " ++ show (abcde) ];
      
      % Telephone number = 157326849
      % Square root of tel. num. = 12543
      % ----------
      % ==========
      
      

      Like

    • GeoffR's avatar

      GeoffR 2:11 pm on 17 October 2024 Permalink | Reply

      
      # the first number is less than 5.10^8 so its square root
      # is less than 22361  and the leading digit is 1 or 2 
      # and 5 cconsecutive digits, in some order
      
      from itertools import permutations
      
      for p1 in permutations('12345'):
          a,b,c,d,e = p1
          if a not in ('12'):continue
          abcde = int(a + b + c + d + e)
          tel_num = abcde ** 2
          if tel_num > 5 * 10**8:continue
          if len(set(str(tel_num))) != 9 \
          or len(str(tel_num)) != len(set(str(tel_num))):
              continue
          print('Tel_num =', tel_num)
      
      

      Like

  • Unknown's avatar

    Jim Randell 8:39 am on 15 October 2024 Permalink | Reply
    Tags:   

    Teaser 2439: [Higher or lower] 

    From The Sunday Times, 21st June 2009 [link]

    Six cards are numbered 1 to 6. One card is placed on the forehead of each of three expert logicians. Each can see the other numbers, but not his own. I asked each in turn if his number was higher, lower or the same as the average of all three. They replied as follows:

    Alf: I don’t know.
    Bert: I don’t know.
    Charlie: I don’t know.

    If I now told you the product of the three numbers, you could work out which number each holds.

    What numbers did Alf, Bert and Charlie hold (in that order)?

    This puzzle was originally published with no title.

    I received a message via the “Contact” form asking for a solution to this puzzle.

    [teaser2439]

     
    • Jim Randell's avatar

      Jim Randell 8:39 am on 15 October 2024 Permalink | Reply

      We can start with all possible scenarios – there are P(6, 3) = 120.

      Then, knowing A says “don’t know” we can remove any candidates where A would know. For example: if A were to see 1 and 2, he would know that whatever his card is (3, 4, 5, or 6) it would be more than the average of the three cards. Similarly, if he were to see 5 and 6, then he would know that whatever his card was (1, 2, 3, or 4) it would be less than the average of the 3 cards.

      This whittles the number of candidates down to 104.

      We can then apply the same reasoning to these remaining candidates, knowing that B says “don’t know”, which gets us down to 70 candidates.

      Finally applying the same process to these candidates, knowing that C says “don’t know”, gets us down to 22 candidate scenarios.

      And only one of the remaining candidates is unique by the product of the numbers. (In fact all the other candidates appear in multiple permutations, so must have repeated products).

      This Python program performs these steps. It runs in 71ms. (Internal runtime is 1.8ms).

      from enigma import (
        irange, compare, seq_all_same, subsets, group, multiply, join, printf
      )
      
      values = set(irange(1, 6))
      
      # return situations from <ss> where <i> answers "don't know" on seeing <j> and <k>
      def check(ss, i, j, k):
        # consider possible situations
        for s in ss:
          # find possible candidates from i's POV (same j and k)
          ts = (t for t in ss if t[j] == s[j] and t[k] == s[k])
          # do the candidates give multiple different answers?
          if not seq_all_same(compare(2 * t[i], t[j] + t[k]) for t in ts):
            # if so keep this situation
            yield s
      
      # initial list of candidate (A, B, C) assignments
      ss = set(subsets(values, size=3, select='P'))
      printf("[start: {n} candidates]", n=len(ss))
      
      # label A, B, C
      (A, B, C) = (0, 1, 2)
      
      # remove candidates that don't give a "don't know" from A
      ss = set(check(ss, A, B, C))
      printf("[after A: {n} candidates]", n=len(ss))
      
      # now remove candidates that don't give a "don't know" from B
      ss = set(check(ss, B, A, C))
      printf("[after B: {n} candidates]", n=len(ss))
      
      # now remove candidates than don't give a "don't know" from C
      ss = set(check(ss, C, A, B))
      printf("[after C: {n} candidates]", n=len(ss))
      
      # group the remaining candidates by product
      g = group(ss, by=multiply)
      
      # look for candidates with a unique product
      for (p, cs) in g.items():
        if len(cs) != 1: continue
        printf("product = {p} -> {cs}", cs=join(cs, sep=" "))
      

      Solution: Alf had 5, Bert had 3, Charlie had 4.

      Like

    • Frits's avatar

      Frits 5:31 am on 25 October 2024 Permalink | Reply

      If Bert heard Alf’s answer and Charlie heard Alf’s and Bert’s answer then the reported solution is incorrect (Charlie would say “lower”).

      Like

      • Jim Randell's avatar

        Jim Randell 9:29 am on 25 October 2024 Permalink | Reply

        @Frits: I think C would not know whether to say “lower” or “same”, so has to say “don’t know”. (And in the actual situation C’s number is the same as the arithmetic mean, so he cannot know that it is lower).

        Like

    • Frits's avatar

      Frits 11:29 am on 25 October 2024 Permalink | Reply

      @Jim, Sorry, I got confused with (5, 4, 3).

      Like

  • Unknown's avatar

    Jim Randell 8:07 am on 13 October 2024 Permalink | Reply
    Tags:   

    Teaser 3238: Any colour, as long as it’s not black 

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

    Henry learnt snooker scoring basics: pot a red (= 1 pt), not counting as a colour, then a colour (yellow = 2 pt, green = 3 pt, brown = 4 pt, blue = 5 pt, pink = 6 pt, black = 7 pt), alternately. Potted reds stay out of play, while a colour potted immediately after a red returns to play. Once the last red and accompanying colour are potted, then pot the colours in the aforementioned sequence. A miss or foul lets your opponent play.

    With fifteen reds available, Henry scored the first points (a prime number) as described, then missed a red with a foul, yielding four points to Ford. Only with these four points could Ford possibly win. Potting all the remaining reds, each followed by colours other than black, then the colours to pink, Ford needed the black to win by one point. He missed with a foul, which ended the game.

    Give Ford’s score.

    [teaser3238]

     
    • Jim Randell's avatar

      Jim Randell 8:40 am on 13 October 2024 Permalink | Reply

      This Python program runs in 140ms. (Internal runtime is 70ms).

      from enigma import (multiset, subsets, is_prime, printf)
      
      # available balls
      colours = { 2, 3, 4, 5, 6, 7 }
      
      # record results
      rs = multiset()
      
      # H potted some (red + colour) to give a prime number
      for hs in subsets(colours, min_size=1, max_size=14, select='R'):
        H = sum(1 + x for x in hs)
        if not is_prime(H): continue
      
        # calculate max possible remaining score
        r = 15 - len(hs)  # remaining reds
        R = (1 + 7) * r + sum(colours)
        # F can only win with 4 extra points from a foul
        if not (R <= H < R + 4): continue
      
        # F then plays the remaining balls
        coloursF = colours.difference({7})  # F does not pot the black
        for fs in subsets(coloursF, size=r, select='R'):
          F = sum(1 + x for x in fs) + sum(coloursF) + 4
          # F needs black to win by one point
          if not (F + 7 == H + 1): continue
          # output solution
          printf("[H = {H} {hs}; F = {F} {fs}]")
          rs.add((H, F))
      
      for (k, n) in rs.most_common():
        printf("(H, F) = {k} [{n} solutions]")
      

      Solution: Ford’s score was 37.

      And Henry’s score, just before Ford committed the foul, was 43.

      In Henry’s turn he had potted 13 reds along with one of the following sets of colours:

      12 yellow (@ 2 points), 1 pink (@ 6 points)
      11 yellow (@ 2 points), 1 green (@ 3 points), 1 blue (@ 5 points)
      11 yellow (@ 2 points), 2 brown (@ 4 points)
      10 yellow (@ 2 points), 2 green (@ 3 points), 1 brown (@ 4 points)
      9 yellow (@ 2 points), 4 green (@ 3 points)

      In Ford’s turn he had potted 2 reds, along with:

      1 blue (@ 5 points), 1 pink (@ 6 points)

      and then the colours from yellow to pink.

      Like

  • Unknown's avatar

    Jim Randell 11:26 am on 10 October 2024 Permalink | Reply
    Tags:   

    Teaser 2609: String art 

    From The Sunday Times, 23rd September 2012 [link] [link]

    Callum knocked some tacks onto the edge of a circular board. He then used pieces of string to make all possible straight-line connections between pairs of tacks. The tacks were arranged so that no three pieces of string crossed at the same point inside the circle. If he had used six tacks this would have required fifteen pieces of string and it would have created fifteen crossing points inside the circle. But he used more than six and in his case the number of crossing points inside the circle was a single-digit multiple of the number of pieces of string used.

    How many tacks did he use?

    [teaser2609]

     
    • Jim Randell's avatar

      Jim Randell 11:27 am on 10 October 2024 Permalink | Reply

      See also: Enigma 77, Enigma 1769.

      There is a line between each pair of tacks, so the number of lines is:

      L(n) = C(n, 2) = n(n − 1)/2

      Each crossing point is defined by two of the lines (any four different points form the vertices of a quadrilateral, the diagonals of which give the crossing lines), and each line is defined by two tacks.

      So the number of crossings is given by:

      X(n) = C(n, 4) = n(n − 1)(n − 2)(n − 3)/24

      And we are interested in when (for some single digit k):

      X(n) = k L(n)
      k = X(n) / L(n)

      12k = (n − 2)(n − 3)

      So we need to find two consecutive numbers (≥ 3), that give a suitable value for k = 2..9.

      The only candidate is k = 6:

      12 × 6 = 72 = 8 × 9

      Hence n = 11.

      Solution: Callum used 11 tacks.

      L(11) = 55
      X(11) = 330
      330 = 6 × 55

      The next solution would occur at n = 14.

      L(14) = 91
      X(14) = 1001
      1001 = 11 × 91

      but the multiple is not a single digit number.


      Here is a program that considers increasing n values:

      from enigma import (irange, inf, C, printf)
      
      # consider number of tacks
      for n in irange(7, inf):
        # calculate number of lines and number of crossings
        (L, X) = (C(n, 2), C(n, 4))
        (k, r) = divmod(X, L)
        if k > 9: break
        if r != 0: continue
        # output solution
        printf("n={n}: L={L} X={X}; k={k}")
      

      Alternatively here is a program that solves the equation:

      from enigma import (Polynomial, irange, printf)
      
      # make the polynomial p(n) = (n - 2)(n - 3)
      n = Polynomial('n', var='n')
      p = (n - 2) * (n - 3)
      
      # consider possible single digit multiples k
      for k in irange(2, 9):
        # find integer roots of the polynomial p(n) = 12k
        for r in p.find_roots(12 * k, domain='Z'):
          # look for values > 6
          if r > 6:
            # output solution
            printf("n={r} [k={k}]")
      

      Like

    • Ruud's avatar

      Ruud 7:29 am on 11 October 2024 Permalink | Reply

      import math
      import itertools
      
      for number_of_tacks in itertools.count(7):
          number_of_crossing_points = math.comb(number_of_tacks, 4)
          number_of_pieces_of_string = math.comb(number_of_tacks, 2)
          if number_of_crossing_points / number_of_pieces_of_string > 9:
              break
          if number_of_crossing_points % number_of_pieces_of_string == 0:
              print(f"{number_of_tacks=}  {number_of_pieces_of_string=}  {number_of_crossing_points=}  {number_of_crossing_points//number_of_pieces_of_string=}")
      

      Like

  • Unknown's avatar

    Jim Randell 11:10 am on 8 October 2024 Permalink | Reply
    Tags:   

    Teaser 2580: Hard times 

    From The Sunday Times, 4th March 2012 [link] [link]

    Penny found a multiplication table in the back of one of Joe’s old school books – the top corner of it is illustrated. She noticed that prime numbers only appeared twice in the body of the table, whereas 4 (for example) appeared 3 times and 12 appeared 6 times. Penny could not help wondering how many times large numbers appeared in a huge table.

    What is the smallest number that will appear 250 times?

    Note: In this puzzle we are looking for exact numbers of appearances (rather than minimum number of appearances).

    [teaser2580]

     
    • Jim Randell's avatar

      Jim Randell 11:11 am on 8 October 2024 Permalink | Reply

      See also: Teaser 11.

      Here is a straightforward, but slow solution. It runs in 7.6s (using PyPy):

      from enigma import (irange, inf, tau, arg, printf)
      
      N = arg(250, 0, int)
      
      for n in irange(1, inf):
        if tau(n) == N:
          printf("{N} divisors -> n = {n}")
          break
      

      Solution: The first number to appear exactly 250 times is: 5670000.

      5670000 = (2^4)(3^4)(5^4)(7)

      Although this is not the first number to appear at least 250 times. That is: 1081080, which appears 256 times.

      1081080 = (2^3)(3^3)(5)(7)(11)(13)


      However, with a bit more work we can come up with a much faster program:

      If n has divisors d1, …, dk then it will appear at the following positions in the table:

      d1 × d1
      d2 × d2

      dk × dk

      (where dj = n / dj).

      So n appears k times if it has k divisors.

      And if a number is expressed as a product of its prime factors:

      n = (p1^e1) × (p2^e2) × … × (pk^ek) = ∏(pj^ej)

      Then each prime pj can appear 0 to ej times, hence the number of different divisors is:

      tau(n) = (e1 + 1) × (e2 + 1) × … × (ek + 1) = ∏(ej + 1)

      We are looking for a number with exactly 250 divisors, so we can look at possible factorisations of 250.

      So, for example, if we factorise 250 as:

      250 = a × b × c

      Then any number of the form:

      p1^(a − 1) × p2^(b − 1) × p3^(c − 1)

      (for primes p1, p2, p3) will have exactly 250 divisors.

      So, by considering the possible factorisations of 250, and using the smallest primes, we can find the smallest candidate number for each factorisation, and then choose the smallest of the candidate numbers found. (Often, but not always, the smallest number is given by the prime factorisation of the original number).

      This Python program runs in 65ms, and has an internal runtime of 205µs.

      from enigma import (Accumulator, primes, trim, rev, multiply, arg, printf)
      
      # number of divisors to find
      N = arg(250, 0, int)
      
      # find factorisations of <n> using divisors <ds>
      def _factorisations(n, ds, i, ss=[]):
        # are we done?
        if n == 1:
          yield tuple(ss)
        else:
          # look for the next divisor
          while i >= 0:
            d = ds[i]
            if n >= d:
              (r, x) = divmod(n, d)
              if x == 0:
                yield from _factorisations(r, ds, i, [d] + ss)
            i -= 1
      
      # find factorisations of <n> (aka multiplicative partitions)
      def factorisations(n):
        # always return the trivial factorisation
        yield (n,)
        if n < 4: return
        # look for other factorisations using divisors (other than 1 and n)
        ds = trim(primes.divisors(n), head=1, tail=1)
        yield from _factorisations(n, ds, len(ds) - 1)
      
      # find smallest number with k divisors
      def solve(k):
        # consider factorisations of k
        r = Accumulator(fn=min)
        for ds in factorisations(k):
          # pair the largest powers with the smallest primes
          n = multiply(p**(x - 1) for (p, x) in zip(primes, rev(ds)))
          r.accumulate(n)
        return r.value
      
      n = solve(N)
      printf("{N} divisors -> n = {n}")
      assert primes.tau(n) == N
      

      The [[ factorisations() ]] function will appear in the next release of the enigma.py library.

      Like

  • Unknown's avatar

    Jim Randell 3:47 am on 6 October 2024 Permalink | Reply
    Tags:   

    Teaser 3237: Oranges and lemons 

    From The Sunday Times, 6th October 2024 [link] [link]

    George and Martha attend a local club where there is a “fruit machine”. There are three dials, each with five “fruits” but here the “fruits” are numbers. On the first dial are the numbers 1-5; on the second there are the numbers 5-9. On the third, 5 appears again but with four two-digit numbers.

    You put £1 in the machine, then pull a handle which spins the dials, and the numbers on the dials appear completely at random. The jackpot payout for three 5s is £52. Otherwise the machine pays £2 if the three displayed numbers sum to a prime total.

    If the machine pays out 80% of its intake on average, what are the lowest possible values for the for the four two-digit numbers?

    Note: Although not explicitly stated, the intention for this puzzle is that the four 2-digit numbers on the third reel are all different.

    [teaser3237]

     
    • Jim Randell's avatar

      Jim Randell 3:58 am on 6 October 2024 Permalink | Reply

      I think the puzzle could be clearer on what constitutes the “lowest possible values”. I went for the values with the smallest sum, but you could go for the smallest maximum value (although in the end both these give the same answer).

      I also assumed that numbers could be repeated on a reel (as the puzzle does not state that they are all different, and it is usual for the reels on a fruit machine to have repeated symbols). However we do get a plausible looking answer with the additional constraint that they are all different.

      (I have opened a discussion topic if you have thoughts on these issues [link]).

      There are 5^3 = 125 possible positions of the reels, each equally likely, so the total take is £125. And if the total payout among all positions is 80% of this, it is £100.

      from enigma import (irange, decompose, primes, cproduct, printf)
      
      primes.expand(113)  # max possible prime
      
      # check a set of reels; return (<total-take>, <total-payout>)
      def check(rs):
        t = w = 0
        for ns in cproduct(rs):
          t += 1
          if ns == (5, 5, 5):
            w += 52
          elif sum(ns) in primes:
            w += 2
        return (t, w)
      
      # the first two reels
      r1 = [1, 2, 3, 4, 5]
      r2 = [5, 6, 7, 8, 9]
      
      # consider total sum of the four 2-digit numbers
      for t in irange(40, 396):
        f = 0
        # construct possible third reels
        for ns in decompose(t, 4, increasing=1, sep=0, min_v=10, max_v=99, fn=list):
          r3 = [5] + ns
          (tot, pay) = check([r1, r2, r3])
          if pay == 100:
            # output solution
            printf("{r3} -> ({tot}, {pay})")
            f += 1
        if f > 0: break
      

      If repeated values are allowed:

      Solution: The 4-digit values with the lowest total are: 14, 14, 14, 14. (total = 56)

      If all values have to be different:

      Solution: The 4-digit values with the lowest total are: 14, 15, 16, 24. (total = 69)

      The second one was the published solution.


      To see the solution to the alternative puzzle where the four 2-digit numbers are all different, you just need to pass [[ sep=1 ]] to [[ decompose() ]] at line 24.

      I think it is likely that this is the answer that the setter was after. [This has now been confirmed].

      Liked by 1 person

    • Frits's avatar

      Frits 4:36 am on 6 October 2024 Permalink | Reply

      from itertools import combinations_with_replacement as cwr, product
      
      # determine suitable primes up to 99+5+9
      P = {3, 5, 7}
      P = {x for x in range(11, 114, 2) if all(x % p for p in P)}
      
      score = lambda seq: 52 if seq == (5, 5, 5) else (2 * (sum(seq) in P))
      
      d1 = range(1, 6)
      d2 = range(5, 10)
      # select four 2-digit numbers for the third dial
      for n4 in cwr(range(10, 100), 4):
        d3 = (5, ) + n4
        # play 5^3 games with every possible outcome
        # it should pay out 4 * 5^2 pounds (80%)
        if sum(score(p) for p in product(d1, d2, d3)) == 100:
          print("answer:", n4)
          exit() # lowest 
      

      Like

    • ruudvanderham's avatar

      ruudvanderham 1:12 pm on 6 October 2024 Permalink | Reply

      The program below finds all two digit numbers that lead to a payout of 100:

      import itertools
      from istr import istr
      
      
      for d3 in range(10, 100):
          payout = 52
          for d1 in range(1, 6):
              for d2 in range(5, 10):
                  if istr.is_prime(d1 + d2 + d3):
                      payout += 4 * 2
                  if istr.is_prime(d1 + d2 + 5):
                      payout += 2
      
          if payout == 100:
              print(d3, end=" ")
      

      If the digits may be the same, we pick four times the lowest value.
      If the digits have to be different, we pick the four lowest values.

      Like

    • Frits's avatar

      Frits 8:39 am on 7 October 2024 Permalink | Reply

      Using a different interpretation of “lowest possible values”.

      from itertools import product
      
      # determine suitable primes up to 99+5+9
      P = {3, 5, 7}
      P = {x for x in range(11, 114, 2) if all(x % p for p in P)}
      
      payout = lambda seq: 52 if seq == (5, 5, 5) else (2 * (sum(seq) in P))
      
      d1 = range(1, 6)
      d2 = range(5, 10)
      
      n_rng = [5] + list(range(10, 100)) 
      
      # payouts per number on the 3rd dial
      pay = {n: sum(payout(p) for p in product(d1, d2, [n])) for n in n_rng}
      # retrieve the lowest number on the 3rd dial for a specific payout
      low = {v: min([k for k in pay.keys() if pay[k] == v]) for v in pay.values()}
      
      todo = 100 - pay[5] # total expected payout is 0.80 * 5^3
      
      # select four 2-digit numbers for the third dial so the combined payout is
      # equal to <todo>
      n4s = [[low[x] for x in p] for p in product(low, repeat=4) if sum(p) == todo]
      
      # assume “lowest possible values" means minimal sum
      mn = min([sum(n4) for n4 in n4s])
      print("answer:", ' or '.join(str(sorted(n4)) for n4 in n4s if sum(n4) == mn)) 
      

      Like

  • Unknown's avatar

    Jim Randell 9:16 am on 3 October 2024 Permalink | Reply
    Tags:   

    Teaser 2599: Removing a card 

    From The Sunday Times, 15th July 2012 [link] [link]

    I have taken a set of cards and written a different digit on each. Overall I have used a set of consecutive digits. Then I have arranged the cards in some order to make one large number. This number is divisible by the digit that is one more than the number of cards.

    Reading from left to right, if any card is removed then the remaining number is divisible by the position of the card removed. For example, if the card in the sixth position is removed and the remaining cards are pushed together to form another number, then that number is divisible by 6.

    What was the original number?

    [teaser2599]

     
    • Jim Randell's avatar

      Jim Randell 9:16 am on 3 October 2024 Permalink | Reply

      Note that the number formed from the cards “is divisible by the digit that is one more than the number of cards”.

      If there are k cards, then (k + 1) must be a single digit, i.e.:

      k + 1 ≤ 9
      ⇒ k ≤ 8

      And the example given involves removing the 6th card and closing up the gap, hence:

      k > 6
      ⇒ k ≥ 7

      So the only possible values for k are 7 or 8.

      This Python program runs in 228ms. (Internal runtime is 142ms).

      from enigma import (irange, tuples, subsets, nconcat, delete, printf)
      
      # solve the puzzle for digits <ds>
      def solve(ds):
        k = len(ds)
        # divisibility by 3 or 9 does not depend on order
        if (k == 2 or k == 8) and sum(ds) % (k + 1) > 0: return
        # choose an ordering for the digits
        for ss in subsets(ds, size=k, select='P'):
          if ss[0] == 0: continue
          n = nconcat(ss)
          # the number is divisible by one more than the number of digits
          if n % (k + 1) > 0: continue
          # if the digit at position i is removed, the number formed from
          # the remaining digits is divisible by i
          if any(nconcat(delete(ss, [i - 1])) % i > 0 for i in irange(2, k)): continue
          # output solution
          printf("k={k}: {ds} -> {ss} -> {n}")
      
      digits = list(irange(0, 9))
      
      # the puzzle implies there between 7 and 8 cards
      for k in irange(7, 8):
        # choose k consecutive digits
        for ds in tuples(digits, k):
          solve(ds)
      

      Solution: The original number was: 2435160.


      If we allow the full range numbers of cards (2 to 9) there are the following solutions:

      k=2: 21, 45, 87
      k=3: 120, 456, 576, 756, 876
      k=5: 14352, 41352, 47658, 74658, 78654, 87654
      k=6: 513240
      k=7: 2435160
      k=9: 143756820, 156473280, 173856420, 176583240, 253716840, 273156480, 453716820, 476513280, 513276840, 516783240, 543176820, 573126840, 573416820, 586713240, 713526480, 716543280, 743516820, 746153280, 753216480, 756183240, 756813240, 873156420, 876513240

      Note that beyond k=2 the numbers are even (as removing the 2nd digit must result in a number divisible by 2, and so the final digit must be even).

      And beyond k=5 the numbers end in 0 (as removing the 5th digit must result in a number divisible by 5, and so the final digit must be 0). And so the only consecutive sequence of digits is 0..(k − 1).

      This means that for k=8 the set of digits is 0..7, but these digits have a sum of 28, which is not divisible by 9, and so none of the arrangements of digits will be. So there is no possible collection of 8 cards.

      We know then, that the number must be formed from 7 cards, using the digits 0..6.

      The following run file uses these restrictions and divisibility rules on the 7-digit number, and has an internal run time of 275µs.

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # k=7: n = ABCDEFG
      --digits="0-6"
      
      # the number is divisible by 8
      "ABCDEFG % 8 = 0"
      
      # removing the digit at position i gives a number divisible by (i + 1)
      "ACDEFG % 2 = 0"
      "ABDEFG % 3 = 0"
      "ABCEFG % 4 = 0"
      "ABCDFG % 5 = 0"
      "ABCDEG % 6 = 0"
      "ABCDEF % 7 = 0"
      
      # additionally ...
      # G must be 0
      --assign="G,0"
      # ABDEFG is divisible by 3 -> C is divisible by 3
      --invalid="1|2|4|5,C"
      # ABCEFG is divisible by 4 -> F is divisible by 2
      --invalid="1|3|5,F"
      
      --answer="ABCDEFG"
      

      Like

  • Unknown's avatar

    Jim Randell 11:18 am on 1 October 2024 Permalink | Reply
    Tags:   

    Brain-Teaser 545: Gold Cup 

    From The Sunday Times, 5th December 1971 [link]

    Although 42 horses (numbered 1 to 42) were originally entered for the Teaser Gold Cup, only 38 took part in the race.

    The number of the horse which came second in the race exceeded the number of the winner by the same number as that by which the number of the third horse exceeded the number of the second.

    When the numbers of the 4 non-runners were placed in numerical order, the difference between each number and the next was the same in every case, and that difference was the same as the difference between the number of the horse which won the race and the number of the horse which came second.

    The sum of the numbers of the highest and lowest of the four non-runners was equal to the sum of the numbers of the horses which came first, second, and third.

    One of the first three horses was numbered 15. Horses numbered 24 and 28 fell at the third fence, and were not remounted.

    What were the numbers of the 4 non-runners?

    [teaser545]

     
    • Jim Randell's avatar

      Jim Randell 11:18 am on 1 October 2024 Permalink | Reply

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

      It runs in 90ms. (Internal runtime of the generated program is 12ms).

      #! python3 -m enigma -rr
      
      SubstitutedExpression
      
      # suppose the first three horses are: A, B, C (ordered by number)
      # and the four non-runners are: W, X, Y, Z (ordered by number)
      
      --base=43
      --digits="1-42"
      --distinct="ABCWXYZ"
      --invalid="24|28,ABCWXYZ"
      
      # A, B, C are ordered by number
      "A < B" "B < C"
      # and form an arithmetic progression (common diff = D)
      "B - A = D"
      "C - B = D"
      # one of them is 15
      "15 in {A, B, C}"
      
      # W, X, Y, Z are ordered by number
      "W < X" "X < Y" "Y < Z"
      # and form an arithmetic progression (common diff = D)
      "X - W = D"
      "Y - X = D"
      "Z - Y = D"
      
      # equal sums
      "W + Z == A + B + C"
      
      --template=""
      

      Solution: The 4 non-runners were: 12, 19, 26, 33.

      Which forms an arithmetic progression with common difference of 7.

      The horses in the first 3 places were: 8, 15, 22.

      Which also form an arithmetic progression with a common difference of 7.

      The sum of the numbers of the first 3 horses is 8 + 15 + 22 = 45, the same as the sum of the highest and lowest numbered non-runners 12 + 33 = 45.

      Like

      • Ruud's avatar

        Ruud 4:36 pm on 1 October 2024 Permalink | Reply

        import itertools
        
        
        for non_runners in itertools.combinations(set(range(1, 43)) - {15, 24, 28}, 4):
            if (diff := non_runners[1] - non_runners[0]) == non_runners[2] - non_runners[1] == non_runners[2] - non_runners[1] == non_runners[3] - non_runners[2]:
                runners123 = set(range(1, 43)) - set(non_runners) - {24, 28}
                for first in runners123:
                    second = first + diff
                    third = second + diff
                    if second in runners123 and third in runners123 and 15 in {first, second, third} and first + second + third == non_runners[0] + non_runners[3]:
                        print(f"{non_runners=} {first=} {second=} {third=}")
        

        , which prints:

        non_runners=(12, 19, 26, 33) first=8 second=15 third=22
        

        Like

    • GeoffR's avatar

      GeoffR 7:23 pm on 1 October 2024 Permalink | Reply

      
      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..42:NR1; var 1..42:NR2; var 1..42:NR3; var 1..42:NR4;
      var 1..42:W1; var 1..42:W2; var 1..42:W3;
      
      constraint all_different ([NR1, NR2, NR3, NR4, W1, W2, W3]);
      
      constraint W2 - W1 == W3 - W2;
      
      constraint  NR1 < NR2 /\ NR2 < NR3 /\ NR3 < NR4;
      
      constraint NR2 - NR1 == NR3 - NR2 /\ NR3 - NR2 == NR4 - NR3;
      
      constraint NR2 - NR1 == W2 - W1;
      
      constraint NR1 + NR4 == W1 + W2 + W3;
      
      constraint sum ([W1 == 15, W2 == 15, W3 == 15]) == 1;
      
      var set of int: horses = {NR1, NR2, NR3, NR4, W1, W2, W3};
      
      constraint card ({24, 28} intersect horses) == 0;
      
      solve satisfy;
      
      output ["Numbers of non-runners = " ++ show([NR1, NR2, NR3, NR4]) ++
      "\nWinning numbers = " ++ show([W1, W2, W3]) ];
      
      % Numbers of non-runners = [12, 19, 26, 33]
      % Winning numbers = [8, 15, 22]
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 2:14 am on 29 September 2024 Permalink | Reply
    Tags:   

    Teaser 3236: A fair start 

    From The Sunday Times, 29th September 2024 [link] [link]

    We did well selling plants on our charity stall this year: £70 was taken in the first hour. The plants were priced at £1, £2, £3, £4 and £5 but all purchases were made from just three of those categories. All the customers spent different amounts and all bought three items, but no-one bought three items at the same price. Last year the Vicar spent £14 but so far this year no-one has exceeded or even matched that amount. In fact the Vicar was the first customer and spent the average amount in that first hour.

    What, in ascending order, were the prices of the items that the Vicar bought?

    News: I am experimenting with GitHub Discussions to host discussions that are not directly related to the solutions of individual puzzles. Feel free to join in.

    [teaser3236]

     
    • Jim Randell's avatar

      Jim Randell 2:48 am on 29 September 2024 Permalink | Reply

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

      from enigma import (divisors_pairs, subsets, multiset, cproduct, decompose, printf)
      
      # possible denominations
      dss = subsets([1, 2, 3, 4, 5], size=3)
      # possible number of people = n, average spend = a
      nas = ((n, a) for (n, a) in divisors_pairs(70, every=1) if a < 14)
      
      # consider possible (denominations, (people, average)) values
      for (ds, (n, a)) in cproduct([dss, nas]):
      
        # the vicar spent the average amount
        m = multiset.from_seq(ds, count=2)
        for vs in m.express(a, k=3):
      
          # and the rest spent different amounts (less than 14)
          for rs in decompose(70 - a, n - 1, increasing=1, sep=1, min_v=4, max_v=13):
            if a in rs: continue
            # check each amount can be made
            if not m.expressible(rs, k=3): continue
            # output solution
            printf("n={n}: a={a} ds={ds} rs={rs} -> vicar = {vs}", vs=list(vs.sorted()))
      

      Solution: The vicar bought items priced: £2, £3, £5.

      So the vicar spent £10, which is the mean amount among 7 people spending £70.

      The others spent: £7, £8, £9, £11, £12, £13, which brings the total spend to £70.

      We can actually break down each purchase:

      £7 = £2 + £2 + £3
      £8 = £2 + £3 + £3
      £9 = £2 + £2 + £5
      £10 = £2 + £3 + £5 [vicar; mean]
      £11 = £3 + £3 + £5
      £12 = £2 + £5 + £5
      £13 = £3 + £5 + £5
      total = £70

      Like

    • Frits's avatar

      Frits 5:05 am on 30 September 2024 Permalink | Reply

      from itertools import product, combinations
      
      # combinations of three different prices
      prices = list(combinations(range(1, 6), 3))
      # possible price distributions
      dist = set(p for p in product(range(3), repeat=3) if sum(p) == 3)
      
      # choose number of customers
      for n in range(len(dist), 0, -1):
        avg, r = divmod(70, n)
        if avg >= 14: break
        if r: continue
        
        # choose three different prices
        for ps in prices:
          # choose <n> price distributions 
          for ds in combinations(dist, n):
            spent_amnts = {sum(x * y for x, y in zip(ps, d)) for d in ds}
            # sum spent amounts is 70 and the average must be present
            if avg not in spent_amnts or sum(spent_amnts) != 70: continue
            # all different amounts and less than the Vicar spent last year
            if len(spent_amnts) != n or max(spent_amnts) >= 14: continue
            # price distributions of the items that the Vicar bought
            v_dist = [d for d in ds if sum(x * y for x, y in zip(ps, d)) == avg][0]
            v_amnts = [[ps[i]] * v_dist[i] for i in range(3) if v_dist[i]]
            print("answer:", [y for x in v_amnts for y in x])
      

      Like

  • Unknown's avatar

    Jim Randell 9:19 am on 27 September 2024 Permalink | Reply
    Tags:   

    Teaser 2606: Cue for a queue 

    From The Sunday Times, 2nd September 2012 [link] [link]

    Alan, Brian, Colin, Dave and Ed have surnames Smith, Jones, Rogers, Mason and Hall, but not in that order. They form themselves into a queue. Brian is somewhere ahead of Mr Smith who is somewhere ahead of Ed. Similarly, Mr Jones is ahead of Colin who is ahead of Dave who is ahead of Mr Hall. Mr Mason’s two neighbours in the queue are Alan and Mr Rogers.

    If I told you Alan’s surname it would now be possible to work our all their surnames and their positions in the queue.

    What is Alan’s surname and what is his position in the queue?

    [teaser2606]

     
    • Jim Randell's avatar

      Jim Randell 9:20 am on 27 September 2024 Permalink | Reply

      I used the [[ SubstitutedExpression ]] solver from the enigma.py library to assign positions 1-5 in the queue to the forenames and surnames, such that the required conditions are met.

      We can then use the [[ filter_unique() ]] function to select solutions from the viable assignments, such that knowing Alan’s surname gives a single solution for all names.

      The following Python program runs in 89ms. (Internal runtime is 5.8ms).

      from enigma import (SubstitutedExpression, trim, filter_unique, update, peek, join, printf)
      
      # generate possible queues
      def queues():
        # allocate positions 1-5 in the queue to:
        #
        #  Alan = A; Brian = B; Colin = C; Dave = D; Ed = E
        #  Smith = S; Jones = J; Rogers = R; Mason = M; Hall = H
        #
        p = SubstitutedExpression(
          [
            # the surnames are not given in the correct order
            "A != S or B != J or C != R or D != M or E != H",
            # B is ahead of S, who is ahead of E
            "B < S", "S < E",
            # J is ahead of C, who is ahead of D, who is ahead of H
            "J < C", "C < D", "D < H",
            # M's neighbours (on different sides) are A and R
            "abs(A - R) = 2", "abs(A - M) = 1", "abs(R - M) = 1",
          ],
          base=6,
          digits=[1, 2, 3, 4, 5],
          distinct=["ABCDE", "SJRMH"],
        )
      
        # solve the puzzle
        for s in p.solve(verbose=0):
          # assemble the queue
          q = ["??"] * 6
          for k in "ABCDE": q[s[k]] = update(q[s[k]], [0], k)
          for k in "SJRMH": q[s[k]] = update(q[s[k]], [1], k)
          # return the queue
          yield trim(q, head=1)
      
      # knowing A's surname gives a unique solution
      f = (lambda q: peek(x for x in q if x[0] == 'A'))
      for q in filter_unique(queues(), f).unique:
        printf("{q}", q=join(q, sep=" "))
      

      Solution: Alan Smith is second in the queue.

      The queue is as follows:

      1st = Brian Jones
      2nd = Alan Smith
      3rd = Colin Mason
      4th = Dave Rogers
      5th = Ed Hall

      There are 5 candidate solutions:

      BJ, AS, CM, DR, EH
      BJ, CS, ER, DM, AH
      BJ, CS, DR, EM, AH
      AJ, BM, CR, DS, EH
      AJ, CM, BR, DS, EH

      The first of these is unique for AS. The next two both have AH, and the final two have AJ.

      Like

      • ruudvanderham's avatar

        ruudvanderham 5:39 pm on 27 September 2024 Permalink | Reply

        import itertools
        
        for firstnames, lastnames in zip(itertools.repeat("Alan Brian Colin Dave Ed".split()), itertools.permutations("Smith Jones Rogers Mason Hall".split(), r=5)):
            for positions in itertools.permutations(range(1, 6)):
                position = {firstname: position for firstname, position in zip(firstnames, positions)}
                position.update({lastname: position for lastname, position in zip(lastnames, positions)})
                if position["Brian"] < position["Smith"] < position["Ed"]:
                    if position["Jones"] < position["Colin"] < position["Dave"] < position["Hall"]:
                        if {position["Mason"] - position["Alan"], position["Mason"] - position["Rogers"]} == {-1, 1}:
                            if list(lastnames) != "Smith Jones Rogers Mason Hall".split():
                                for firstname, lastname in zip(firstnames, lastnames):
                                    print(f"{firstname+ " "+lastname:12} {position[firstname]}  ", end="")
                                print()
        

        This prints:

        Alan Smith   2  Brian Jones  1  Colin Mason  3  Dave Rogers  4  Ed Hall      5  
        Alan Jones   1  Brian Rogers 3  Colin Mason  2  Dave Smith   4  Ed Hall      5
        Alan Jones   1  Brian Mason  2  Colin Rogers 3  Dave Smith   4  Ed Hall      5
        Alan Hall    5  Brian Jones  1  Colin Smith  2  Dave Rogers  3  Ed Mason     4  
        Alan Hall    5  Brian Jones  1  Colin Smith  2  Dave Mason   4  Ed Rogers    3
        

        So, it has to be Alan Smith (the others are not unique) and he is in second position.

        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