Tagged: by: C Higgins Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:03 am on 25 February 2024 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2572: Abominable 

    From The Sunday Times, 8th January 2012 [link] [link]

    In the art class Pat used five circles for his new design, Snowman. One circle made the head and another touching circle (with radius three times as big) made the body. Two equal circles (of radius one centimetre less than that of the head) made the arms (one on each side) and they touched both the body and the head. The fifth circle enclosed the other four, touching each of them. The radius of the head’s circle was a whole number of centimetres.

    How many centimetres?

    [teaser2572]

     
    • Jim Randell's avatar

      Jim Randell 9:04 am on 25 February 2024 Permalink | Reply

      See also: Teaser 2926.

      The arrangement looks like this:

      If we consider the head, body, one arm and the enclosing circle we have four mutually tangent circles, so we can use Descartes’ Theorem [ @wikipedia ] to express a relationship between their curvatures.

      If the curvatures are represented by k (where: k = 1 / r, i.e. the curvature is the reciprocal of the corresponding radius), then we have:

      (Σk)² = 2Σ(k²)

      In this instance the circles have radii of r, 3r, r − 1, 4r and so the curvatures are:

      k1 = 1/r
      k2 = 1/3r
      k3 = 1/(r − 1)
      k4 = −1/4r

      The curvature of the fourth circle is negative as it circumscribes the other three circles.

      We can solve the puzzle numerically by simply considering possible r values.

      The following program runs in 65ms. (Internal runtime is 102µs).

      Run: [ @replit ]

      from enigma import (Rational, irange, inf, sq, printf)
      
      Q = Rational()
      
      # consider possible values for r
      for r in irange(2, inf):
      
        # calculate the 4 curvatures
        ks = (Q(1, r), Q(1, 3 * r), Q(1, r - 1), Q(-1, 4 * r))
      
        # check Descartes' Theorem
        if sq(sum(ks)) == 2 * sum(map(sq, ks)):
          printf("r = {r}")
          break
      

      Solution: The head has a radius of 13 cm.

      Or we can solve it by finding the roots of a polynomial.

      This Python program runs in 66ms. (Internal runtime is 3.2ms).

      Run: [ @replit ]

      from enigma import (Polynomial, sq, printf)
      
      # consider the numerators of the curvatures, where the denominator is 12r(r-1)
      ks = [
        12 * Polynomial([-1, 1]),  # k1 = 12(r-1) / 12r(r-1) = 1/r
         4 * Polynomial([-1, 1]),  # k2 = 4(r-1) / 12r(r-1) = 1/3r
        12 * Polynomial([ 0, 1]),  # k3 = 12r / 12r(r-1) = 1/(r-1)
        -3 * Polynomial([-1, 1]),  # k4 = -3(r-1) / 12r(r-1) = -1/4r
      ]
      
      # Descartes' Theorem: sq(sum(ks)) = 2 * sum(map(sq, ks))
      p = sq(sum(ks)) - 2 * sum(map(sq, ks))
      
      # solve the polynomial for positive integer solutions
      for r in p.rational_roots(domain='Z', include='+'):
        printf("r = {r}")
      

      Analytically, we find the polynomial we are solving is:

      r² − 26r + 169 = 0

      (r − 13)² = 0
      r = 13

      Like

  • Unknown's avatar

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

    Teaser 2665: Milky ways 

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

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

    How many cows are in the herd?

    [teaser2665]

     
    • Jim Randell's avatar

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

      (See also: Teaser 2995, Teaser 2779).

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

      Run: [ @replit ]

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

      Solution: There were 7 cows in the herd.

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

      Of these subfactorial(7) = 1854 are derangements.

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

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


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

      Like

  • Unknown's avatar

    Jim Randell 12:12 pm on 26 September 2023 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2653: Tough guys 

    From The Sunday Times, 28th July 2013 [link] [link]

    The SS Thomas More has two identical vertical masts mounted on the centre line of the deck, the masts being a whole number of feet tall and seven feet apart. Two straight guy ropes secure the top of each mast to a single anchor point on the centre line of the deck some distance forward of the masts. The total length of the two ropes is a whole number of feet, with one rope being two feet longer than the other.

    What is the height of the masts?

    [teaser2653]

     
    • Jim Randell's avatar

      Jim Randell 12:13 pm on 26 September 2023 Permalink | Reply

      If the ropes are length x and (x + 2), then the total length (= 2x + 2) is a whole number of feet, so x is a whole number of semi-feet (i.e. units of 6 inches).

      So let’s do everything in semi-feet.

      From the 2 right-angled triangles we have:

      r² = d² + h²
      (r + 4)² = (d + 14)² + h²

      r = (7d + 45) / 2

      And as r is an integer, d must be an odd integer.

      This Python program looks for the smallest viable solution:

      It runs in 58ms. (Internal runtime is 200µs)

      Run: [ @replit ]

      from enigma import (irange, inf, div, ircs, printf)
      
      # generate candidate solutions
      def generate():
        # consider d values
        for d in irange(1, inf, step=2):
          r = div(7 * d + 45, 2)
          h = ircs(r, -d)
          x = div(h, 2)
          if h is None or x is None: continue
          # return solution
          yield (d, r, h, x)
      
      # find the first solution
      for (d, r, h, x) in generate():
        printf("height = {x} ft [d={d} r={r} -> h={h}]")
        break
      

      Solution: The masts are 30 ft tall.

      The ropes are 30.5 ft and 32.5 ft long (giving a total rope length of 63 ft), and they are anchored 5.5 ft away from the nearest mast.

      The next solution is found when the masts are 540 ft tall, which seems a bit impractical.


      Analytically we are looking for integer solutions to the equation:

      h² = [(7d + 45) / 2]² − d²

      where h is an even integer, say h = 2z (so z is the mast height in feet):

      (2z)² = (45/4)(d + 7)² − 45
      16z² − 45(d + 7)² = −180

      writing X = 4z (= 2h), Y = d + 7 we get a form of Pell’s equation:

      X² − 45 Y² = −180

      And we can use the pells.py module (from the py-enigma-plus repository – see: Teaser 2994) to quickly find larger solutions to the original equation:

      from enigma import (div, arg, ifirst, printf)
      import pells
      
      # generate (h, d, r, height) values
      def solve():
        # find solutions to: 16z^2 - 45(d + 7)^2 = -180
        for (z, d) in pells.diop_quad(16, -45, -180):
          d -= 7
          r = div(7 * d + 45, 2)
          if r and r > 0 and d > 0 and z > 0:
            yield (2 * z, d, r, z)
      
      # find the first N solutions
      N = arg(8, 0, int)
      for (i, (h, d, r, height)) in enumerate(ifirst(solve(), N), start=1):
        printf("[{i}] h={h} d={d} r={r} -> height = {height} ft")
      
      % time python3 teaser2653pells.py 8
      [1] h=60 d=11 r=61 -> height = 30 ft
      [2] h=1080 d=315 r=1125 -> height = 540 ft
      [3] h=19380 d=5771 r=20221 -> height = 9690 ft
      [4] h=347760 d=103675 r=362885 -> height = 173880 ft
      [5] h=6240300 d=1860491 r=6511741 -> height = 3120150 ft
      [6] h=111977640 d=33385275 r=116848485 -> height = 55988820 ft
      [7] h=2009357220 d=599074571 r=2096761021 -> height = 1004678610 ft
      [8] h=36056452320 d=10749957115 r=37624849925 -> height = 18028226160 ft
      python3 teaser2653pells.py 8  0.06s user 0.03s system 89% cpu 0.098 total
      

      Like

    • Frits's avatar

      Frits 7:49 pm on 26 September 2023 Permalink | Reply

       
      # 16z^2 - 45(d + 7)^2 = -180 
      # with X = 4z (= 2h), Y = d + 7 we get a form of Pell's equation:
      # X^2 - 45 Y^2 = -180 
      # the first soltion being the trivial one, X=0, Y=2 
      
      # Using Brian's method: https://brg.me.uk/?page_id=1468
      #
      # These solutions can also be generated recursively using:
      # x(n+1) = 9 x(n) + 60 y(n)
      # y(n+1) = 4/3 x(n) + 9 y(n)
      
      x, y = 0, 2
      for n in range(8):
        x, y = 9 * x + 60 * y, (4 * x) // 3 + 9 * y
        print(f"[{n + 1}] h={x // 2} d={y - 7} r={(7 * (y - 7) + 45 ) // 2} "
              f"-> height = {x // 4} ft")  
      

      Like

  • Unknown's avatar

    Jim Randell 12:33 pm on 19 September 2023 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2642: New world order 

    From The Sunday Times, 12th May 2013 [link] [link]

    In maths class Pat has been learning about progressions from his “New World” text book. The book has three sections, namely “Arithmetic”, “Algebra” and “Geometry” which overall run from page 1 to page 500 inclusive — with each section starting on a new page. The geometry section is over twice as long as the arithmetic section.

    As an exercise Pat calculated the sum of the page numbers in each section and he was surprised to find that the sum of the page numbers in “Algebra” was double that of the sum of the page numbers in “Geometry”.

    How many pages are there in the “Arithmetic” section?

    [teaser2642]

     
    • Jim Randell's avatar

      Jim Randell 12:34 pm on 19 September 2023 Permalink | Reply

      The total sum of all the page numbers = T = tri(500).

      If the Arithmetic section is pages 1 .. x, the Algebra section pages (x + 1) .. y, and the Geometry section pages (y + 1) .. 500, then the sums of the page numbers in each section are:

      Ar = tri(x)
      Al = tri(y) − tri(x)
      Ge = T − tri(y)

      tri(y) = T − Ge

      and:

      Al = 2 Ge

      Ge = (T − Ar) / 3

      So, by choosing a value for x, we can calculate the values of Ar, Al, Ge and hence y.

      This Python program runs in 55ms. (Internal runtime is 267µs).

      Run: [ @replit ]

      from enigma import (irange, tri, div, is_triangular, printf)
      
      # Ar: sum(1 .. x) = tri(x)
      # Al: sum(x + 1 .. y) = tri(y) - tri(x)
      # Ge: sum(y + 1 .. 500) = tri(500) - tri(y)
      #
      # Al = 2 * Ge
      
      # total sum of all pages
      T = tri(500)
      
      # consider the end page for arithmetic = x
      for x in irange(2, 165):
        Ar = tri(x)
        Ge = div(T - Ar, 3)
        if Ge is None: continue
        # calculate y
        y = is_triangular(T - Ge)
        if y is None or not x < y < 500: continue
        if not (500 - y > 2 * x): continue
        # output solution
        printf("Ar = 1..{x}; Al = {x1}..{y}; Ge = {y1}..500", x1=x + 1, y1=y + 1)
      

      Solution: There are 45 pages in the Arithmetic section.

      So the sections are:

      Arithmetic: pages 1 – 45 (= 45 pages); sum = 1035
      Algebra: pages 46 – 409 (= 364 pages); sum = 82810 (= Geometry × 2)
      Geometry: pages 410 – 500 (= 91 pages); sum = 41405

      Like

  • Unknown's avatar

    Jim Randell 8:33 am on 17 August 2023 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2634: Good company 

    From The Sunday Times, 17th March 2013 [link] [link]

    Each year at this time Pat’s company gives its staff a bonus to help them “drown their shamrocks” on the Irish national holiday. A total of £500 is shared out amongst all six employees (five men and the manager Kate) whose numbers of years of service consist of six consecutive numbers. Each man gets the same whole number of pounds for each year of his service and Kate gets a higher whole number of pounds for each year of her service. This means that, although Pat does not get the lowest bonus, he does get £20 less than Kate — even though he has served the company for a year longer than her.

    How much does Pat get?

    [teaser2634]

     
    • Jim Randell's avatar

      Jim Randell 8:33 am on 17 August 2023 Permalink | Reply

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

      Run: [ @replit ]

      from enigma import (irange, tuples, update, printf)
      
      # consider possible consecutive years of service (up to 50)
      for ys in tuples(irange(1, 50), 6):
        # total years service
        t = sum(ys)
        # choose a basic bonus amount
        for b in irange(1, 23):
          # remaining bonus
          r = 500 - t * b
          if not r > 0: break
          # make the list of basic bonuses
          bs = list(y * b for y in ys)
          # kate (not first or last) gets the remaining bonus
          for i in (1, 2, 3, 4):
            y = ys[i]
            if r % y != 0: continue
            # kate's bonus is 20 more than pat's
            (k, p) = (bs[i] + r, bs[i + 1])
            if k == p + 20:
              # output solution
              printf("pat = {p}, kate = {k} [years = {ys}, bonus = {bs}]", bs=update(bs, [(i, k)]))
      

      Solution: Pat’s bonus was £ 108.

      The six employees have worked at the company for 4, 5, 6, 7, 8, 9 years. With Kate having worked 8 years and Pat 9 years.

      Each of the non-managers receives a £12/year bonus:

      4 → £48
      5 → £60
      6 → £72
      7 → £84
      9 → £108 (Pat)

      Kate receives a £16/year bonus:

      8 → £128

      Like

    • Frits's avatar

      Frits 1:08 pm on 17 August 2023 Permalink | Reply

      # basic bonus amount: B,          we have B < 500 / (6 + 15)
      for B in range(1, 24):
        # years of service Y, ..., Y+5, we have (6 * Y + 15) * B < 500
        for Y in range(1, int((500 / B - 15) / 6) + 1):
          # index Kate in Y, ..., Y+5: I   (if I = 0 then Pat gets the lowest bonus)
          for I in range(1, 5):
            # Pat does get 20 pounds less than Kate,  K.(Y + I) - B.(Y + I + 1) = 20
            K, r = divmod(20 + B * (Y + I + 1), Y + I)
            if r: continue
            # a total of 500 pounds is shared out amongst all six employees
            if sum(B * (Y + i) if i != I else K * (Y + I) for i in range(6)) != 500:
              continue
            print(f"answer: {(Y + I + 1) * B} pounds")   
      

      or

      # basic bonus amount: B,          we have B < 500 / (6 + 15)
      for B in range(1, 24):
        # years of service Y, ..., Y+5, we have (6 * Y + 15) * B < 500
        for Y in range(1, int((500 / B - 15) / 6) + 1):
          # index Kate in Y, ..., Y+5: I   (if I = 0 then Pat gets the lowest bonus)
          sofar = sum(B * (Y + i) for i in range(6))
         
          # (Y + I).(K - B) = 500 - sofar,  I = index Kate in list years of service 
          for K, I in [(B + d[0], i) for i in range(1, 5) 
                       if not (d := divmod(500 - sofar, Y + i))[1]]:
            # Pat does get 20 pounds less than Kate
            if K * (Y + I) - B * (Y + I + 1) != 20: continue
            print(f"answer: {(Y + I + 1) * B} pounds")
      

      Like

  • Unknown's avatar

    Jim Randell 9:20 am on 27 June 2023 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2530: [A day at the races] 

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

    At the St Patrick’s Day races, Pat backed losers in each of the first three. Starting with £100, he bet a whole-number percentage of his money in the first race and a whole-number percentage of the remainder (but not a whole number of pounds) in the second. In the third, he bet a whole-number percentage of what was left, leaving a whole-number percentage of his £100. This final percentage was the sum of the three percentages he had bet and lost.

    How much was lost on the third race?

    This puzzle was originally published with no title.

    [teaser2530]

     
    • Jim Randell's avatar

      Jim Randell 9:21 am on 27 June 2023 Permalink | Reply

      If the percentages bet are p1, p2, p3, and the remaining amounts after each bet it lost are r1, r2, r3, then:

      p1 + p2 + p3 = r3
      r1 = 100 − p1
      r2 = r1 − p2 . r1 / 100 = r1(100 − p2)/100 [and r2 is not a whole number]
      r3 = r2 − p3 . r2 / 100 = r2(100 − p3)/100

      So we can choose a value for r3, and split that into the three percentages (p1, p2, p3) and check the bets turn out as required.

      And this approach does find the answer in a reasonable time, but it is not hugely fast.

      This Python program runs in 347ms. (Internal runtime is 237ms).

      Run: [ @replit ]

      from enigma import (irange, decompose, printf)
      
      # generate possible (p1, p2, p3) percentage values
      def generate():
        # consider the number of pounds remaining after the 3 bets
        for r in irange(3, 98):
          # this is equal to p1 + p2 + p3
          yield from decompose(r, 3, increasing=0, sep=0, min_v=1)
      
      # consider values for p1, p2, p3
      for (p1, p2, p3) in generate():
        # bet 1, we lose p1 pounds
        r1 = 100 - p1
        # bet 2, we lose not a whole number of pounds
        b2 = r1 * p2  # = (bet 2) * 100
        if b2 % 100 == 0: continue
        r2 = 100 * r1 - b2  # = (remaining 2) * 100
        # bet 3, we are left with (p1 + p2 + p3) pounds
        b3 = r2 * p3  # = (bet 3) * 100^2
        r3 = 100 * r2 - b3  # = (remaining 3) * 100^2
        if r3 != (p1 + p2 + p3) * 10000: continue
        # output solution
        printf(
          "p1={p1}% p2={p2}% p3={p3}% -> b1={b1:.2f} b2={b2:.2f} b3={b3:.2f}; r1={r1:.2f} r2={r2:.2f} r3={r3:.2f}",
          b1=float(p1), b2=(b2 * 0.01), b3=(b3 * 0.0001), r1=float(r1), r2=(r2 * 0.01), r3=(r3 * 0.0001),
        )
      

      Solution: £2.25 was bet (and lost) on the third race.

      The scenario is:

      start = £100.00
      bet 1 = 25% of £100.00 = £25.00
      bet 1 lost, remaining = £75.00
      bet 2 = 25% of £75.00 = £18.75
      bet 2 lost, remaining = £56.25
      bet 3 = 4% of £56.25 = £2.25
      bet 3 lost, remaining = £54.00
      sum of percentage values = 25 + 25 + 4 = 54.


      For a faster program we can do a bit of analysis:

      We can write (r1, r2, r3) in terms of (p1, p2, p3) to get:

      r1 = 100 − p1
      r2 = (100 − p1)(100 − p2)/100
      r3 = (100 − p1)(100 − p2)(100 − p3)/10000
      r3 = p1 + p2 + p3

      10000(p1 + p2 + p3) = (100 − p1)(100 − p2)(100 − p3)

      Note that 5^4 divides the LHS, so it must divide the terms of the RHS, so one of them must be a multiple of 25 (i.e. 25, 50, 75), and another must also be a multiple of 5.

      And this gives us a faster way to generate possible (p1, p2, p3) values.

      This Python 3 program uses a different [[ generate() ]] function, and runs in 61ms. (Internal runtime is 89µs).

      Run: [ @replit ]

      from enigma import (irange, div, subsets, printf)
      
      # generate possible percentage values
      def generate():
        # one of the values is a multiple of 25
        for a in (25, 50, 75):
          # an another is a multiple of 5
          for b in irange(5, 95 - a, step=5):
            c_ = div(10000 * (a + b + 100), 10000 + (100 - a) * (100 - b))
            if c_ is None or not (c_ < 100): continue
            # return the values (in some order)
            yield from subsets((a, b, 100 - c_), size=len, select='mP')
      
      # consider values for p1, p2, p3
      for (p1, p2, p3) in generate():
        # bet 1, we lose p1 pounds
        r1 = 100 - p1
        # bet 2, we lose not a whole number of pounds
        b2 = r1 * p2  # = (bet 2) * 100
        if b2 % 100 == 0: continue
        r2 = 100 * r1 - b2  # = (remaining 2) * 100
        # bet 3, we are left with (p1 + p2 + p3) pounds
        b3 = r2 * p3  # = (bet 3) * 100^2
        r3 = 100 * r2 - b3  # = (remaining 3) * 100^2
        if r3 != (p1 + p2 + p3) * 10000: continue
        # output solution
        printf(
          "p1={p1}% p2={p2}% p3={p3}% -> b1={b1:.2f} b2={b2:.2f} b3={b3:.2f}; r1={r1:.2f} r2={r2:.2f} r3={r3:.2f}",
          b1=float(p1), b2=(b2 * 0.01), b3=(b3 * 0.0001), r1=float(r1), r2=(r2 * 0.01), r3=(r3 * 0.0001),
        )
      

      In fact only one set of values (25, 25, 4) satisfies the analysis. And only with the 4% value for the final bet is the condition that the second amount bet is not a whole number of pounds satisfied.

      Like

    • Frits's avatar

      Frits 7:08 pm on 27 June 2023 Permalink | Reply

      Manual solution:

      Let the percentages NOT bet at each stage be p1, p2 and p3 and
      let the remainders after each stage be r1, r2 and r3 with the
      initial amount r0. Then r1 = r0.p1 / 100, r2 = r1.p2 / 100 and
      r3 = r2.p3 / 100 giving:
      
      p1 = 100.r1/r0 = 10000.r2/p2.r0 = 1000000.r3/p2.p3.r0
      
         p1.p2.p3 = 10^6.r3/r0
      
      Also:
      
        100.r3/r0 = (100 - p1) + (100 - p2) + (100 - p3)
      
      Eliminating r3/r0 now allows p3 to be derived from p1 and p2:
      
        p3 = (300 - p1 - p2).10^4 / (10^4 + p1.p2)
      
        p1 * p2 * p3 - 10^4 * (300 - p1 - p2 - p3) = 0
      
        p1 * p2 * p3 = (300 - p1 - p2 - p3) * 16 * 5^4
      
      if p1, p2 and p3 are all multiples of 5 then
      (300 - p1 - p2 - p3) is also a multiple of 5 so
      p1 * p2 * p3 must be a multiple of 5^5
      
      so we can conclude at least 2 out (p1, p2, p3) must be equal to 75,
      remaining percentage p* must be a multiple of 16
      and (300 - p1 - p2 - p3) must be a multiple of 9 (3 * 3)
          (150 - p*) must be a multiple of 9
          so p* mod 9 = 6
      the only multiple of 16 and mod 9 = 6 out of 64, 80 and 96 is 96
      so (p1, p2, p3) = (75, 75, 96) in any order
      
      p1 can't be 96 as 0.75 * 96 is a whole number
      p2 can't be 96 as 75 * 0.96 is the same whole number
      so (p1, p2, p3) = (75, 75, 96) in this order
      
      on the third race was lost: (3/4)^2 * 100 * (100 - 96) / 100 = 2.25 pounds
      

      Like

  • Unknown's avatar

    Jim Randell 10:25 am on 25 April 2023 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2627: Inversion 

    From The Sunday Times, 27th January 2013 [link] [link]

    In the woodwork class Pat cut a triangle from a sheet of plywood, leaving a hole in the sheet. The sides of the triangle were consecutive whole numbers of centimetres long. He then wanted to turn the triangle over and fit it back into the hole. To achieve this he had to cut the triangle into three pieces. He did this with two straight cuts, each cut starting at a midpoint of a side. Then each of the three pieces (two of which had the same perimeter) could be turned over and placed back in exactly its starting position in the hole.

    What are the lengths of the sides of the original triangle?

    [teaser2627]

     
    • Jim Randell's avatar

      Jim Randell 10:26 am on 25 April 2023 Permalink | Reply

      I imagined the wood was painted black on one side, and the goal is then to cut the triangle into 3 shapes, such that each shape will fit back into its original hole when turned over, to produce a completely black triangle of the same shape as the original.

      My first thought, when looking for a triangle with sides that are consecutive whole numbers, was to look at a (3, 4, 5) triangle, as these often crop up in these puzzles.

      Making two cuts between midpoints of sides divides the triangle into 3 shapes as shown:

      The quadrilateral Q, can be turned over to fit back into its vacated hole, but the triangles P and R do not, as they are not isosceles (but they are identical, and so have the same perimeter).

      But we can make these triangles isosceles, but in doing this the quadrilateral shrinks to zero size (and our two cuts become one):

      But what if we make the initial triangle such that it can be divided into two smaller isosceles triangles and a quadrilateral that can be turned over?

      We end up with something like this:

      And we can make Q and R have the same perimeter by making the base of R have length 2a.

      Now, the lengths {2a, 2b, 2(a + c)} must be consecutive integers in some order.

      So either:

      b = a + 1; c = 1/2
      b = a + 1/2; c = 1

      We can calculate the height h of triangles P and R to get:

      h² = a² − c² = b² − a²
      ⇒ 2a² − b² − c² = 0

      Substituting the possible values for b and c in terms of a, gives us quadratic equations (in a), which we can solve for viable solutions.

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

      Run: [ @replit ]

      from enigma import (enigma, Polynomial, sq, printf)
      
      Q = enigma.Rational()
      as_int = lambda x: enigma.as_int(x, include="+", default=None)
      
      # consider values for c
      for c in (Q(1, 2), 1):
        # form the polynomial
        b = Polynomial([Q(3, 2) - c, 1])
        p = Polynomial([-sq(c), 0, 2]) - sq(b)
        # find positive rational roots for a
        for a in p.quadratic_roots(domain='Q', include="+", F=Q):
          # find integer sides
          sides = list(map(as_int, [2 * a, 2 * b(a), 2 * (a + c)]))
          if all(sides):
            # output solution
            printf("sides = {sides} [c={c} p={p} -> a={a}]", sides=sorted(sides))
      

      Solution: The sides of the original triangle were 5cm, 6cm, 7cm.


      The polynomials we form are:

      a² − 2a − 5/4 = 0
      ⇒ (2a + 1)(2a − 5) = 0
      ⇒ a = 5/2, b = 7/2, c = 1/2

      a² − a − 5/4 = 0
      ⇒ no rational roots

      The second polynomial does have a real root at: a = (1 + √6) / 2.

      Which gives a triangle with sides of (3.449…, 4.449…, 5.449…).

      So although the puzzle does not work with a (3, 4, 5) triangle, it does work with a (3, 4, 5) + (√6 − 2) triangle.

      Like

  • Unknown's avatar

    Jim Randell 9:14 am on 28 March 2023 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2707: Good Times 

    From The Sunday Times, 10th August 2014 [link] [link]

    The Good Times bus company has a route from Timesboro to Teaseboro which passes through six other towns on its direct route. The distances from each town to the next are different whole numbers of miles, the largest of the seven distances being six miles more than the shortest. I worked out the average of those seven distances and my friend worked out the average distance between any two of the eight towns. Our answers used the same two digits but in reverse orders.

    How far is it from Timesboro to Teaseboro?

    [teaser2707]

     
    • Jim Randell's avatar

      Jim Randell 9:15 am on 28 March 2023 Permalink | Reply

      I am assuming all distances are measured along the road.

      The 7 distances between the towns are different whole numbers, where the largest of the values is 6 more than the smallest. So the distances, when ordered numerically, must form a sequence of 7 consecutive integers.

      We will denote them (x − 3, …, x + 3).

      The total of these distances (and the required answer) is: 7x.

      And the mean distance is: x.

      We can choose a 2-digit value for x (the mean of the distances), and then look for an arrangement of the distances that gives the mean pairwise distance to be the reverse of x.

      This Python program runs in 205ms.

      Run: [ @replit ]

      from enigma import (C, irange, multiset, subsets, tuples, printf)
      
      # count the number of pairwise distances between the 8 towns
      n = C(8, 2)
      
      # count the number of times each segment occurs in the total distance
      m = multiset()
      for k in irange(1, 7):
        for s in tuples(irange(7), k):
          m.update(s)
      
      # calculate total distance between pairs in <ss>
      pair_dist = lambda ss: sum(s * m[i] for (i, s) in enumerate(ss))
      
      # consider the digits a, b
      for (a, b) in subsets(irange(1, 9), size=2, select='P'):
        # x is the mean of the segment lengths
        # y is the reverse of x
        (x, y) = (10 * a + b, 10 * b + a)
      
        # choose an order for the segments
        for ss in subsets(irange(x - 3, x + 3), size=7, select='P'):
          if pair_dist(ss) == n * y:
            # total distance is 7x
            printf("d={d} [x={x} y={y} ss={ss}]", d=7 * x)
            break  # we only need one example
      

      Solution: The total distance is 98 miles.

      For example the distances could be: (13, 15, 12, 11, 14, 16, 17) miles, to give a total distance of 98 miles. (But there are other arrangements of these distances which also work).

      The mean distance is 98/7 = 14 miles.

      If we label the distances (a, b, c, d, e, f, g) then there are 28 ways of choosing pairs of distances, and the total of these pairwise distances is given by:

      T = 7(a + g) + 12(b + f) + 15(c + e) + 16d

      (In the program this is determined by lines 6-10).

      So in this case we get: T = 1148, and so the pairwise mean is: 1148/28 = 41 miles.


      And we can use this analysis to produce a faster program.

      If the arrangement of distances is:

      (x + a, x + b, x + c, x + d, x + e, x + f)

      where (a, …, f) are the offsets (−3, …, 3) in some order.

      Then the pairwise mean is given by:

      3x + (7(a + g) + 12(b + f) + 15(c + e) + 16d)/28

      And so we can find arrangements of (a, …, f) that are multiples of 28, and these will give integer pairwise distances.

      We can then look for instances where a 2-digit mean distance (= x) along with a viable set of offsets gives a pairwise mean that is the reverse of x.

      Run: [ @replit ]

      from enigma import (irange, subsets, dot, div, printf)
      
      # find ordering of offsets, that give an integer pairwise mean
      rs = dict()
      for vs in subsets(irange(-3, +3), size=7, select='P'):
        k = div(dot([7, 12, 15, 16, 15, 12, 7], vs), 28)
        if k is not None:
          rs[k] = vs
      
      # consider digit reversed pairs of 2-digit numbers
      for (a, b) in subsets(irange(1, 9), size=2, select='P'):
        # x is the mean of the segment lengths
        # y is the reverse of x
        (x, y) = (10 * a + b, 10 * b + a)
      
        # look for corresponding offsets
        for (k, vs) in rs.items():
          if 3 * x + k == y:
            ss = list(x + v for v in vs)
            printf("d={d} [x={x} y={y} ss={ss}]", d=7 * x)
      

      Like

  • Unknown's avatar

    Jim Randell 10:55 am on 3 January 2023 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2702: Problemblem 

    From The Sunday Times, 6th July 2014 [link] [link]

    Every Sunday customers in our pub try to solve the current Teaser, so Pat the barman has designed an appropriate pub logo. This is a small rectangular flag divided into three smaller rectangles, coloured red, green and blue. Their sides are all whole numbers of centimetres, the area of the green rectangle is twice the area of the red rectangle, and the perimeter of the red rectangle is twice the perimeter of the green rectangle. Furthermore, the area of the flag is a three-figure number of square centimetres, the same as the area of the blue rectangle but with the three digits in the reverse order.

    What is the area of the flag?

    [teaser2702]

     
    • Jim Randell's avatar

      Jim Randell 10:56 am on 3 January 2023 Permalink | Reply

      This Python program generates candidate areas for the red, green and blue rectangles. It then looks for appropriate dimensions that satisfy the remaining conditions, and allow them to fit together as specified.

      It runs in 60ms. (Internal runtime is 7.3ms).

      Run: [ @replit ]

      from enigma import (irange, nrev, div, divisors_pairs, cproduct, ordered, printf)
      
      # fit the rectangles (x1, y1), (x2, y2) and a rectangle area A together
      def fit(x1, y1, x2, y2, A):
        for z1 in {x1, y1}:
          # can we match a side?
          if z1 in {x2, y2}:
            (x, y) = (z1, div(A, z1))
            if y is not None:
              yield ordered(x, y)
          # can we join sides?
          for z2 in {x2, y2}:
            y = (x1 + y1 - z1) - (x2 + y2 - z2)
            if y > 0:
              if z2 * y == A:
                yield ordered(z2, y)
            elif y < 0:
              y = -y
              if z1 * y == A:
                yield ordered(z1, y)
      
      # consider the area of the flag, it is a 3-digit number
      for A in irange(100, 999):
        # and the area of blue is the reverse of this
        B = nrev(A)
        if B < 100 or not (B < A): continue
        # the area of red and green are 1/3 and 2/3 of what is left
        R = div(A - B, 3)
        if R is None: continue
        G = 2 * R
      
        # find dimensions for red and green, such that red has twice the perimeter of green
        for ((rx, ry), (gx, gy)) in cproduct(divisors_pairs(v) for v in (R, G)):
          if not (rx + ry == 2 * (gx + gy)): continue
      
          # find dimensions of blue that fits to make a rectangle
          for (bx, by) in fit(rx, ry, gx, gy, B):
            # output solution
            printf("A={A} [R={R} G={G} B={B}]", R=(rx, ry), G=(gx, gy), B=(bx, by))
      

      Solution: The area of the flag is: 231 sq cm.

      The flag is a 7×33 rectangle, as shown below:

      The red rectangle is: 1×33. The green rectangle is 6×11. The blue rectangle is 6×22.

      (Dimensions in cm).


      We have sometime come across claims that a phrase such as “a whole number of pounds” (or similar – see Teaser 2869, Teaser 2692) necessarily implies a value must be at least 2 (although I do not accept this argument myself). But this puzzle uses “a whole number of centimetres” to refer to a value of 1, and without allowing this value there would be no solution.

      Like

    • GeoffR's avatar

      GeoffR 3:28 pm on 3 January 2023 Permalink | Reply

      % A Solution in MiniZinc - same rectangle arrangement
      % Red = A * B, Green = C * D, Blue = C  * E, where B = D + E
      include "globals.mzn";
      
      % Assumed Rectangle/Area dimension ranges
      var 1..25:A; var 1..50:B;
      var 1..25:C; var 1..25:D; var 1..25:E;
      
      var 10..999:flag_area;
      var 1..999:red_area; var 1..999:green_area; var 1..999:blue_area;
      var 4..200:red_perim; var 4..200:green_perim;
      
      % Red length = Green length + Blue length
      constraint B == D + E;
      
      % Digits (x,y,z) for 3-digit flag area
      var 1..9:x; var 0..9:y;  var 1..9:z; 
      
      % Find three digits of flag area
      constraint x == flag_area div 100 /\ y == flag_area div 10 mod 10
      /\ z == flag_area mod 10;
      
      % Flag area digits are reversed for the blue area
      constraint blue_area = C * E /\ blue_area == 100*z + 10*y + x; 
      constraint green_area = C * D /\ red_area = A * B;
      constraint red_perim = 2 * (A + B);
      constraint green_perim == 2 * (C + D);
      
      % Area/perimeter relationships
      constraint green_area = 2 * red_area;
      constraint red_perim = 2 * green_perim;
      constraint flag_area = red_area + blue_area + green_area;
      
      solve satisfy;
      
      output [ "Flag area = " ++ show(flag_area) ++ " sq. cm.," 
      ++ " Red area = " ++ show(red_area) ++ " sq. cm." 
      ++ "\n" ++ "Green area = " ++ show(green_area) ++ " sq. cm.,"
      ++ " Blue area = " ++ show(blue_area) ++ " sq. cm."  ];
      
      % Flag area = 231 sq. cm., Red area = 33 sq. cm.
      % Green area = 66 sq. cm., Blue area = 132 sq. cm.
      % ----------
      % ==========
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:56 am on 15 December 2022 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2686: Good innings 

    From The Sunday Times, 16th March 2014 [link] [link]

    Tomorrow is St Patrick’s Day, so Pat will go on his usual tour of inns. Last year each inn had the same admission charge (made up of a two-figure number of pounds plus a two-figure number of pence surcharge). This four-figure total of pence happened to be the same as the four-figure number of pence Pat started the evening with, except that the order of the digits was reversed. In the first inn he spent one pound less than a third of what he had left after paying admission. In the second inn he also spent one pound less than a third of what he came in with after paying admission. That left him just enough to get into the third inn.

    How much was that?

    [teaser2686]

     
    • Jim Randell's avatar

      Jim Randell 9:57 am on 15 December 2022 Permalink | Reply

      The entry fee is a 4-digit number of pence, and Pat’s initial funds are the reverse of this number (also a 4-digit number).

      As Pat pays 3 entry fees, each one cannot be more than £33.33, so we don’t need to check entry fees more than that.

      Run: [ @replit ]

      from enigma import (irange, nreverse, as_int, ediv, catch, printf)
      
      # check for viable spending pattern
      def check(entry):
        # starting amount is the reverse of the entry fee
        funds = start = nreverse(entry)
        # first pub
        funds = as_int(funds - entry, "+")
        spend1 = as_int(ediv(funds, 3) - 100, "+")
        funds = as_int(funds - spend1, "+")
        # second pub
        funds = as_int(funds - entry, "+")
        spend2 = as_int(ediv(funds, 3) - 100, "+")
        funds = as_int(funds - spend2, "+")
        # third pub
        funds = as_int(funds - entry, "0")
        # output solution
        printf("entry = {entry}; start = {start}; spend1 = {spend1}; spend2 = {spend2}")
      
      # consider 4-digit entry fees
      for entry in irange(1000, 3333):
        catch(check, entry)
      

      Solution: The entry fee was: £14.56.

      So Pat’s finances go:

      start = £65.41
      pub 1 entry = £14.56 → £50.85
      pub 1 spend = £15.95 → £34.90
      pub 2 entry = £14.56 → £20.34
      pub 2 spend = £5.78 → £14.56
      pub 3 entry = £14.56 → £0.00

      Like

  • Unknown's avatar

    Jim Randell 9:19 am on 8 December 2022 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2694: Impaired 

    From The Sunday Times, 11th May 2014 [link] [link]

    Pat was given a rod of length 40 centimetres and he cut it into six pieces, each piece being a whole number of centimetres long, and with the longest piece twice as long as one of the other pieces. Then he took two of the pieces and wrote down their total length. He did this for every possible set of two pieces and he never got the same total more than once. However, when he started again and repeated the process but with sets of three pieces, one of the totals was repeated.

    What (in increasing order) were the lengths of his pieces?

    [teaser2694]

     
    • Jim Randell's avatar

      Jim Randell 9:20 am on 8 December 2022 Permalink | Reply

      Each of the pieces must be of a different length. Otherwise there will be duplication amongst the 2-subsets.

      This Python program runs in 52ms. (Internal runtime is 533µs).

      Run: [ @replit ]

      from enigma import (
        irange, inf, decompose, subsets, seq_all_different as all_different, printf
      )
      
      # totals of k-subsets of ps
      subs = lambda ps, k: (sum(ss) for ss in subsets(ps, size=k))
      
      # there are pieces of length x and 2x
      for x in irange(1, inf):
        # remaining length (must allow 4 more pieces)
        r = 40 - 3 * x
        if r < 10: break
        # cut the remaining length into 4 pieces
        for ps in decompose(r, 4, increasing=1, sep=1, min_v=1, max_v=2 * x - 1):
          ps += (x, 2 * x)
      
          # all 2-subsets have different totals
          if not all_different(subs(ps, 2)): continue
      
          # but there are exactly 2 3-subsets that have the same total
          ss = list(subs(ps, 3))
          if len(set(ss)) + 1 != len(ss): continue
      
          # output solution
          printf("ps = {ps}", ps=sorted(ps))
      

      Solution: The lengths of the pieces are (cm): 1, 4, 5, 7, 9, 14.

      This gives two 3-subsets that give a total of 20:

      1 + 5 + 14 = 20
      4 + 7 + 9 = 20

      The set (1, 3, 4, 5, 9, 18) gives no repeated totals for 2-subsets or 3-subsets.

      Like

    • GeoffR's avatar

      GeoffR 8:01 pm on 8 December 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % six lengths of rod - assume max single length = 30cm.
      var 1..30:A; var 1..30:B; var 1..30:C; 
      var 1..30:D; var 1..30:E; var 1..30:F;
      
      constraint all_different([A, B, C, D, E, F]);
      
      % put the rods in increasing size order
      constraint increasing([A, B, C, D, E, F]);
      
      % initial rod is 40 cm long
      constraint A + B + C + D + E + F == 40;
      
      % the longest rod is twice as long as one of the other rods
      constraint F == 2*E \/ F == 2*D \/ F == 2*C \/ F == 2*B \/ F == 2*A;   
      
      % sub-totals for 2 rods (15 no.)-  UB = 30 + 29 = 59
      var 3..59: t1; var 3..59: t2; var 3..59: t3; var 3..59: t4; var 3..59: t5;
      var 3..59: t6; var 3..59: t7; var 3..59: t8; var 3..59: t9; var 3..59: t10;
      var 3..59: t11; var 3..59: t12; var 3..59: t13; var 3..59: t14; var 3..59: t15;
      
      % All two rod sums
      constraint t1 = A + B /\ t2 = A + C /\ t3 = A + D /\ t4 = A + E
      /\ t5 = A + F /\ t6 = B + C /\ t7 = B + D /\ t8 = B + E 
      /\ t9 = B + F /\ t10 = C + D /\ t11 = C + E /\ t12 = C + F
      /\ t13 = D + E /\ t14 = D + F /\ t15 = E + F;
      
      % All 2 rod sums are different
      constraint all_different([t1, t2, t3, t4, t5, t6, t7, t8, t9, t10,
      t11, t12, t13, t14, t15]);
      
      % sub-totals for 3 rods (20 no.)-  UB = 30+29+28 = 87
      var 6..87:T1; var 6..87:T2; var 6..87:T3; var 6..87:T4; 
      var 6..87:T5; var 6..87:T6; var 6..87:T7; var 6..87:T8;
      var 6..87:T9; var 6..87:T10; var 6..87:T11; var 6..87:T12;
      var 6..87:T13; var 6..87:T14; var 6..87:T15; var 6..87:T16; 
      var 6..87:T17; var 6..87:T18; var 6..87:T19; var 6..87:T20;
      
      % All 3-digit sums - one of sums is duplicated - see output
      constraint T1 = A+B+C /\ T2 = A+B+D /\ T3 = A+B+E /\ T4 = A+B+F
      /\ T5 = A+C+D /\  T6 = A+C+E /\ T7 = A+C+F /\ T8 = A+D+E
      /\ T9 = A+D+F /\ T10 = A+E+F /\ T11 = B+C+D /\ T12 = B+C+E
      /\ T13 = B+C+F /\ T14 = B+D+E /\ T15 = B+D+F /\ T16 = B+E+F
      /\ T17 = C+D+E /\ T18 = C+D+F /\ T19 = C+E+F /\ T20 = D+E+F;
      
      % array of 3-digit sums
      array[1..20] of var 6..87: y; 
      constraint y = [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10,
      T11, T12, T13, T14, T15, T16 ,T17, T18, T19, T20];
      
      % array of 2-digit sums
      array[1..15] of var 3..59: x; 
      constraint x = [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11,
      t12, t13, t14, t15];
      
      solve satisfy;
      
      output[" Six Rod Pieces are [A,B,C,D,E,F] = " ++ show([A,B,C,D,E,F]) 
      ++ " \n 2-rod length sums: " ++ show(x)
      ++ " \n 3-rod length sums: " ++ show(y)];
      
      % Six Rod Pieces are [A,B,C,D,E,F] = [1, 4, 5, 7, 9, 14]  << ANSWER
      %  2-rod length sums: [5, 6, 8, 10, 15, 9, 11, 13, 18, 12, 14, 19, 16, 21, 23] 
      %  3-rod length sums: [10, 12, 14, 19, 13, 15, 20, 17, 22, 24, 16,
      %  18, 23, 20, 25, 27, 21, 26, 28, 30]
      % ----------
      %  - INVALID, No duplicated 3 rod sums
      %  Six Rod Pieces are [A,B,C,D,E,F] = [1, 3, 4, 5, 9, 18] 
      %  2-rod length sums: [4, 5, 6, 10, 19, 7, 8, 12, 21, 9, 13, 22, 14, 23, 27] 
      %  3-rod length sums: [8, 9, 13, 22, 10, 14, 23, 15, 24, 28, 12, 16,
      %  25, 17, 26, 30, 18, 27, 31, 32]
      % ----------
      % ==========
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:41 am on 23 August 2022 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2738: Prime day for the Irish 

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

    St Patrick’s Day is March 17 and it is a prime day in many ways:

    What number month? 3;
    What number day? 17;
    How many letters in “March”? 5;
    How many days in March? 31.

    I asked Pat the same questions about his birthday this year — but I simply asked whether the four answers were prime or not. When he had told me he said: “Now, if I told you its day of the week this year, you should be able to work out my birthday”.

    Then, without me actually being told the day, I was indeed able to work out his birthday.

    What is his birthday?

    [teaser2738]

     
    • Jim Randell's avatar

      Jim Randell 10:42 am on 23 August 2022 Permalink | Reply

      This Python program runs in 57ms. (Internal run time is 1.7ms).

      Run: [ @replit ]

      from datetime import (date, timedelta)
      from enigma import (repeat, is_prime, filter_unique, unpack, printf)
      
      # number of letters in each month (English names)
      months = (
        "january", "february", "march", "april", "may", "june", "july",
        "august", "september", "october", "november", "december",
      )
      mlen = dict((k, len(x)) for (k, x) in enumerate(months, start=1))
      
      # number of days in each month
      mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      
      # generate dates in 2015, return (<date>, <answers-to-questions>)
      def generate():
        inc = lambda x, i=timedelta(days=1): x + i
        # consider consecutive days in 2015
        for d in repeat(inc, date(2015, 1, 1)):
          if d.year > 2015: break
          # are the following prime?
          qs = tuple(is_prime(x) for x in (
            # 1. the month number
            d.month,
            # 2. the day number
            d.day,
            # 3. the number of letters in the month
            mlen[d.month],
            # 4. the number of days in the month
            mdays[d.month],
          ))
          yield (d, qs)
      
      # candidate dates
      rs = generate()
      # selection functions
      fn_d = unpack(lambda d, qs: d)
      fn_qs = unpack(lambda d, qs: qs)
      fn_qs_dow = unpack(lambda d, qs: (qs, d.isoweekday()))
      
      # if we knew the answers to the questions _and_ the day of the week
      # then we could work out the date
      rs = filter_unique(rs, fn_qs_dow, fn_d).unique
      
      # now we can work out the date only knowing the answers to the questions
      rs = filter_unique(rs, fn_qs, fn_d).unique
      
      # output solutions
      for (d, qs) in rs:
        printf("date = {d} [qs={qs}]", d=d.strftime('%A, %d %B %Y'))
      

      Solution: Pat’s birthday is on 29th November.


      There are 9 dates that can be identified as unique knowing the answers to the questions and the day of the week (in 2015).

      If the answers to the questions are: (not prime, prime, prime, not prime) we have:

      Mon ⇒ 13th April 2015
      Tue ⇒ 7th April 2015
      Wed ⇒ 29th April 2015
      Sat ⇒ 11th April 2015

      If the answers to the questions are: (prime, prime, not prime, prime) we have:

      Mon ⇒ 13th July 2015
      Tue ⇒ 7th July 2015
      Wed ⇒ 29th July 2015
      Sat ⇒ 11th July 2015

      And if the answers to the questions are: (prime, prime, not prime, not prime) we have:

      Sun ⇒ 29th November 2015

      As the setter does not need to be told the day of the week to find the birthday from these 9 he must have been told (prime, prime, not prime, not prime) so the candidate dates are narrowed down to a single possibility.

      Like

    • Frits's avatar

      Frits 2:48 pm on 23 August 2022 Permalink | Reply

      partition_unique has recently been updated.

         
      from calendar import month_name, monthrange, day_name, weekday
      
      # https://brg.me.uk/?page_id=4800
      from partition_unique import partition_unique
      
      # primes up to 365
      P = {3, 5, 7, 11, 13, 17, 19}
      P |= {2} | {x for x in range(23, 366, 2) if all(x % p for p in P)}
      
      year = 2015
      sols = []
      # generate dates in 2015
      # store (<answers-to-questions>, <day name>, <(month, day)>)
      for month in range(1, 13):
        month_length = monthrange(year, month)[1]
        len_month_name = len(month_name[month])
        for d in range(1, month_length + 1):
          # find the day of the week
          d_name = day_name[weekday(year, month, d)]
          
          sols.append(((# 1. the month number
                       month in P, 
                       # 2. the day number
                       d in P, 
                       # 3. the number of letters in the month
                       len_month_name in P,
                       # 4. the number of days in the month
                       month_length in P),
                       d_name, (month, d) 
                     ))   
          
      f_answs = lambda answs, nm, md: answs
      f_answs_nm = lambda answs, nm, md: (answs, nm)
      f_md = lambda answs, nm, md: md
      
      # find those solutions for which the answers plus day of the week
      # lead to the date
      sols = partition_unique(sols, f_answs_nm, f_md)[0]
      
      # find those solutions for which the answers lead to the date
      sols = partition_unique(sols, f_answs, f_md)[0]
      
      for _, _, md in sols:
        print(f"answer: {month_name[md[0]]} {md[1]}")
      

      Like

    • NigelR's avatar

      NigelR 5:19 pm on 25 August 2022 Permalink | Reply

      Not, I suspect, very ‘pythonic’ but seems to run ok. enigma timer gives 3.2ms:

      prim=[2,3,5,7,11,13,17,19,23,29,31]
      mths=["january", "february", "march", "april", "may", "june", "july",
        "august", "september", "october", "november", "december"]
      days=[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
      dow=['We','Th','Fr','Sa','Su','Mo','Tu']  # 1 Jan 2015 fell on a Thursday
      #i = month (0-11),j = day of month.  Returns string of day of week (Mo-Su)
      day = lambda i,j: dow[(j+sum([days[k] for k in range(i)]))%7]
      #generate answers to questions + day of week for each day in format 'n/p n/p n/p n/p dd'
      ans=[[''.join(['p' if x+1 in prim else 'n','p' if y in prim else 'n', 'p' if len(mths[x]) in prim else 'n',
             'p'if days[x] in prim else 'n',day(x,y)]),x,y] for x in range(12) for y in range (1,days[x]+1)]
      #create dictionary with count of each occurrence of 'n/p n/p n/p n/p dd'
      cand={x[0]:[y[0] for y in ans].count(x[0]) for x in ans }
      #remove non-unique entries
      lst=[x for x in cand.keys() if cand[x] == 1] 
      #identify whether there is a unique day of week entry:
      result = [x for x in lst if [y[4:] for y in lst].count(x[4:])==1]
      if len(result)>1:
          print('No unique solution found')
          exit()
      print([['Birthday is' + ' '+str( x[2]) + ' '+ str(mths[x[1]+1].capitalize())] for x in ans if x[0]==result[0]][0][0])
      

      Like

  • Unknown's avatar

    Jim Randell 8:56 am on 23 June 2022 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2753: No question about it! 

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

    “Sign Works” make rectangular signs of all sizes. Pat ordered a sign for the pub with the following instructions:

    “The length must be twice the width. Furthermore, the dimensions should be such that if you take its length (in centimetres), square the sum of its digits and take away the length itself, then you get the width. On the other hand, if you take its width (in centimetres), square the sum of its digits and take away the width itself, then you get the length”.

    This was enough information for the sign-maker to calculate the dimensions of the sign.

    What were they?

    [teaser2753]

     
    • Jim Randell's avatar

      Jim Randell 8:57 am on 23 June 2022 Permalink | Reply

      This is very straightforward.

      from enigma import (irange, inf, dsum, sq, printf)
      
      for width in irange(1, inf):
        length  = 2 * width
        if width + length == sq(dsum(width)) == sq(dsum(length)):
          printf("width={width} length={length}")
          break
      

      Solution: The sign was 27 cm × 54 cm.

      Like

    • GeoffR's avatar

      GeoffR 9:30 am on 23 June 2022 Permalink | Reply

      
      % A Solution in MiniZinc
      include "globals.mzn";
      
      var 1..9:a; var 1..9:b; % length digits
      var 1..9:c; var 1..9:d; % width digits
      
      % Length and width of sign ( assumed 2 digits)
      var 10..99:L == 10*a + b; 
      var 10..99:W == 10*c + d;
      
      constraint L == 2 * W;
      
      constraint W == (a + b) * (a + b) - L;
      
      constraint L == (c + d) * (c + d) - W;
      
      solve satisfy;
      
      output [ "Length(cm) = " ++ show(L) ++ ", Width(cm) = " ++ show(W) ];
      
      % Length(cm) = 54, Width(cm) = 27
      % ----------
      % ==========
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 10:10 am on 1 July 2021 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2799: Desert patrol 

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

    Pat is at Base Camp in the desert. There is enough fuel at the base for his vehicle to travel 420 miles, but the vehicle can hold (in its tank and in cans) at most enough fuel for 105 miles. On any journey he can leave some fuel in cans at any point and then pick up some or all of it whenever he passes in future. He has worked out that there is just enough fuel for him to reach the Main Camp.

    How far is it between the two camps?

    [teaser2799]

     
    • Jim Randell's avatar

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

      This is a similar idea to Enigma 1732, except that this is for a 1 way journey.

      See: Jeep Problem [ @wikipedia ].

      This Python program runs in 54ms.

      Run: [ @replit ]

      from enigma import (Rational, irange, inf, printf)
      
      Q = Rational()  # choose an implementation of rationals
      
      T = 420  # total amount of fuel available
      R = 105  # range
      
      # consider amount of fuel required for a maximal distance in k trips
      f = d = 0
      for k in irange(1, inf):
        f += R
        d += R * Q(1, 2 * k - 1)
        printf("{k} trips, fuel = {f}, distance = {x:.2f} ({d})", x=float(d))
        # stop when the fuel required reaches (or exceeds) the available fuel
        if not (f < T): break
      

      Solution: The distance between the camps is 176 miles.

      The journey will require 4 trips:

      Trip 1:
      With a full load of fuel (105 miles), drive 15 miles (= 105 / 7).
      Make a fuel dump with 75 miles of fuel, and use the remaining 15 miles to drive back.
      There is 315 miles of fuel remaining at base camp.

      Trip 2:
      With a full load (105 miles), drive 15 miles, and fill up at dump 1 (leaving 60 miles at dump 1).
      Drive a further 21 miles (= 105 / 5), and make a fuel dump with 63 miles of fuel.
      Drive back to dump 1 using the remaining 21 miles of fuel, and refuel for the last 15 miles (leaving 45 miles at dump 1).
      There is 210 miles of fuel remaining at base camp.

      Trip 3:
      With a full load (105 miles), drive 15 miles to dump 1 and refuel (leaving 30 miles at dump 1).
      Drive 21 miles to dump 2 and refuel (leaving 42 miles of fuel at dump 2).
      Drive 35 miles (= 105 / 3) to dump 3 and leave 35 miles of fuel.
      Drive back to dump 2 using the remaining 35 miles of fuel, and refuel for the 21 miles to dump 1 (leaving 21 miles at dump 2).
      Drive back to dump 1 and refuel for the 15 miles to base camp (leaving 15 miles at dump 1).
      There is 105 miles of fuel remaining at base camp.

      Trip 4:
      Fill up with the remaining fuel (105 miles), drive 15 miles to dump 1 and refuel (exhausting dump 1).
      Drive 21 miles to dump 2 and refuel (exhausting dump 2).
      Drive 35 miles to dump 3 and refuel (exhausting dump 3).
      Drive 105 miles (= 105 / 1) and arrive at main camp.
      Total distance = 15 + 21 + 35 + 105 = 176 miles.

      Like

    • Frits's avatar

      Frits 1:44 pm on 8 July 2021 Permalink | Reply

      Similar (next one R=315, T = 5*R)

      T = 420       # total amount of fuel available
      R = 105       # range
      
      k = T // R    # number of trips
      
      # number of times traject <n-1>th depot to the <n>th depot is travelled 
      V = lambda k, n : 2 * (k - n) + 1
      
      # T / R = k = 4 so 4 trips can be made setting up 3 depots.
      # The traject <n-1>th depot to the <n>th depot (n = 1..3) is travelled 
      # V(n) = 2 * (k - n) + 1 times (where 0th depot is the base camp) so we need
      # to split R into V(n) equal parts and store V(n) - 2 parts at the depot 
      # leaving 2 parts to reach and return from the <n>th depot
      # this means the <n>th depot is sum (i=1..n, R / V(n)) miles from base camp
       
      # consider amount of fuel required for a maximal distance in k trips
      print("answer:", R + sum(R // V(k, n) for n in range(1, k)), "miles")
      

      Like

      • Frits's avatar

        Frits 3:39 pm on 8 July 2021 Permalink | Reply

        or

        from math import log
        
        def H(n):
            """Returns an approximate value of n-th harmonic number.
        
               http://en.wikipedia.org/wiki/Harmonic_number
            """
            # Euler-Mascheroni constant
            gamma = 0.57721566490153286060651209008240243104215933593992
            return gamma + log(n) + 0.5 / n - 1 / (12 * n**2) + 1 / (120 * n**4)
            
        R = 105    
        n = 4
        print("answer:", round((H(2 * n - 1) - 0.5 * H(n - 1)) * R), "miles")
        

        Like

    • Frits's avatar

      Frits 1:43 pm on 17 July 2021 Permalink | Reply

      Different order:

      # make 3 return trips to 1st depot (15 miles from Base Camp) and 
      #      each time drop 75 miles of fuel at 1st depot
      # make 4th trip to 1st depot and fill up (210 miles of fuel left at 1st depot)
      # make 2 return trips from 1st to 2nd depot (21 miles from 1st depot) and
      #      each time drop 63 miles of fuel at 2nd depot
      # make 3th trip from 1st to 2nd depot and fill up 
      #    (0 / 105 miles of fuel left at 1st/2nd depot)
      # make a return trip from 2nd to 3rd depot (35 miles from 2nd depot) and
      #      drop 35 miles of fuel at 3rd depot
      # make 2nd trip from 2nd to 3rd depot and fill up
      #    (0 / 0 / 0 miles of fuel left at 1st/2nd/3rd depot)
      # drive 150 miles to Main Camp
      
      # Total miles: 15 + 21 + 35 + 150 = 176 miles
      

      Like

  • Unknown's avatar

    Jim Randell 8:31 am on 15 September 2020 Permalink | Reply
    Tags: by: C Higgins,   

    Teaser 2719: Foursums 

    From The Sunday Times, 2nd November 2014 [link] [link]

    In a woodwork lesson the class was given a list of four different non-zero digits. Each student’s task was to construct a rectangular sheet whose sides were two-figure numbers of centimetres with the two lengths, between them, using the four given digits. Pat constructed the smallest possible such rectangle and his friend constructed the largest possible. The areas of these two rectangles differed by half a square metre.

    What were the four digits?

    [teaser2719]

     
    • Jim Randell's avatar

      Jim Randell 8:32 am on 15 September 2020 Permalink | Reply

      This Python program runs in 55ms.

      Run: [ @repl.it ]

      from enigma import (subsets, irange, Accumulator, partitions, product, nconcat, join, printf)
      
      # generate (width, height) pairs from digits <ds>
      def generate(ds):
        for (ws, hs) in partitions(ds, 2):
          for (w, h) in product((ws, ws[::-1]), (hs, hs[::-1])):
            yield (nconcat(w), nconcat(h))
      
      # choose 4 different non-zero digits
      for ds in subsets(irange(1, 9), size=4):
        # record the min/max areas
        rs = Accumulator.multi(fns=[min, max])
      
        # form the digits into width, height pairs
        for (w, h) in generate(ds):
          # calculate the area: A = ab x cd
          A = w * h
          rs.accumulate_data(A, (w, h))
      
        # do the max and min areas differ by 5000 cm^2 ?
        (mn, mx) = rs
        if mx.value - mn.value == 5000:
          printf("digits = {ds}")
          printf("-> min area = {mn.value} = {wh}", wh=join(mn.data, sep=" x "))
          printf("-> max area = {mx.value} = {wh}", wh=join(mx.data, sep=" x "))
          printf()
      

      Solution: The four digits are: 1, 6, 7, 8.

      The minimum area is: 17 × 68 = 1156.

      The maximum area is: 81 × 76 = 6156.

      Like

    • Frits's avatar

      Frits 11:55 pm on 15 September 2020 Permalink | Reply

       
      from enigma import SubstitutedExpression, irange, subsets
      
      # determine smallest possible rectangle 
      def mini(A, B, C, D):
        minimum = 9999
        for s in subsets(str(A)+str(B)+str(C)+str(D), size=4, select="P"):
          area = (int(s[0])*10 + int(s[1])) * (int(s[2])*10 + int(s[3]))
          minimum = min(minimum, area)
        return minimum  
        
      # determine larges possible rectangle
      def maxi(A, B, C, D):
        maximum = 0 
        for s in subsets(str(A)+str(B)+str(C)+str(D), size=4, select="P"):
          area = (int(s[0])*10 + int(s[1])) * (int(s[2])*10 + int(s[3]))
          maximum = max(maximum, area)
        return maximum   
          
      
      p = SubstitutedExpression([
          "(maxi(A, B, C, D) - mini(A, B, C, D)) == 5000",
          # only report a solution once
          "A < B",
          "B < C", 
          "C < D", 
          ],
          verbose=0,
          answer="(A, B, C, D)",
          env=dict(mini=mini, maxi=maxi), # external functions
          digits=irange(1, 9),
      )
      
      # Solve and print answer
      print("Answer")
      for (_, ans) in p.solve(): 
        print(ans)
      
      
      # Output:
      #
      # Answer
      # (1, 6, 7, 8)
      

      Like

      • Frits's avatar

        Frits 11:59 pm on 15 September 2020 Permalink | Reply

        Also possible is to determine the smallest and largest possible rectangle in one function/loop.

        Like

      • Jim Randell's avatar

        Jim Randell 2:15 pm on 16 September 2020 Permalink | Reply

        @Frits: There’s no need to turn the digits into strings and back.

        Here’s a version that just operates on the digits directly:

        from enigma import (SubstitutedExpression, irange, subsets, nconcat)
         
        # determine smallest possible rectangle 
        def mini(A, B, C, D):
          minimum = 9999
          for s in subsets((A, B, C, D), size=4, select="P"):
            area = nconcat(s[:2]) * nconcat(s[2:])
            minimum = min(minimum, area)
          return minimum  
           
        # determine larges possible rectangle
        def maxi(A, B, C, D):
          maximum = 0
          for s in subsets((A, B, C, D), size=4, select="P"):
            area = nconcat(s[:2]) * nconcat(s[2:])
            maximum = max(maximum, area)
          return maximum   
         
        p = SubstitutedExpression([
              "(maxi(A, B, C, D) - mini(A, B, C, D)) = 5000",
              # only report a solution once
              "A < B",
              "B < C", 
              "C < D", 
            ],
            answer="(A, B, C, D)",
            env=dict(mini=mini, maxi=maxi), # external functions
            digits=irange(1, 9),
        )
         
        # Solve and print answer
        p.run(verbose=16)
        

        And you could use Python’s argument syntax to deal with the list of digits without unpacking them:

        def mini(*ds):
          ...
          for s in subsets(ds, size=4, select="P"):
            ...
        

        Like

    • Frits's avatar

      Frits 3:21 pm on 16 September 2020 Permalink | Reply

      Thanks.

      I have seen the (*ds) before and have done some investigation but it is not yet in my “tool bag”.

      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