Updates from Jim Randell Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 11:29 am on 24 January 2023 Permalink | Reply
    Tags:   

    Teaser 1850: Linear town 

    From The Sunday Times, 1st March 1998 [link]

    The new transcontinental road across Eastern Europe into Asia was to outshine the Pan-American Highway, in that the old concept of a linear town had been revived. It was to have houses on each side of its entire length. Even the contract for the house numbers was huge. They started at 1 and ran to a neatly bureaucratic 1 followed by a number of noughts. One manufacturer’s representative delayed by her sick child, arrived just before the deadline to submit a bid to provide all the digits required. Unfortunately her computer had been programmed to calculate the sum of all the digits required, rather than the number of digits. This incorrect answer started with some digits that formed a number equal to the cube of her daughter’s age. Luckily, from this total she was able to work out the number of digits actually needed.

    How many digits were needed?

    [teaser1850]

     
    • Jim Randell's avatar

      Jim Randell 11:30 am on 24 January 2023 Permalink | Reply

      Let N(k) be the number of digits used when expressing the numbers [0, …, (10^k) − 1] in decimal.

      We can start with:

      N(1) = len([0, …, 9]) = 10

      And if we think about moving from N(k) to N(k + 1) then we need to add in the (k + 1) digit numbers.

      These come in 9 groups, each consisting of 10^k numbers each with (k + 1) digits.

      N(1) = 10
      N(k + 1) = N(k) + 9(k + 1)10^k

      At each stage the number of digits required for the house numbers [1, …, 10^k] is:

      N'(k) = N(k) − 1 + k + 1
      N'(k) = N(k) + k

      Similarly we can calculate S(k), the sum of the digits in the numbers [0, …, 10^(k − 1)].

      We start with:

      S(1) = sum([0, …, 9]) = 45

      And if we think about moving from N(k) to N(k + 1), then we add in the (k + 1) digit numbers.

      There are 9 groups, each of which considers of 10^k numbers, that start with digits 1 .. 9, and the non-leading digits of each group sum to S(k)

      S(k + 1) = S(k) + 45.10^k + 9.S(k) = 10.S(k) + 45.10^k

      Hence:

      S(1) = 45
      S(k + 1) = 10.S(k) + 45.10^k

      And the required sum of the digits for the houses numbered [1, …, 10^k] is:

      S'(k) = S(k) + 1

      The following Python program calculates increasing (k, N(k), S(k)) values, until we find appropriate values to solve the puzzle.

      It runs in 50ms. (Internal runtime is 109µs).

      Run: [ @replit ]

      from enigma import (irange, cb, join, printf)
      
      # generate: (k, N(k), S(k)) values
      def generate():
        k = 1
        m = 10
        N = 10
        S = 45
        while k < 10:
          yield (k, N, S)
          N += 9 * (k + 1) * m
          S *= 10
          S += 45 * m
          k += 1
          m *= 10
      
      # suppose the child is aged between 3 and 16
      cubes = set(str(cb(x)) for x in irange(3, 16))
      
      # consider increasing values
      for (k, N, S) in generate():
        # adjust for numbers [1 ... 10^k]
        N += k
        S += 1
        # look for matching prefixes
        S = str(S)
        xs = list(x for x in cubes if S.startswith(x))
        if xs:
          printf("N(10^{k}) = {N}; S(10^{k}) = {S}; prefix = {xs}", xs=join(xs, sep=", "))
          break
      

      Solution: The number of digits required is: 5,888,896.

      The houses are numbered from 1 to 1,000,000.

      The sum of the digits required is: 27,000,001.

      Which has a prefix of 27, so the child is aged 3.

      Like

  • Unknown's avatar

    Jim Randell 4:37 pm on 20 January 2023 Permalink | Reply
    Tags:   

    Teaser 3148: Quiz probabilities 

    From The Sunday Times, 22nd January 2023 [link] [link]

    Each of four contending couples in a quiz game has equal probability of elimination at the end of each of the first three rounds, one couple going after each round. In the fourth round, the remaining couple has a constant probability p, less than ½, of winning the jackpot, which consists of £1000 in the first game; if the jackpot is not won, it is added to the £1000 donated in the next game. Each couple may enter three successive games of the quiz, except that any couple having played for the jackpot in the fourth round of any game then withdraws altogether, being replaced by a new couple in the next
    game.

    If the probability that a couple, competing from the first game, wins £2000 is 7/96, what is the value of p as a fraction?

    [teaser3148]

     
    • Jim Randell's avatar

      Jim Randell 5:09 pm on 20 January 2023 Permalink | Reply

      The mechanics of this sound like the TV quiz Pointless.

      At first I couldn’t find a solution, but if I interpret “wins £2000” as “wins at least £2000” then I do find an answer (and it conforms to the general rule of probability puzzles).

      Run: [ @replit ]

      from enigma import (Rational, Polynomial, require, printf)
      
      require("enigma.version", "2023-01-23") # ensure Polynomial functionality
      
      Q = Rational()
      
      # probability of winning the jackpot in the final round (= p)
      win = Polynomial([0, 1])
      # and so the probability of not winning the jackpot (= 1 - p)
      lose = 1 - win
      
      # generate possible outcomes with k goes remaining and a jackpot of t
      # return: (<amount>, <probability>)
      def outcomes(k, t, P=1):
        if k == 0:
          # 0 goes remaining = a loss with probability 1
          yield (0, P)
        elif k > 0:
          # there is a 1/4 chance of getting through to the final, and no more goes
          P_ = P * Q(1, 4)
          # and then a chance of winning
          yield (t, win * P_)
          # otherwise you lose
          yield (0, lose * P_)
      
          # and there is a 3/4 chance of getting knocked out, and having another go
          P_ = P * Q(3, 4)
          # and a chance the jackpot is won, so it starts again from 1000
          yield from outcomes(k - 1, 1000, win * P_)
          # otherwise it rolls over
          yield from outcomes(k - 1, t + 1000, lose * P_)
      
      # consider 3 goes, with a jackpot starting at 1000
      # collect probabilities for a win of (at least) 2000
      P = Polynomial.sum(p for (t, p) in outcomes(3, 1000) if t >= 2000)
      
      # look for p values that give P(p) = 7/96
      v = Q(7, 96)
      printf("[{P} = {v}]", P=P.print('p'))
      for p in (P - v).rational_roots():
        if 0 < p < Q(1, 2):
          printf("p = {p}")
      

      Solution: p = 1/3.


      The probabilities of a particular one of the original couples winning prizes is detailed below:

      P(win £0) = (−37/64)p + 1
      P(win £1000) = (21/64)p² + (1/4)p
      P(win £2000) = (−9/64)p³ + (−3/64)p² + (3/16)p
      P(win £3000) = (9/64)p³ + (−9/32)p² + (9/64)p

      So the probability of winning exactly £2000 does not reach 7/96 in p = (0 .. 1/2).

      However if we set the probability of winning at least £2000 to 7/96 we get:

      [(−9/64)p³ + (−3/64)p² + (3/16)p] + [(9/64)p³ + (−9/32)p² + (9/64)p] = 7/96
      (−21/64)p² + (21/64)p = 7/96
      9p² − 9p + 2 = 0
      (3p − 1)(3p − 2) = 0
      p = 1/3

      Which gives the following probabilities:

      P(win £0) = 155/192 (≈ 80.7%)
      P(win £1000) = 23/192 (≈ 12.0%)
      P(win £2000) = 5/96 (≈ 5.2%)
      P(win £3000) = 1/48 (≈ 2.1%)

      So the puzzle could be solved as:

      If the probability that a couple, competing from the first game, wins at least £2000 is 7/96, what is the value of p as a fraction?

      or:

      If the probability that a couple, competing from the first game, wins exactly £2000 is 5/96, what is the value of p as a fraction?


      The general rule of probability puzzles is that the answer is either 1/2, 1/3 or 2/3 (see Puzzle #98).

      Interestingly the quadratic equation given above also has a root at p = 2/3, suggesting that if the probability of winning in the final is either 1/3 or 2/3 the chance of winning “at least £2000” is 7/96 in both cases. Presumably because although you are more likely to win the jackpot, the jackpot itself is likely to be smaller (because someone is more likely to have won in previous rounds).

      Like

      • John Crabtree's avatar

        John Crabtree 10:42 pm on 20 January 2023 Permalink | Reply

        I came to the same conclusion about the interpretation.

        Like

      • Brian Gladman's avatar

        Brian Gladman 5:27 pm on 21 January 2023 Permalink | Reply

        @Jim. John Owen has confirmed that the intended interpretation is “exactly £2000”.

        Using a program similar to your own, I have calculated the polynomial equations needed to solve the probabilities of winning £0, £1000, £2000 and £3000. Interestingly, the cubic for £2000 is solvable (with (1/3) as the answer) when the stated probability is 5/96 in place of 7/96. It is also solvable for 6/96 (1/16) with p = (2/3).

        I am pretty convinced there is a mistake somewhere.

        Like

        • SpanishJim's avatar

          SpanishJim 7:26 pm on 21 January 2023 Permalink | Reply

          I don’t think there’s a mistake, the way I read the question. There are two ways to win £2000: in the second game, if no one wins the first game. And in the third game, when the prize was won once in the previous two games. The odds of these are 6/32(P-P^2) and 9(P^2)/32. These add to give a quadratic equation which gives a neat answer.

          First time commenting, normally I am in awe of the contributors here.

          Liked by 1 person

          • Brian Gladman's avatar

            Brian Gladman 8:14 pm on 21 January 2023 Permalink | Reply

            I believe that your first probability of winning £2000 is correct but your second one should be (9/64)P^2(1-P).

            Like

            • SpanishJim's avatar

              SpanishJim 8:51 pm on 21 January 2023 Permalink | Reply

              Thanks for replying.

              This is how I got there.
              Chance of winning the third round jackpot :
              3/4*3/4*1/4*P

              Chance of the prize in the third round being £2000:
              1-(chance 1st and 2nd jackpots are lost) – (chance 1st and 2nd jackpots are won)
              =
              1 – P^2 – (1-P)^2
              =2P

              You can’t simply say P(1-P) because there are two ways it can happen.

              Like

              • Brian Gladman's avatar

                Brian Gladman 9:47 pm on 21 January 2023 Permalink | Reply

                A win followed by a loss creates a third game rollover. But a loss followed by a win doesn’t create a third game rollover.

                Like

                • SpanishJim's avatar

                  SpanishJim 11:47 am on 22 January 2023 Permalink | Reply

                  You’re right of course, that is where I went wrong. Thanks for pointing that out. Since the answer I got is so neat I wonder if the setter made the same mistake.

                  Liked by 1 person

          • Jim Randell's avatar

            Jim Randell 10:27 pm on 21 January 2023 Permalink | Reply

            @SpanishJim: Thanks for your comment, and welcome to the site.

            I agree there are 2 ways to win £2000. And I calculated the probabilities as:

            (1) knocked out in the first game, and the jackpot is not won
            (2) finalists in the second game, and win the jackpot
            P1 = (3/4)(1 − p) × (1/4)(p)

            (1) knocked out in the first game, and the jackpot is won
            (2) knocked out in the second game, and the jackpot is not won
            (3) finalists in the third game, and win the jackpot
            P2 = (3/4)(p) × (3/4)(1 − p) × (1/4)(p)

            And these probabilities sum to give a polynomial that cannot reach 7/96 (in the required range).

            However, if the single way to win £3000 is also added in, we do get a viable solution:

            (1) knocked out in the first game, and the jackpot is not won
            (2) knocked out in the second game, and the jackpot is not won
            (3) finalists in the third game, and win the jackpot
            P3 = (3/4)(1 − p) × (3/4)(1 − p) × (1/4)(p)

            These probabilities are the same as those calculated by my program (which constructs the amount won and probability for each of the 22 possible outcomes).

            As is usually the case in probability puzzles I also wrote a simulation of the situation, which supports the above results [@replit].

            But perhaps I have made a mistake in the mechanics of the game. (I understood it play out the same way that the TV quiz Pointless does).

            Like

  • Unknown's avatar

    Jim Randell 4:32 pm on 18 January 2023 Permalink | Reply
    Tags: by: Nick Jones   

    Teaser 2708: Abracadabra 

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

    In his magic cave Ali Baba discovers a string necklace comprising sixty various gold beads that he decides to melt down and turn into coins. Each bead is spherical but with a cylindrical hole symmetrically cut through it, and in each bead the hole is one centimetre long. Ali Baba melts down all these beads and makes the gold into circular coins of two centimetres diameter and half a centimetre thickness.

    How many coins does he obtain?

    [teaser2708]

     
    • Jim Randell's avatar

      Jim Randell 4:33 pm on 18 January 2023 Permalink | Reply

      If we start with a sphere of radius R, and bore a hole through it of radius r (from north pole to south pole) then we are left with a shape (known as a “napkin ring”), and we wish to determine its volume.

      First we note that the height of the napkin ring is given by the value 2h, where:

      h² = R² − r²

      Now consider a 2-dimensional slice through the napkin ring parallel to the equator of the sphere (i.e. along a line of latitude), at a height of y above (or below) the equator.

      The area of the circle belonging to the original sphere is:

      A = 𝛑 (R² − y²)

      And the area removed by the hole is (independent of y):

      a = 𝛑 r²

      And the area of the slice at height ±y is:

      S = 𝛑 (R² − y² − r²)

      or in terms of h:

      S = 𝛑 (h² − y²)

      So the area of slice depends only on h (the semi-height of the napkin ring) and y (the latitude at which the slice is taken).

      This means if we have two napkin rings with different (R, r) values, but the same h value, then the areas of cross-sections through the rings at latitudes in the interval [−h, h] will always be the same.

      We may then apply the 3-dimensional version of Cavalieri’s principle [@wikipedia] to deduce that two napkin rings with same height also have the same volume.

      And we can calculate this volume by considering a napkin ring with height 2h and a hole with radius 0. This is just a complete sphere with radius h.

      Hence the volume of any napkin ring with height 2h is:

      V = (4/3) 𝛑 h³


      So in this puzzle, it does not matter that we are only given the length of the hole through each bead. Each of the beads will have the same volume, even if they are based on spheres with different radii (the holes would also have to have different radii in order to give matching heights).

      Each of the beads has a height of 10mm, so the volume of each bead (in mm³) is:

      B = (4/3) 𝛑 (5³)
      B = (500/3) 𝛑

      And there are 60 beads, giving a total volume of gold (in mm³):

      G = 60B = 10000 𝛑

      Each coin produced has a radius of 10mm and a height of 5mm, so the volume (in mm³) of each coin is:

      C = 𝛑 (10²) 5 = 500 𝛑

      And the number of coins that can be produced (if there is no wastage) is:

      N = G / C = 20

      Hence:

      Solution: 20 coins can be made.


      See also: Napkin ring problem at Wikipedia.

      Like

  • Unknown's avatar

    Jim Randell 11:37 am on 16 January 2023 Permalink | Reply
    Tags: ,   

    Teaser 2709: Around America 

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

    The 51 states of America have two-letter abbreviations:

    AK, AL, AR, AZ, CA, CO, CT, DC, DE, FL, GA, HI, IA,
    ID
    , IL, IN, KS, KY, LA, MA, MD, ME, MI, MN, MO, MS,
    MT
    , NC, ND, NE, NH, NJ, NM, NV, NY, OH, OK, OR,
    PA
    , RI, SC, SD, TN, TX, UT, VA, VT, WA, WI, WV, WY.

    I have written as many different letters as possible around a circle such that, if you look at any adjacent pair of letters, then clockwise they are one of the above abbreviations.

    One of my letters is R.

    Starting at R, list the letters clockwise.

    In the book The Sunday Times Brain Teasers Book 2 (2020), the abbreviation NI is given instead of NJ (which I believe was how the puzzle was originally published), but this has been corrected on The Sunday Times website.

    [teaser2709]

     
    • Jim Randell's avatar

      Jim Randell 11:39 am on 16 January 2023 Permalink | Reply

      (See also: Enigma 1754).

      The USA has 50 states and 1 federal district (DC), giving us 51 abbreviations in all. Although none of them are NI, but this was probably a typo for NJ (New Jersey), as it has been corrected on The Sunday Times website. And we get the same solution if NJ or NI are used.

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

      Run: [ @replit ]

      from enigma import (group, item, Accumulator, printf)
      
      states = str.split(
        "AK AL AR AZ CA CO CT DC DE FL GA HI IA ID IL IN KS "
        "KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV "
        "NY OH OK OR PA RI SC SD TN TX UT VA VT WA WI WV WY"
      )
      
      # map letters to next letters
      g = group(states, by=item(0), f=item(1), fn=set)
      
      # generate possible circular chains of letters
      def solve(s):
        # possible next letters
        xs = g.get(s[-1])
        if not xs: return
        # does the sequence form a chain?
        if len(s) > 1 and s[0] in xs:
          yield s
        # try to extend the sequence
        for x in xs.difference(s):
          yield from solve(s + x)
      
      # find maximal chains starting with 'R'
      r = Accumulator(fn=max, collect=1)
      for s in solve('R'):
        r.accumulate_data(len(s), s)
      
      # output solution
      printf("[max length = {r.value}]")
      for s in r.data:
        printf("chain = [{s}]")
      

      Solution: The maximal length chain is: RILAKSDCTNMO (12 letters).

      Making the following 12 states: RI, IL, LA, AK, KS, SD, DC, CT, TN, NM, MO, OR.


      Starting with the earliest letter in the alphabet we can write this maximal length chain as:

      AKSDCTNMORIL

      There is another length 12 chain that does not include R:

      AKSDCTNMOHIL

      And if we wish to include V the longest chains have length 10:

      AKSDCORINV
      AKSDCOHINV

      Like

    • Jim Randell's avatar

      Jim Randell 12:18 pm on 16 January 2023 Permalink | Reply

      Here is a variation on this puzzle:

      If the state abbreviations (above) are printed on to 51 cards (one to each abbreviation), it is possible to make a circular chain of cards, so that the same letters are next to each other at the joins. (You are not allowed to reverse the letters in an abbreviation, i.e. invert a card).

      For example, here is a chain of 8 cards:

      IN:NV:VT:TN:NM:MO:OR:RI:[IN]

      How long is the longest circular chain of cards you can make?

      My solution follows…


      This Python program runs in 82ms. (Internal runtime is 29.7ms).

      Run: [ @replit ]

      from enigma import (group, item, Accumulator, join, printf)
      
      states = str.split(
        "AK AL AR AZ CA CO CT DC DE FL GA HI IA ID IL IN KS "
        "KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV "
        "NY OH OK OR PA RI SC SD TN TX UT VA VT WA WI WV WY"
      )
      
      # group states by first letter
      g = group(states, by=item(0), fn=set)
      
      # generate possible circular chains of states
      def solve(ss):
        # possible next letters
        xs = g.get(ss[-1][-1])
        if not xs: return
        # does the sequence form a chain?
        if len(ss) > 1 and ss[-1][-1] == ss[0][0]:
          yield ss
        # try to extend the sequence
        for x in xs:
          if not (x < ss[0] or x in ss):
            yield from solve(ss + [x])
      
      # find maximal chains starting with the alphabetically first state
      r = Accumulator(fn=max, collect=1)
      for s0 in states:
        for ss in solve([s0]):
          r.accumulate_data(len(ss), ss)
      
      # output solution
      printf("[max length = {r.value}]")
      for ss in r.data:
        printf("chain = [{ss}]", ss=join(ss, sep=","))
      

      Solution: The longest chain uses 21 cards.

      There are 56 possible chains of length 21. Here is one of them:

      AK:KS:SC:CO:OH:HI:IN:NM:MN:NC:CA:AR:RI:ID:DC:CT:TN:NV:VA:AL:LA:[AK]

      Like

    • Jim Olson's avatar

      Jim Olson 5:00 pm on 16 January 2023 Permalink | Reply

      Thanks for pointing out the Times ignorance of the number of states!

      Like

  • Unknown's avatar

    Jim Randell 4:30 pm on 13 January 2023 Permalink | Reply
    Tags:   

    Teaser 3147: Noteworthy 

    From The Sunday Times, 15th January 2023 [link] [link]

    Apparently in Costa Lotta a single-digit percentage of banknotes are forgeries and so I have designed a marker pen which tests whether notes are genuine. I thought it would be quite useful to the banks because, on average, for every N uses it only gives an incorrect result once (where N is some whole number).

    Unfortunately my design has been abandoned by the banks because it turns out that on average for every N occasions on which the pen indicates a forgery, only one of the notes will in fact be forged!

    What is N?

    [teaser3147]

     
    • Jim Randell's avatar

      Jim Randell 5:23 pm on 13 January 2023 Permalink | Reply

      (See also: Enigma 1636).

      If F is the single-digit percentage of forged notes then in a representative sample of X notes, then (F/100)X of them are actually forged.

      And when the pen indicates N forgeries, in fact only one of the notes is forged, then the pen is giving false positives (i.e. it is testing some genuine notes as forgeries). It is not clear if the pen gives false negatives or not. In the following I assume the pen does not give false negatives (i.e. it will always indicate a forged note is forged, but it will also sometime indicate that a genuine note is forged), but see below for a treatment that includes false negatives.

      If we suppose that when testing a quantity of notes, the pen indicates that a fraction f of them are forged.

      Then if we test N notes (of which (F/100)N are forgeries) we get 1 false positive. So:

      f.N = (F/100)N + 1

      And if X is the quantity of notes required to get N positive tests, then:

      f.X = N
      ⇒ f = N/X

      But in this case only 1 of the notes is forged, so:

      (F/100)X = 1
      ⇒ X = 100 / F
      ⇒ f = N/X = (F/100)N

      Combining the equations:

      (F/100)N² = (F/100)N + 1
      F.N² − F.N − 100 = 0

      And we know F is a value in [1..9].

      Run: [ @replit ]

      from enigma import (irange, quadratic, printf)
      
      for F in irange(1, 9):
        for N in quadratic(F, -F, -100, domain="Z"):
          if N > 0:
            printf("F={F} N={N}")
      

      Solution: N = 5.

      This is the case when there are no false negatives. (i.e. the pen always gives a positive test for a forgery, but sometimes will give a positive test for a genuine note).

      In this case: F = 5, N = 5, fp = 4/19 (≈ 21.1%), fn = 0.

      Applying the pen to a representative sample of 100 notes:

      100 notes = 95 genuine, 5 forgeries.
      The pen tests as forgeries, all 5 of the forged notes (correctly), and 20 of the genuine notes (incorrectly), giving 25 positive tests in total.
      The pen tests as genuine, none of the forgeries, and 75 of the genuine notes (correctly), giving 75 negative tests in total.
      The average error rate is 20/100 = 1/5 (as required).
      And of the positive tests 5/25 = 1/5 are actually forgeries (as required).

      For an alternative interpretation of the puzzle, see my comment below.


      I also wrote a simulation of testing 10 million notes with the pen, and it verifies the values produced give a viable solution to the puzzle. [@replit]

      Like

      • Jim Randell's avatar

        Jim Randell 12:51 pm on 14 January 2023 Permalink | Reply

        With a bit more work we can allow for false negatives as well as false positives:

        fp = fraction of genuine notes that test as forgeries
        fn = fraction of forged notes that test as genuine

        The equations become:

        “of 100 notes, 100/N test incorrectly”
        ⇒ fn.F + fp.(100 − F) = 100/N

        “testing X hundred notes gives N positive tests”
        ⇒ fp.(100 − F).X + (1 − fn).F.X = N

        “only one of the positive tests in X hundred notes is actually a forgery”
        ⇒ (1 − fn).F.X = 1

        It turns out there are many solutions that satisfy these equations.

        Setting fn = 0 gives us the quadratic equation in my original comment.

        But if we set fp = fn (= r) (i.e. the rate of false positives is the same as the rate of false negatives) then we get:

        r.F + r.(100 − F) = 100/N
        ⇒ r = 1/N

        (1 − r).F.X = 1
        ⇒ X = N / (N − 1).F

        r.(100 − F).X + 1 = N
        ⇒ F.N² − 2F.N + (2F − 100) = 0

        And this quadratic equation can be solved in a similar way to the previous one, and also gives a single solution to the puzzle:

        Run: [ @replit ]

        from enigma import (irange, quadratic, printf)
        
        for F in irange(1, 9):
          for N in quadratic(F, -2*F, 2*F - 100, domain="Z"):
            if N > 0:
              printf("F={F} N={N}")
        

        Solution: N = 8.

        This is the case the false positive rate and false negative rate are the same.

        In this case: F = 2, N = 8, fp = 1/8 (= 12.5%), fn = 1/8 (= 12.5%).

        Applying the pen to a representative sample of 100 notes (or 10,000 notes if you don’t like fractional notes):

        100 notes = 98 genuine, 2 forgeries.
        The pen tests as forgeries, 1.75 of the forged notes (correctly), and 12.25 of the genuine notes (incorrectly), giving 14 positive tests in total.
        The pen tests as genuine, 0.25 of the forged notes (incorrectly), and 85.75 of the genuine notes (correctly), giving 86 negative tests in total.
        The average error rate is (12.25 + 0.25)/100 = 1/8 (as required).
        And of the positive tests 1.75/14 = 1/8 are actually forgeries (as required).

        This scenario also tests as viable using the simulation code mentioned above.

        I’m inclined to think this might be the intended interpretation, as we need to restrict F to a single digit integer in order to get a unique solution. But it would have saved some bother if the puzzle text had just stated that the rates of false positives and false negatives were equal.


        And here is a solution where the false positive and false negative rates are similar (and smaller than in the published answer), but not equal:

        F = 1, N = 11, fp = 890/9801 (≈ 9.1%), fn = 10/99 (≈ 10.1%).

        Applying the pen to a representative sample of 100 notes:

        100 notes = 99 genuine, 1 forgery.
        The pen tests as forgeries, 0.90 of the forged note (correctly), and 8.99 of the genuine notes (incorrectly), giving 9.89 positive tests in total.
        The pen tests as genuine, 0.10 of the forged note (incorrectly), and 89 of the genuine notes (correctly), giving 89.10 negative tests in total.
        The average error rate is (8.99 + 0.10)/100 = 1/11 (as required)
        And of the positive tests 0.90/9.89 = 1/11 are actually forgeries (as required)

        Like

        • Jim Randell's avatar

          Jim Randell 12:33 pm on 22 January 2023 Permalink | Reply

          The published answer was: N = 8.

          Which confirms my suspicions that the false positive and false negative rates are intended to be the same.

          Like

    • Tony Smith's avatar

      Tony Smith 8:11 pm on 17 January 2023 Permalink | Reply

      Nothing in the wording suggests that the rates might be different.

      Like

      • Jim Randell's avatar

        Jim Randell 10:40 am on 18 January 2023 Permalink | Reply

        I think you need to assume that the false positive and false negative rates are the same in order to arrive at the intended solution. Something that could have easily been mentioned in the puzzle text.

        Like

    • Tony Smith's avatar

      Tony Smith 11:45 am on 18 January 2023 Permalink | Reply

      “on average, for every N uses it only gives an incorrect result once”
      I think that is perfectly clear and unambiguous.

      I also think that is immediately obvious that if the rates were different it would not be possible to deduce an answer without more information about the two rates.

      The puzzle is actually solvable by the application of Bayes’s theorem with all relevant probabilities expressible in terms of N and the error rate.

      Like

  • Unknown's avatar

    Jim Randell 9:12 am on 10 January 2023 Permalink | Reply
    Tags:   

    Teaser 2687: Creased 

    From The Sunday Times, 23rd March 2014 [link] [link]

    I took a rectangular piece of paper whose sides were whole numbers of centimetres — indeed the longer side was exactly three times the length of the shorter side. I folded the rectangle along a diagonal. I then took the folded piece and folded it again along its line of symmetry. Then I completely unfolded it to see that the two creases had divided the rectangle into two equal triangles and two equal quadrilaterals. In square centimetres the area of each triangle was a three-figure number, and the area of each quadrilateral was another three- figure number using the same three digits but in a different order.

    What was the area of each triangle?

    [teaser2687]

     
    • Jim Randell's avatar

      Jim Randell 9:14 am on 10 January 2023 Permalink | Reply

      If the dimensions of the original rectangle are x by 3x. Then it has an area of 3x².

      The result of the folds looks like this:

      X is in the centre of the rectangle (so 3x/2 from the left/right edges and x/2 from the top/bottom edges).

      And Y is vertically below X, so we have:

      OY = 3x/2
      XY = x/2
      OX = x√(5/2)

      The triangles OXY and XZY are similar. With the sides of OXY being 3× those of XZY.

      Hence:

      XZ = (x/3)√(5/2)

      And the area of triangle OXZ is given by:

      T = (1/2)(XZ)(OX)
      T = (1/2)(x/3)x(5/2)
      T = (5/12)x²

      And the area of the quadrilateral ABXZ is:

      Q = (3/2)x² − T
      Q = (13/12)x²

      The following Python program runs in 51ms. (Internal runtime is 99µs).

      Run: [ @replit ]

      from enigma import (irange, inf, nsplit, printf)
      
      # digits of a number, in order
      digits = lambda n: sorted(nsplit(n))
      
      # consider x values
      for x in irange(1, inf):
        x2 = x * x
        (T, t) = divmod(x2 * 5, 12)
        (Q, q) = divmod(x2 * 13, 12)
        # T and Q are 3 digit numbers, with no remainder
        if Q > 999: break
        if t > 0 or q > 0 or T < 100: continue
        # do T and Q use the same digits?
        if digits(T) == digits(Q):
          printf("x={x}: T={T} Q={Q}")
      

      Solution: The area of each triangle is: 135 cm².

      And the area of the quadrilaterals is: 351 cm².

      The original rectangle had dimensions: 18 cm × 54 cm. (Area = 972 = 2 × (135 + 351)).

      Like

    • GeoffR's avatar

      GeoffR 1:22 pm on 10 January 2023 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % using Jim's analysis and notations
      var 1..50:x; var 1..50:OX; var 1..50:OY;
      var 1..50:XZ; var 100..250:T; var 100..500:Q;
      
      constraint 2 * OX == 3 * x /\ 2 * OY == x;
      constraint 12 * T == 5 * x * x;
      constraint 12 * Q == 13 * x * x;
      
      % Digits in triangular area (a, b, c)
      var 1..9:a; var 0..9:b; var 1..9:c;
      constraint a == T div 100 /\ b == T div 10 mod 10 /\ c == T mod 10;
      
      % Digits in quadrilateral area (d, e, f)
      var 1..9:d; var 0..9:e; var 0..9:f;
      constraint d == Q div 100 /\ e == Q div 10 mod 10 /\ f == Q mod 10;
      
      % Digits in triangular area = Digits in quadrilateral area
      constraint {a, b, c} == {d, e, f};
      
      solve satisfy;
      
      output ["Triangle area = " ++ show(T) ++ " sq cm"
      ++ "\n" ++ "Quadrilateral area = " ++ show(Q) ++ " sq cm"];
      
      % Triangle area = 135 sq cm
      % Quadrilateral area = 351 sq cm
      % ----------
      % ==========
      
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 9:40 am on 11 January 2023 Permalink | Reply

        @Geoff: I think the digit constraint at line 21 needs to be a bit stronger. Two numbers that had repeated digits could pass the test without being reorderings of the same collection of digits (e.g. 141 and 414).

        Like

        • GeoffR's avatar

          GeoffR 10:03 am on 11 January 2023 Permalink | Reply

          @Jim:
          Maybe like this?

          % Digits in triangular area = Digits in quadrilateral area
          constraint {a, b, c} == {d, e, f} /\ a != d /\ b != e /\ c != f
          /\ all_different([a, b, c]);
          

          Like

        • Jim Randell's avatar

          Jim Randell 11:22 am on 11 January 2023 Permalink | Reply

          I think something like this would be more appropriate:

          constraint sort([a, b, c]) = sort([d, e, f]);
          

          Like

    • Hugh Casement's avatar

      Hugh Casement 7:46 am on 11 January 2023 Permalink | Reply

      As triangles OXY and XZY are similar, YZ = x/6, which gives us another way to calculate T
      as half base OZ times height XY, without any square roots.

      I notice that whenever two integers in the ratio 5:13 have the same digit sum,
      that is 9 or a multiple of 9; e.g. 45:117, 90:234, 270:702.
      The last of those would be a further solution, wouldn’t it?

      Like

  • Unknown's avatar

    Jim Randell 4:55 pm on 8 January 2023 Permalink | Reply
    Tags: ,   

    Teaser 3146: Curling league [revised] 

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

    The number of teams in our curling league is more than 4, but less than 26. In a tournament each team plays each of the other teams once, and the teams are ranked according to the number of wins they achieve (draws are impossible). If any teams are tied on wins, ranking is only possible if those teams have different numbers of wins in their mutual games. For example, in a three-way tie if A beats B, B beats C and A beats C, the ranking is ABC, but if C beats A (or A has not yet played C), then ranking is impossible, as A and B have one win each.

    At one point (when each team had played G games), ranking the teams as above was possible. However, if each team had played G−1 games, a ranking would not have been possible, irrespective of results. With one more team in the league, the minimum number of games needed to allow a ranking would be G+2.

    How many teams are in the league and what was the value of G?

    This is a revised version of the puzzle posted as Teaser 3146. The underlined text has been changed from the published version, to be in line with the intention of the setter.

    [teaser3146]

     
    • Jim Randell's avatar

      Jim Randell 9:41 am on 9 January 2023 Permalink | Reply

      I solved the previous version of this puzzle with a fairly straightforward search [link]. And it came up with a solution quickly, without having to explore the entire solution space. Which was handy because that would have taken a long time.

      So I looked at making my program more efficient. By looking at the number of wins achieved by each team we find there are only certain patterns of wins that are viable. Limiting the search to these patterns of wins, and doing more checks to reject candidate solutions early I was able to come up with a (more complicated) program that explores the entire solution space in a reasonable time, and constructively finds a single solution to the puzzle.

      (This program can also be used to find both solutions to the puzzle as originally worded).

      The following Python program runs in 7.6s.

      Run: [ @replit ]

      from enigma import (
        irange, group, item, div, subsets, multiset, rev,
        diff, filter2, seq_all_different, cache, printf
      )
      
      # labels for <n> teams; 1..n
      teams = lambda n: irange(1, n)
      
      # a match is represented by (<winning-team>, <losing-team>)
      
      # check a set of matches for <n> teams is orderable for teams <ts>
      def orderable(ms, ts):
        # extract the winners
        (ws, _) = zip(*ms)
        # and count the number of wins for each team
        w = dict((t, ws.count(t)) for t in ts)
        # group teams with the same number of wins together
        g = group(w.items(), by=item(1), f=item(0), fn=sorted)
        # look for tied groups
        for (_, xs) in g.items():
          if len(xs) < 2: continue
          # collect the mutual games amongst the group
          ws_ = list(x for (x, y) in ms if x in xs and y in xs)
          # and check they are all different
          if not seq_all_different(ws_.count(t) for t in xs): return False
        # looks good
        return True
      
      # allocate played matches for <n> teams, <k> matches per team, <p> matches in total,
      # from unallocated matches <ps>
      def allocate(n, k, p, ps, ws, t=1, ss=[]):
        # are we done?
        if t > n:
          yield ss
        else:
          # allocated games involving team <t>
          xs = list(x for x in ss if t in x)
          r = k - len(xs)
          if r < 0: return
          nw = ws[0]
          # number of allocated wins
          xw = sum(x == t for (x, y) in xs)
          if xw > nw: return
          # consider unallocated games involving team <t>
          (ts, ps_) = filter2((lambda x: t in x), ps)
          # select the remaining matches for team <t>
          for rs in subsets(ts, size=r):
            for js in subsets(irange(r), size=nw - xw, fn=set):
              # construct the new set of matches
              ss_ = ss + list(((t, x + y - t) if j in js else (x + y - t, t)) for (j, (x, y)) in enumerate(rs))
              # check wins/losses are still viable for the remaining teams
              xws = multiset.from_seq(x[0] for x in ss_)
              xls = multiset.from_seq(x[1] for x in ss_)
              if any(xws.count(j) > w or xls.count(j) > k - w for (j, w) in zip(irange(t, n), ws)): continue
              # and check it is orderable for teams up to t
              if orderable(ss_, teams(t)):
                yield from allocate(n, k, p - r, ps_, ws[1:], t + 1, ss_)
      
      # decompose total played matches <p> into wins for <n> teams, each played <k> matches
      def decompose(p, n, k, w=0, ws=[]):
        if len(ws) == n:
          if p == 0:
            yield rev(ws)
        elif not (w > k):
          # choose number of teams with <w> wins
          for m in irange(min(w, k - w) + 1, 0, step=-1):
            if len(ws) + m > n or m * w > p: continue
            yield from decompose(p - m * w, n, k, w + 1, ws + [w] * m)
      
      # find orderable sets of wins for <n> teams, each having played <k> matches
      def wins(n, k):
        # calculate the number of played matches
        p = div(n * k, 2)
        if p is None: return
        # construct the possible matches
        ps = list(subsets(teams(n), size=2))
        # construct possible wins
        for ws in decompose(p, n, k):
          # we can allocate matches between teams with the same number of wins
          g = group(enumerate(ws, start=1), by=item(1), f=item(0), fn=sorted)
          ss = list()
          for (_, vs) in g.items():
            if len(vs) > 1:
              ss.extend(subsets(vs, size=2))
          if len(ss) > p: continue
          # allocate the remaining matches
          yield from allocate(n, k, p - len(ss), diff(ps, ss), ws, 1, ss)
      
      # check if <n> teams, each played <k> games is orderable
      @cache
      def check(n, k):
        if not (n > k > 0): return None
        for r in wins(n, k):
          printf("[n={n} k={k} -> {r}]", r=sorted(r))
          return True
        printf("[n={n} k={k} -> None]")
        return False
      
      # solve the puzzle
      def solve(start, stop):
        # n = number of teams in the league
        for n in irange(start + 1, stop + 1):
          # k = number of matches played by each team
          # look for minimum k that is orderable
          for k in irange(1, n - 1):
            # can we find an orderable set of wins?
            r = check(n, k)
            # find the smallest k that gives an orderable set
            if r:
              printf("-> n={n}: min_k = {k}")
              (T, G) = (n - 1, k - 2)
              # look to see if (T, G) is possible and (T, G - 1) is not possible
              if check(T, G) is True and check(T, G - 1) is False:
                # output solution
                printf("=> T = {T}; G = {G}")
                printf("##### SOLUTION FOUND -> league has {T} teams; games played = {G} #####")
                yield (T, G)
              break
          printf()
      
      # output solution summary
      for (T, G) in list(solve(5, 25)):
        printf("=> SOLUTION: league has {T} teams; games played = {G}")
      

      Solution: There are 16 teams in the league. G = 6.

      For example, with teams A – P, each can play 6 games, and be orderable as follows:

      1st = A [6 wins; beat: B D E G H I]
      2nd = B [5 wins; beat: C D G H K]
      3rd = C [5 wins; beat: G K L M N]
      4th = D [4 wins; beat: E F K L]
      5th = E [4 wins; beat: F K L N]
      6th = F [4 wins; beat: M N O P]
      7th = G [3 wins; beat: H I J]
      8th = H [3 wins; beat: I J N]
      9th = I [3 wins; beat: J O P]
      10th = J [3 wins; beat: N O P]
      11th = K [2 wins; beat: L M]
      12th = L [2 wins; beat: M P]
      13th = M [2 wins; beat: O P]
      14th = N [1 win; beat: O]
      15th = O [1 win; beat: P]
      16th = P [0 wins]

      And it is not possible for 16 teams to have each played 5 games and be orderable.

      >>> from enigma import first
      >>> first(wins(16, 5))
      []
      

      But it is possible for 17 teams, A – Q, to have each played 8 games and be orderable:

      1st = A [8 wins; beat: B D E G H I J K]
      2nd = B [7 wins; beat: C D G H I J K]
      3rd = C [7 wins; beat: G I J K L M N]
      4th = D [6 wins; beat: E F I J K L]
      5th = E [6 wins; beat: F J L M N O]
      6th = F [6 wins; beat: L M N O P Q]
      7th = G [5 wins; beat: H L M O P]
      8th = H [5 wins; beat: L M O P Q]
      9th = I [4 wins; beat: N O P Q]
      10th = J [3 wins; beat: K O Q]
      11th = K [3 wins; beat: O P Q]
      12th = L [2 wins; beat: M N]
      13th = M [2 wins; beat: N Q]
      14th = N [2 wins; beat: P Q]
      15th = O [1 win; beat: P]
      16th = P [1 win; beat: Q]
      17th = Q [0 wins]

      but not for any number of games less than 8.


      Here are some notes on the refinements I used to eliminate cases from testing compared with my original program, that allows it to run in a reasonable time over the entire solution space…

      Firstly we don’t care what order the teams are in, so we can assume team 1 is 1st in the order, team 2 is 2nd, and so on.

      For a league with n teams, each having played k matches there are p = (n × k /2) matches played in total (each match involves 2 teams), so we can allocate these p wins among the n teams in a non-decreasing sequence of [0..k] wins. With team 1 having the largest number of wins, down to team n having the least wins.

      Then looking at groups of teams with the same number of wins, these groups must be ordered according to the matches played amongst the teams involved. If team X has 0 wins there cannot be another team Y with 0 wins, as we can only order them with an X vs. Y match, which neither team can be allowed to win. So there can be at most 1 team with 0 wins, at most 2 teams with 1 wins, etc.

      And by considering losses we get the same pattern coming down. There can be at most 1 team with k wins, at most 2 teams with (k – 1) wins, and so on.

      So, I generated viable patterns of wins and then allocated matches according to these patterns by working through the list of wins and allocating appropriate matches for each team.

      When allocating the remaining matches for each team I check after each allocation that we haven’t allocated too many wins (or losses) to one of the later occurring teams, and also after we have allocated the matches for a team the matches for that team and all earlier teams are fixed, so the set of matches restricted to just these teams must already be orderable (as they are not going to change later).

      Also, it soon became obvious that using [[ enigma.decompose() ]] to generate all possible decompositions and discarding those that were unsuitable was not very efficient as n increased, so I wrote a specific [[ decompose() ]] routine to do this for me.

      Then I realised that if we have groups of teams with the same number of wins, and we know the order that we want them to appear in, then we can allocate the wins involved between those teams immediately, before we start the general allocation of the remaining matches.

      Together all these improvements let the program run in 10s or less (running under PyPy 7.3.11). This was hugely faster than my initial program for the original puzzle, and let the program examine the entire solution space, and construct orderings where they were possible.

      Like

      • Frits's avatar

        Frits 4:41 pm on 9 January 2023 Permalink | Reply

        Using a different solve() is a bit faster. Basically we start with making a list of orderable set of wins (first item only) by calling decompose().

        Only changes made to the code is to return a tuple in decompose() and to suppress printing in the check routine.

        The “not found” branche is not further developed as it seems that all decompose() output is orderable.

           
        def solve(start, stop):
          F = dict()
          # n = number of teams in the league
          for n in range(start, stop):
            # k = number of matches played by each team
            for k in range(1, n):  
              if n % 2 and k % 2: continue
              # get first orderable set of wins
              lst = list(first(decompose(n * k // 2, n, k)))
              if lst: 
                print((n, k), "-->", lst[0])
                if not check(n, k, p=0):
                  print("not found")
                else:
                  F[n] = k
                break
        
          # check dictionary of first orderable set of wins
          for n, k in F.items():
            if n - 1 in F:
              if F[n - 1] == k - 2:
                printf("=> SOLUTION: league has {n - 1} teams; games played = {k - 2}")
              elif k > 3 and F[n - 1] < k - 2:
                if check(n - 1, k - 2, p=0) and not check(n - 1, k - 3, p=0):
                  printf("=> SOLUTION: league has {n - 1} teams; games played = {k - 2}")
        

        Like

        • Jim Randell's avatar

          Jim Randell 9:46 pm on 9 January 2023 Permalink | Reply

          @Frits: I think most of the speed increase you are seeing comes from not checking the full range for n. If the number of teams can be up to 25 then we need to check up to n = 26. Otherwise it seems to be doing pretty much the same thing as my program, but finding solutions at the end rather than as it goes along.

          The values generated by [[ decompose() ]] all give an orderable set of matches when the program is run normally, but this is not the case in general. While playing around I found [[ check(10, 8) ]] (for example) needed to look at multiple decompositions before an orderable set was found.

          Like

          • Frits's avatar

            Frits 11:21 pm on 9 January 2023 Permalink | Reply

            @Jim, you are right, with range(start, stop + 2) the performance gain is minimal.

            A bit surprising, your program calls check() 135 times and mine only 23 times (but these are all time consuming).

            Like

          • Jim Randell's avatar

            Jim Randell 11:28 pm on 9 January 2023 Permalink | Reply

            The [[ check() ]] function is cached (via the [[ @cache ]] decorator), so we get access to previously computed values without recalculating them.

            Like

            • Frits's avatar

              Frits 11:34 pm on 9 January 2023 Permalink | Reply

              Removing @cache most of the time slightly improves the run time!

              Like

    • Jim Randell's avatar

      Jim Randell 9:36 am on 27 January 2023 Permalink | Reply

      Here is a non-constructive solution, that uses the pattern of wins given in my notes above.

      Thinking about leagues of n teams where each team has played k matches, then if we start with a value for k we see the smallest possible value that n can be is (k + 1).

      And when we think about allocating the p = (n.k / 2) matches among the n teams, then there can only be 1 team with 0 wins or k wins, 2 teams with 1 win or (k − 1) wins, etc.

      So, if k is even (say 2x) we get a pattern of the form:

      1, 2, …, (x + 1), …, 2, 1
      max(n) = 2T(x) + (x + 1) = (x + 1)²

      And if k is odd (say 2x + 1) we get a pattern of the form:

      1, 2, …, (x + 1), (x + 1), …, 2, 1
      max(n) = 2T(x + 1) = (x + 1)(x + 2)

      So by considering increasing values of k we can find a range of potentially viable n values and build up a set of viable (n, k) values. Note that no (n, k) value where both n and k are odd is viable, as a corresponding p value cannot be calculated, but we assume that all remaining (n, k) value will allow a viable ordering to be constructed. (In my program above this assumption is not made, and we don’t accept an (n, k) until an orderable scenario has been constructed. Removing this requirement significantly reduces the amount of work required to find a solution, and is the approach used in the setters’ own solution).

      Once an appropriate set of (n, k) values has been constructed we can look for scenarios that provide a solution to the puzzle.

      We are looking for a number of teams T and number of games G such that:

      (T, G) is viable
      (T, G − 1) is not viable
      (T + 1, G + 2) is viable
      (G + 2) is the smallest viable number of games for a league with (T + 1) teams

      The Python program performs this search. It runs in 57ms. (Internal runtime is 534µs).

      Run: [ @replit ]

      from enigma import (irange, divc, tri, div, peek, printf)
      
      # for a given k value generate possible range of n values
      def nrange(k):
        lo = k + 1
        # count maximum possible wins = number of matches
        h = divc(k, 2)
        t = k * tri(h)
        if k % 2 == 0: t += h * (h + 1)
        hi = div(2 * t, k)
        return (lo, hi)
      
      def solve(start, stop):
        ss = list()
        # possible number of teams
        t = 1
        # record possible (n, k) values
        R = set()
        # consider increasing k values
        for k in irange(1, stop + 1):
          (lo, hi) = nrange(k)
          # fill out possible (n, k) values in R
          for n in irange(lo, hi):
            if k % 2 == 0 or n % 2 == 0:
              R.add((n, k))
          # check possible teams
          while t + 1 < lo:
            t += 1
            # find min_k for t
            min_k = peek(k for k in irange(1, t - 1) if (t, k) in R)
            printf("t={t} -> min_k={min_k}")
            # look for solutions
            (T, G) = (t - 1, min_k - 2)
            if T < start or T > stop: continue
            if (T, G) in R and (T, G - 1) not in R:
              printf("=> T = {T}; G = {G}")
              printf("##### SOLUTION FOUND -> league has {T} teams; games played = {G}")
              ss.append((T, G))
        # output solution summary
        if ss:
          printf()
          for (T, G) in ss:
            printf("=> SOLUTION: league has {T} teams; games played = {G}")
      
      # solve the puzzle
      solve(5, 25)
      

      Like

  • Unknown's avatar

    Jim Randell 4:29 pm on 6 January 2023 Permalink | Reply
    Tags: ,   

    Teaser 3146: Curling league 

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

    In our curling league (between 4 and 26 teams), each team plays each other once. Teams are ranked according to the number of wins (draws are impossible). If any teams are tied on wins, ranking is only possible if those teams have different numbers of wins in their mutual games. For example, in a three-way tie if A beats B, B beats C and A beats C, the ranking is ABC, but if C beats A (or A has not yet played C), then ranking is impossible, as A and B have one win each.

    At one point (each team had played G games), ranking the teams as above was possible. However, if each team had played G-1 games, a ranking would have been impossible, irrespective of results. With one more team in the league, the minimum number of games needed to allow a ranking is G+2.

    How many teams are in the league and what was the value of G?

    Note: The setter of the puzzle is expecting a solution where the league has at least 5 and no more than 25 teams. (Which I would have phrased as “between 5 and 25 teams”).

    See Teaser 3146: Curling league [revised] for a revised version of this puzzle.

    [teaser3146]

     
    • Jim Randell's avatar

      Jim Randell 5:42 pm on 6 January 2023 Permalink | Reply

      I wrote a quick program to start attacking this puzzle starting with the smallest number of teams and working upwards, although I didn’t expect my program to run in a reasonable time as the number of teams grew.

      However I found that a situation that satisfies the conditions described in the puzzle presented itself very quickly, which makes me wonder if I have missed something.

      The following Python program runs in 57ms (it stops when the first solution is found). (Internal run time is 4.3ms).

      Run: [ @replit ]

      from enigma import (
        irange, group, item, div, subsets, multiset,
        cproduct, seq_all_different, cache, printf
      )
      
      # labels for <n> teams; 1..n
      teams = lambda n: irange(1, n)
      
      # a match is represented by (<winning-team>, <losing-team>)
      
      # check a set of matches for <n> teams is orderable
      def orderable(n, ms):
        # extract the winners
        (ws, _) = zip(*ms)
        # and count the number of wins for each team
        w = dict((t, ws.count(t)) for t in teams(n))
        # group teams with the same number of wins together
        g = group(w.items(), by=item(1), f=item(0), fn=sorted)
        # look for tied groups
        for (_, ts) in g.items():
          if len(ts) < 2: continue
          # collect the mutual games amongst the group
          ws_ = list(x for (x, y) in ms if x in ts and y in ts)
          # and check they are all different
          if not seq_all_different(ws_.count(t) for t in ts): return False
        # looks good
        return True
      
      # choose <p>-subsets of potential matches <ps>, with <k> matches played per team
      def choose(n, ps, p, k):
        for ss in subsets(ps, size=p):
          m = multiset.from_seq(*ss)
          if all(m.count(t) == k for t in teams(n)):
            yield ss
      
      # find orderable sets of wins for <n> teams, each having played <k> matches
      def wins(n, k):
        # calculate the number of played matches
        p = div(n * k, 2)
        if p is None: return
        # construct the possible matches
        ps = list(subsets(teams(n), size=2))
        # choose a p-subset with k matches played per team
        for ss in choose(n, ps, p, k):
          # now choose the winning team in each match
          for ms in cproduct([(x, y), (y, x)] for (x, y) in ss):
            # is this set of matches orderable?
            if orderable(n, ms):
              yield ms
      
      # check if <n> teams, each played <k> games is orderable
      @cache
      def check(n, k):
        if not (n > k > 0): return None
        for r in wins(n, k):
          printf("[n={n} k={k} -> {r}]")
          return True
        printf("[n={n} k={k} -> None]")
        return False
      
      # solve the puzzle
      def solve():
        # n = number of teams in the league
        for n in irange(5, 27):
          # k = number of matches played by each team
          # look for minimum k that is orderable
          for k in irange(1, n - 1):
            # can we find an orderable set of wins?
            r = check(n, k)
            # find the smallest k that gives an orderable set
            if r:
              printf("-> n={n}: min_k = {k}")
              (T, G) = (n - 1, k - 2)
              # look to see if (T, G) is possible and (T, G - 1) is not possible
              if check(T, G) is True and check(T, G - 1) is False:
                # output solution
                printf("=> T = {T}; G = {G}")
                printf("##### SOLUTION FOUND -> league has {T} teams; games played = {G} #####")
                # we are done
                return
              break
          printf()
      
      solve()
      

      There are two solution to this puzzle:

      Solution: There are either 4 teams in the league, and G = 2. Or there are 16 teams in the league and G = 6.

      My program quickly finds the first of these solutions, but would take a long time to find the second one (and to exhaustively check the entire solution space).

      But the program I wrote for the revised puzzle [link] does find both solutions in a reasonable time.


      Here is a description of the solution with 4 teams:

      It is possible for each of the 4 teams (A, B, C, D) to have played 2 games. For example:

      A beat B
      A beat C
      B beat D
      D beat C

      And we see the teams are ranked:

      1st: A (2 wins)
      2nd: B (1 win, but B beat D)
      3rd: D (1 win, but D lost to B)
      4th: C (0 wins)

      It is not possible to rank the teams if they have only played one game. As there will only have been 2 games played, involving all 4 teams. 2 of the teams will have 1 win (and cannot be ranked, as they have not played each other), and the other will have 0 wins (likewise).

      If there were 5 teams in the league (A, B, C, D, E) then it is not possible for each of the teams to have played an odd number of games (as each game requires 2 teams), but it is possible for each team to play 4 games, for example:

      A beat B
      A beat C
      A beat D
      A beat E
      B beat C
      B beat D
      B beat E
      C beat D
      C beat E
      D beat E

      The teams are ranked:

      1st: A (4 wins)
      2nd: B (3 wins)
      3rd: C (2 wins)
      4th: D (1 win)
      5th: E (0 wins)

      All that remains is to show that it is not possible to find a set of matches with 2 wins per team, and the program can do this for us:

      >>> from enigma import first
      >>> first(wins(5, 2))
      []
      

      Like

      • Frits's avatar

        Frits 12:35 pm on 7 January 2023 Permalink | Reply

        @Jim, nice solution. It is not easy to solve this week.

        How can you be sure this is the only solution? So you might give an incorrect answer (for “our curling league”).

        One thing I can deduce is that if k = n-1 then we can always find an orderable set of wins.

        Like

        • Jim Randell's avatar

          Jim Randell 12:44 pm on 7 January 2023 Permalink | Reply

          My program gets bogged down as n increases, so it just stops at the first viable solution, and I haven’t been able to check all values up to n = 26.

          There may be an analytical way to show that there is only a single solution. Although if there are multiple solutions we won’t be able to tell which of them the setter had in mind.

          Like

        • Jim Randell's avatar

          Jim Randell 11:32 am on 9 January 2023 Permalink | Reply

          I’ve put a more efficient (but more complex) version of my program up on the page for the revised version of this puzzle [link].

          And we see that the original formulation of the puzzle has 2 solutions.

          Like

      • Jim Randell's avatar

        Jim Randell 10:26 am on 8 January 2023 Permalink | Reply

        Apparently the setter of this puzzle is expecting a solution for this puzzle with the number of teams in the league in the interval [5..25]. If this is the case, I think the wording should have been chosen more carefully.

        Like

    • Brian Gladman's avatar

      Brian Gladman 2:48 pm on 7 January 2023 Permalink | Reply

      Congratulations on getting a solution for this one Jim. At the moment I don’t have one 😦

      Like

    • Brian Gladman's avatar

      Brian Gladman 10:50 am on 8 January 2023 Permalink | Reply

      John Owen has just clarified on the Sunday Times discussion group site that “between 4 and 26” means “5 or more and at most 25”.

      Like

      • Jim Randell's avatar

        Jim Randell 2:49 pm on 8 January 2023 Permalink | Reply

        @Brian: This change certainly makes things trickier, as my program gets bogged down before it can check leagues with 10 or more teams.

        Although I would have expected this condition to be expressed as “between 5 and 25 teams”.

        Like

      • Frits's avatar

        Frits 4:14 pm on 8 January 2023 Permalink | Reply

        Nice to encounter a teaser which doesn’t seem to get solved (within a reasonable time) by brute force. My adapted version (with recursion) of Jim’s program is getting too slow at n=10.

        It isn’t easy to find a way to simplify the problem so it can be solved.

        Like

  • Unknown's avatar

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

    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 4:34 pm on 30 December 2022 Permalink | Reply
    Tags:   

    Teaser 3145: Easier to ask the audience 

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

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

    Martha proceeded to dial the number correctly.

    What number did she dial?

    Happy New Year from S2T2!

    [teaser3145]

     
    • Jim Randell's avatar

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

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

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

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

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

      Run: [ @replit ]

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

      Solution: The number is: 173056.

      173056 = 416².

      Like

      • Jim Randell's avatar

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

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

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

        This Python program just checks these numbers.

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

        Run: [ @replit ]

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

        Like

        • Frits's avatar

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

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

          Like

        • Tony Smith's avatar

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

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

          Like

          • Jim Randell's avatar

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

            Neat. So we only need to check 9 cases:

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

            Like

            • Frits's avatar

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

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

              Like

              • Jim Randell's avatar

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

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

                Like

      • Jim Randell's avatar

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

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

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

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

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

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

        Run: [ @replit ]

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

        Liked by 1 person

    • GeoffR's avatar

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

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

      Like

    • GeoffR's avatar

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

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

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

      Like

    • GeoffR's avatar

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

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

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

      Like

  • Unknown's avatar

    Jim Randell 9:27 am on 29 December 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 826: A matter of numeracy 

    From The Sunday Times, 25th May 1977 [link]

    As a contribution to the debate on numeracy, I asked some of my pupils to select a three-digit number between them. Five of them made two statements about the number each.

    Andy:
    (1) It is the difference between two squares.
    (2) It is not the sum of two cubes.

    Bill:
    (1) It is an even number.
    (2) It does not have exactly two prime factors.
    (i.e. it cannot be expressed as a×b where a and b are prime).

    Carol:
    (1) It is the sum of two squares.
    (2) It is both a square and a cube.

    David:
    (1) It has exactly three prime factors.
    (2) It is a prime number.

    Eric:
    (1) It is the sum of a square and a cube.
    (2) It is the sum of three squares.

    Frank, the only one who is completely reliable, then added that each of the five had made just one true statement.

    What was the number?

    This puzzle is included in the book The Sunday Times Book of Brain-Teasers: Book 2 (1981) under the title “The Great Debate”.

    [teaser826]

     
    • Jim Randell's avatar

      Jim Randell 9:28 am on 29 December 2022 Permalink | Reply

      There is already a [[ sum_of_squares() ]] function in the enigma.py, but I wrote some additional helper functions for this puzzle.

      The following Python program runs in 59ms. (Internal run time is 6.4ms).

      from enigma import (
        irange, inf, prime_factor, multiset, sum_of_squares,
        divisors_pairs, iroot, is_cube, is_square, printf
      )
      
      # some helper functions:
      
      # generate (a, b) values such that n = a**2 - b**2
      def diff_of_squares(n):
        for (p, q) in divisors_pairs(n):
          if p % 2 == q % 2:
            yield ((q + p) // 2, (q - p) // 2)
      
      # generate (a, b) values such that n = a**3 + b**3
      def sum_of_cubes(n):
        for a in irange(0, inf):
          a3 = a**3
          x = n - a3
          if x < a3: break
          b = is_cube(n - a3)
          if b is not None:
            yield (a, b)
      
      # generate (a, b) values such that n = a**2 + b**3
      def sum_of_sq_cb(n):
        for b in irange(0, iroot(n, 3)):
          a = is_square(n - b**3)
          if a is not None:
            yield (a, b)
      
      
      # consider 3-digit numbers
      for n in irange(100, 999):
        # evaluate the statements, just 1 from each pair is true
      
        # B1: "it is an even number"
        B1 = (n % 2 == 0)
      
        # prime factors
        fs = multiset.from_pairs(prime_factor(n))
      
        # B2: "it cannot be expressed as the product of 2 primes
        B2 = (fs.size() != 2)
        if B1 == B2: continue
      
        # D1: "it has exactly 3 prime factors
        D1 = (fs.size() == 3)
      
        # D2: "it is a prime number"
        D2 = (fs.size() == 1)
        if D1 == D2: continue
      
        # C2: "it is both a square an a cube"
        # = all prime factors appear a multiple of 2 and a multiple of 3 times
        C2 = all(v % 6 == 0 for v in fs.values())
      
        # C1: "it is the sum of 2 squares"
        C1 = any(sum_of_squares(n, 2))
        if C1 == C2: continue
      
        # E2: "it is the sum of 3 squares"
        E2 = any(sum_of_squares(n, 3))
      
        # E1: "it is the sum of a square and a cube"
        E1 = any(sum_of_sq_cb(n))
        if E1 == E2: continue
      
        # A1: "it is the difference between 2 squares"
        A1 = any(diff_of_squares(n))
      
        # A2: "it is not the sum of 2 cubes"
        A2 = (not any(sum_of_cubes(n)))
        if A1 == A2: continue
      
        # output solution
        printf("n={n} [A={A1}+{A2} B={B1}+{B2} C={C1}+{C2} D={D1}+{D2} E={E1}+{E2}]")
      

      Solution: The number was: 637.

      And 637 = 7×7×13.

      A1: True; 637 = 319² − 318² = 49² − 42² = 31² − 18²
      A2: False; 637 = 5³ + 8³

      B1: False; 637 is odd
      B2: True; 637 is the product of 3 prime factors

      C1: True; 637 = 14² + 21²
      C2: False; 637 is not a square, nor a cube

      D1: True; 637 is the product of 3 prime factors
      D2: False; 637 is not prime

      E1: False; 637 is not the sum of a square and a cube
      E2: True; 637 = 0² + 14² + 21² = 3² + 12² + 22² = 5² + 6² + 24² = 12² + 13² + 18²

      Like

  • Unknown's avatar

    Jim Randell 9:29 am on 27 December 2022 Permalink | Reply
    Tags:   

    Teaser 1882: Multiple solutions 

    From The Sunday Times, 11th October 1998 [link]

    One of the neatest problems ever published appeared on this page as Brainteaser 1040, on July 4, 1982. It asked for a number consisting of one each of the digits 1 to 9, so that the first digit of the number was divisible by 1, the first two (taken as a two-digit integer) by 2, and so on to 9. The answer was: 381654729.

    The reverse of this problem is to use one of each digit to form a number of which the last digit is divisible by 1, the last two (taken as a two-digit integer) by 2, and so on, to 9 or 10, according to whether we use the digits 1 to 9 or 0 to 9.

    There are, sadly, many answers. If you examined the last three digits of all the answers, and regarded them as three-digit numbers, it would strike you that they all had a large (highest) common factor.

    Curiously, only one multiple of that factor (consisting of three different digits) fails to occur as the last three digits of an answer.

    Which number is it that fails to occur?

    [teaser1882]

     
    • Jim Randell's avatar

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

      See also: Teaser 1040, Teaser 3053.

      This Python program uses the [[ SubstitutedExpression ]] solver from the enigma.py library to find answers to the embedded puzzle. It then calculates the GCD of the answers, and looks for 3-digit multiples of this value that do not occur.

      It runs in 62ms.

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, mgcd, irange, nsplit, printf)
      
      # suppose the number is: ABCDEFGHIJ
      p = SubstitutedExpression(
        [
          "IJ % 2 = 0",
          "HIJ % 3 = 0",
          "GHIJ % 4 = 0",
          "FGHIJ % 5 = 0",
          "EFGHIJ % 6 = 0",
          "DEFGHIJ % 7 = 0",
          "CDEFGHIJ % 8 = 0",
          "BCDEFGHIJ % 9 = 0",
          "ABCDEFGHIJ % 10 = 0", # or: "0 = J"
        ],
        d2i=dict(),
        answer="HIJ",
      )
      
      # solve the puzzle and accumulate the answers
      ns = set(p.answers(verbose=0))
      # calculate the gcd of the answers
      g = mgcd(*ns)
      
      # look for 3-digit multiples of g
      for m in irange.round(100, 999, step=g, rnd='I'):
        # does not occur in an answer
        if m in ns: continue
        # has 3 different digits
        if len(set(nsplit(m, 3))) != 3: continue
        # output solution
        printf("multiple = {m} [gcd = {g}]")
      

      Solution: The 3-digit multiple that does not occur is: 960.

      There are 202 answers to the embedded puzzle. And the final 3 digits of each answer form a multiple of 120.

      Like

    • GeoffR's avatar

      GeoffR 9:41 am on 28 December 2022 Permalink | Reply

      This program finds the maximum number of solutions and sets of digits for HIJ values.

      From the output values in the HIJ set, it can be readily seen that the gcd is 120 and the missing multiples of this gcd are 600 and 960. 600 can be ruled out as a solution as it contains repeating digits, so the missing gcd multiple is 960 (8 x 120).

      from functools import reduce
      
      # convert digit sequence to number
      d2n = lambda s: reduce(lambda x,  y: 10 * x + y, s)
      
      J = 0   # 10th last digit
      cnt = 0  # counter for number of solutions
      dig3 = set()  # set for all different values of HIJ
      
      for I in range(1, 10):
        IJ = d2n([I, J])
        if IJ % 2 != 0:continue
        
        for H in range(1, 10):
          if H in (I, J): continue
          HIJ = d2n([H, I, J])
          if HIJ % 3 != 0: continue
          
          for G in range(1, 10):
            GHIJ = d2n([G, H, I, J])
            if G in (H, I, J): continue
            if GHIJ % 4 != 0:continue
             
            for F in range(1, 10):
              if F in (G, H, I, J): continue
              FGHIJ = d2n([F, G, H, I, J])
              if FGHIJ % 5 != 0: continue
              
              for E in range(1, 10):
                if E in (F, G, H, I, J):continue
                EFGHIJ = d2n([E, F, G, H, I, J])
                if EFGHIJ % 6 != 0:continue
                
                for D in range(1, 10):
                  if D in (E, F, G, H, I, J):continue
                  DEFGHIJ = d2n([D, E, F, G, H, I, J])
                  if DEFGHIJ % 7 != 0:continue
                  
                  for C in range(1, 10):
                    if C in (D, E, F, G, H, I, J):continue
                    CDEFGHIJ = d2n([C, D, E, F, G, H, I, J])
                    if CDEFGHIJ % 8 != 0: continue
                    
                    for B in range(1, 10):
                      if B in (C, D, E, F, G, H, I, J):continue
                      BCDEFGHIJ = d2n([B, C, D, E, F, G, H, I, J])
                      if BCDEFGHIJ % 9 != 0:continue
                      
                      for A in range(1, 10):
                        if A in (B, C, D, E, F, G, H, I, J):continue
                        ABCDEFGHIJ = d2n([A, B, C, D, E, F, G, H, I, J])
                        if ABCDEFGHIJ % 10 != 0:continue
                        # update HIJ digit set and number of solutions
                        dig3.add(HIJ)
                        cnt += 1
      
      print('Number of solutions = ', cnt)  
      print('HIJ set = ', dig3)  
      
      # Number of solutions =  202
      # HIJ set =  {480, 840, 360, 240, 720, 120}
      
      
      
      
      

      Like

  • Unknown's avatar

    Jim Randell 4:34 pm on 23 December 2022 Permalink | Reply
    Tags:   

    Teaser 3144: Beware the jabbers’ clock, my son! 

    From The Sunday Times, 25th December 2022 [link]

    On the NHS vaccine booking website, my queue position updated every minute. My initial position was an odd value in the 9000s. This value had four different digits, and its leftmost digit and just two others in it were factors of it. Curiously, these properties applied to each of the decreasing four-figure values after the next eight updates. I also noticed that no value had a digit in common with its predecessor, no zeroes occurred before the fourth update and no nines after this.

    My son, told just the above, found all the possible values from which a complete set could be selected. From these he was certain of more than one of the nine queue positions.

    Give these certain positions in displayed order.

    This completes the archive of puzzles from 2022.

    [teaser3144]

     
    • Jim Randell's avatar

      Jim Randell 5:14 pm on 23 December 2022 Permalink | Reply

      I took “no zeroes occurred before the fourth update …” to mean there are no zeros in the first 4 numbers in the sequence (but it doesn’t imply that there necessarily is a zero digit in the 5th number in the sequence). And “… no nines after this” to mean that there are no nines in last 5 numbers of the sequence (and again, not implying that there is a 9 digit in 4th number in the sequence). Although I think that you can use a slightly different interpretation of these conditions and still come up with the same answer.

      This Python program runs in 307ms.

      Run: [ @replit ]

      from enigma import (irange, subsets, nconcat, intersect, printf)
      
      # does <x> divide <n>?
      divides = lambda n, x: x != 0 and n % x == 0
      
      # generate candidate numbers
      def generate():
        for (A, B, C, D) in subsets(irange(0, 9), size=4, select="P"):
          if A == 0: continue
          n = nconcat(A, B, C, D)
          # n is divisible by A and exactly 2 of B, C, D
          if divides(n, A) and sum(divides(n, d) for d in (B, C, D)) == 2:
            yield (A, B, C, D)
      
      # find viable sequences from <ns>
      def solve(ns, ss=[]):
        k = len(ss)
        # are we done?
        if k == 9:
          yield ss
        else:
          # choose the next number
          for (i, n) in enumerate(ns, start=1):
            # there are no 0s before the 4th update
            if k < 4 and 0 in n: continue
            # there are no 9s after the 3rd update
            if k > 3 and 9 in n: continue
            if k == 0:
              # the first number is odd, and starts with 9
              if n[0] < 9: break
              if n[-1] % 2 != 1: continue
            else:
              # subsequent numbers
              # have no digits in common with the previous number
              if len(set(n + ss[-1])) != 8: continue
            # looks good
            yield from solve(ns[i:], ss + [n])
      
      # collect candidate numbers in decreasing order
      ns = list(generate())
      ns.reverse()
      
      # what numbers occur in all possible sequences?
      ss = list(nconcat(x) for x in intersect(solve(ns)))
      
      # output solution
      printf("{ss}", ss=sorted(ss, reverse=1))
      

      Solution: The positions that are certain are: 7315 and 4728.

      7315 must occur as the 3rd value in the sequence, and 4728 must occur as the 6th value in the sequence.


      There are 5 positions where only one set of digits can occur. But for some of these it is possible to construct more than one valid number from the digits:

      9 + {1, 3, 5} → 9153, 9351, 9513, 9531
      8 + {2, 4, 6} → 8264, 8624
      7 + {1, 3, 5} → 7315
      5 + {0, 1, 3} → 5130, 5310
      4 + {2, 7, 8} → 4728

      Only 7315 and 4728 are unique in their respective positions.

      In total there are 8832 possible sequences of 9 numbers.

      Like

      • Jim Randell's avatar

        Jim Randell 7:38 am on 24 December 2022 Permalink | Reply

        With a little analysis we can get a much faster run time (and the program is only a little longer).

        First we note that the numbers must be of the form:

        9xxx, 8xxx, 7xxx, 6xxx, 5xxx, 4xxx, 3xxx, 2xxx, 1xxx

        And as long as there is valid number that can be constructed using the non-leading digits we don’t really care what order they appear in when we look for the next number. So we can just consider the collection of digits.

        Also, as successive numbers share no digits, as we construct numbers we can exclude the leading digit of the next number (as we already know what it will be).

        Then at the end we take the collections of digits we have found that we know must appear in each position, we can look for those that give just one valid number, and we have found an answer.

        This Python program runs in 56ms. (Internal run time is 963µs).

        Run: [ @replit ]

        from enigma import (irange, subsets, nconcat, diff, intersect, singleton, printf)
        
        # does <x> divide <n>?
        divides = lambda n, x: x != 0 and n % x == 0
        
        digits = list(irange(0, 9))
        
        # is the number formed from (A, B, C, D) valid?
        def valid(A, B, C, D):
          n = nconcat(A, B, C, D)
          if A == 9 and n % 2 != 1:
            return
          if divides(n, A) and sum(divides(n, x) for x in (B, C, D)) == 2:
            return n
        
        # generate candidate digit sets
        # return digits (A, B, C, D) - A is leading digit; B, C, D are ordered
        def candidates(A, ds):
          # choose the three final digits
          for xs in subsets(ds, size=3):
            # can we find a valid number?
            if any(valid(A, B, C, D) for (B, C, D) in subsets(xs, size=len, select="P")):
              yield (A,) + xs
        
        # k = leading digit
        def solve(k, ss=[]):
          # are we done?
          if k == 0:
            yield ss
          else:
            if k == 9:
              # the first number is odd, and has no 0 digit
              cs = candidates(9, diff(digits, {0, 8, 9}))
            elif k > 5:
              # subsequent number with no 0 digit
              cs = candidates(k, diff(digits, ss[-1], {0, k - 1, k}))
            else:
              # subsequent number with no 9 digit
              xs = {k, 9}
              if k > 1: xs.add(k - 1)
              cs = candidates(k, diff(digits, ss[-1], xs))
            for s in cs:
              yield from solve(k - 1, ss + [s])
        
        # look for positions that must occur in all solutions
        for ss in sorted(intersect(solve(9)), reverse=1):
          # look for candidates with only a single valid number
          ns = (valid(ss[0], B, C, D) for (B, C, D) in subsets(ss[1:], size=len, select="P"))
          n = singleton(filter(None, ns))
          if n:
            printf("{n}")
        

        Like

    • Frits's avatar

      Frits 12:48 am on 24 December 2022 Permalink | Reply

      A different approach. At the end only 39 candidate numbers remain.

         
      from itertools import permutations
      from functools import reduce
      
      # convert digit sequences to number
      d2n = lambda s: reduce(lambda x,  y: 10 * x + y, s)
      
      # return entry and column value where column <col> is unique
      def unique_col(seq, col=0):
        d = dict()
        for s in seq:
          d[s[col]] = s[col] in d
      
        return [(s[col], s) for s in seq if not d[s[col]]]
       
      # generate candidate numbers
      def generate():
        for A, B, C, D in permutations(range(9, -1, -1), 4):
          if A == 0: break
          if (A == 9 and D % 2 == 0): continue
          
          n = d2n([A, B, C, D])
          # n is divisible by A and exactly 2 of B, C, D
          if n % A or sum(n % d == 0 for d in (B, C, D) if d) != 2:
            continue
          # A has to connect with A - 1 and A + 1 so B, C and D 
          # may not be equal to A - 1 or A + 1
          if (A < 9 and (A + 1) in {B, C, D}) or \
             (A > 1 and (A - 1) in {B, C, D}): continue
          # no zeroes occurred before the fourth update and no nines after this  
          if (A < 6 or 0 not in {B, C, D}) and \
             (A > 4 or 9 not in {B, C, D}):
            yield (A, B, C, D)
       
      # collect candidate numbers in decreasing order
      ns = list(generate())
      
      # keep maintaining the list of candidate numbers
      while True:
        # determine which numbers always exist for a specific leftmost digit ...
        d = dict()
        for A, B, C, D in ns:
          if A not in d:
            d[A] = {B, C, D}
          else:
            d[A] &= {B, C, D}
       
        # we need nine queue positions (each with a unique leftmost digit)
        if len(d) != 9: 
          print("no solution")
          exit(0)
      
        # ... and delete specific candidate numbers
        for k, vs in d.items():
          for v in vs:
            if k < 9:
              ns = [n for n in ns if n[0] != (k + 1) or v not in n[1:]]
            if k > 1:
              ns = [n for n in ns if n[0] != (k - 1) or v not in n[1:]]  
        
        
        delete = set()  
        # for each candidate number check if valid adjacent queue positions exist
        for A, B, C, D in ns:
          bef, aft = 0, 0
          for A2, B2, C2, D2 in ns:
            # skip if invalid 
            if len(set([A, B, C, D, A2, B2, C2, D2])) != 8: continue
            if A == 1 or A2 == A - 1:
              bef = 1  
            if A == 9 or A2 == A + 1:
              aft = 1
            # break if we find valid adjacent queue positions 
            if bef == aft == 1: break
          else:  
            # A, B, C, D has no (two) valid adjacent queue positions
            delete.add((A, B, C, D))
          
        if not delete: break
       
        # delete specific candidate numbers
        ns = [n for n in ns if n not in delete]
      
      print("certain positions:", [d2n(x[1]) for x in unique_col(ns)])
      

      Like

    • Frits's avatar

      Frits 5:47 pm on 24 December 2022 Permalink | Reply

      Borrowing Jim’s “intersect” call.

       
      from enigma import SubstitutedExpression, intersect
      from functools import reduce
      
      # we have nine 4-digit numbers = 36 variables
      # as the leftmost digits are known and the middle entry has to end on zero
      # we are left with 26 variables, so A-Z
      
      # 9ABC, 8DEF, 7GHI, 6JKL, 5MN0, 4OPQ, 3RST, 2UVW, 1XYZ
      nums = ['ABC', 'DEF', 'GHI', 'JKL', 'MN', 'OPQ', 'RST', 'UVW', 'XYZ']
      
      # convert digit sequences to number
      d2n = lambda s: reduce(lambda x,  y: 10 * x + y, s)
      
      # check validity 2nd argument and possible connection with 1st argument
      def check(s1, s2):
        n = d2n(s2)
        # it's leftmost digit and just two others in it were factors of it
        if n % s2[0] or sum(n % d == 0 for d in s2[1:] if d) != 2:
          return False
      
        if s1 and len(set(s1 + s2)) != 8: return False
        
        return True
      
      # invalid digit / symbol assignments
      d2i = dict()
      for d in range(0, 10):
        vs = set()
        if d:
          vs.update(nums[9 - d])
          if d < 9:
            vs.update(nums[8 - d])
          if d > 1:
            vs.update(nums[10 - d])  
        # no zeroes occurred before the fourth update and no nines after this     
        if d == 0:
          vs.update('ABCDEFGHIJKLMN') 
          # as 5MN0 also delete 0 from 4OPQ entries
          vs.update('OPQ') 
        if d == 9:
          vs.update('OPQRSTUVWXYZ')  
        if d % 2 == 0:
          vs.update('C')  
        d2i[d] = vs
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
          # only check validity of 9ABC
          "check([], [9, A, B, C])",
          
          # check validity 2nd argument and connection with 1st argument
          "check([9, A, B, C], [8, D, E, F])",
          "check([8, D, E, F], [7, G, H, I])",
          "check([7, G, H, I], [6, J, K, L])",
          "check([6, J, K, L], [5, M, N, 0])",
          "check([5, M, N, 0], [4, O, P, Q])",
          "check([4, O, P, Q], [3, R, S, T])",
          "check([3, R, S, T], [2, U, V, W])",
          "check([2, U, V, W], [1, X, Y, Z])",
        ],
        answer="(9000+ABC, 8000+DEF, 7000+GHI, 6000+JKL, 5000+10*MN, 4000+OPQ, \
                 3000+RST, 2000+UVW, 1000+XYZ)",
        d2i=d2i,
        distinct=('ABC', 'DEF', 'GHI', 'JKL', 'MN', 'OPQ', 'RST', 'UVW', 'XYZ'),
        env=dict(check=check),
        denest=32,      # [CPython] work around statically nested block limit
        verbose=0,    # use 256 to see the generated code
      )
      
      answs = [y for _, y in p.solve()]
        
      print("certain positions:", list(d2n([x]) for x in intersect(answs)))
      

      Like

      • Jim Randell's avatar

        Jim Randell 4:41 pm on 26 December 2022 Permalink | Reply

        @Frits: That’s neat. I hadn’t thought of using the [[ SubstitutedExpression ]] solver.

        Here’s a run file that solves the puzzle (it does generate all possible sequences though, so it is not as fast as my second program).

        This run file executes in 98ms.

        Run: [ @replit ]

        #! python3 -m enigma -rr
        
        SubstitutedExpression
        
        # the positions are:
        # 9xxx = ABCD
        # 8xxx = EFGH
        # 7xxx = IJKL
        # 6xxx = MNPQ
        # 5xxx = RSTU
        # 4xxx = VWXY
        # 3xxx = Zabc
        # 2xxx = defg
        # 1xxx = hijk
        
        # leading digits are known
        --assign="A,9"
        --assign="E,8"
        --assign="I,7"
        --assign="M,6"
        --assign="R,5"
        --assign="V,4"
        --assign="Z,3"
        --assign="d,2"
        --assign="h,1"
        
        # no number shares digits with its neighbour
        --distinct="ABCDEFGH,EFGHIJKL,IJKLMNPQ,MNPQRSTU,RSTUVWXY,VWXYZabc,Zabcdefg,defghijk"
        
        # each number is divisible by its leading digit and exactly 2 of the others
        --code="divides = lambda n, d: d > 0 and n % d == 0"
        --code="check = lambda n, A, B, C, D: divides(n, A) and sum(divides(n, x) for x in (B, C, D)) == 2"
        
        "check({ABCD}, {A}, {B}, {C}, {D})"
        "check({EFGH}, {E}, {F}, {G}, {H})"
        "check({IJKL}, {I}, {J}, {K}, {L})"
        "check({MNPQ}, {M}, {N}, {P}, {Q})"
        "check({RSTU}, {R}, {S}, {T}, {U})"
        "check({VWXY}, {V}, {W}, {X}, {Y})"
        "check({Zabc}, {Z}, {a}, {b}, {c})"
        "check({defg}, {d}, {e}, {f}, {g})"
        "check({hijk}, {h}, {i}, {j}, {k})"
        
        # 9xxx is odd
        "{D} % 2 == 1"
        
        # no 0's in 9xxx, 8xxx, 7xxx, 6xxx
        --invalid="0,ABCDEFGHIJKLMNPQ"
        # no 9's in 5xxx, 4xxx, 3xxx, 2xxx, 1xxx
        --invalid="9,RSTUVWXYZabcdefghijk"
        
        # collect numbers that occur in all sequences
        --answer="({ABCD}, {EFGH}, {IJKL}, {MNPQ}, {RSTU}, {VWXY}, {Zabc}, {defg}, {hijk})"
        --code="fn = lambda xs: sorted(intersect(xs), reverse=1)"
        --accumulate="fn"
        
        --template="({ABCD} {EFGH} {IJKL} {MNPQ} {RSTU} {VWXY} {Zabc} {defg} {hijk})"
        --solution=""
        
        --denest=-1  # CPython workaround
        

        Like

    • Hugh Casement's avatar

      Hugh Casement 11:49 am on 1 January 2023 Permalink | Reply

      I found, in addition to those mentioned by Jim,
      6492, 6924, 6948
      eight possible numbers starting with 3 and also containing 015 or 156 in some order
      seven possible numbers starting with 2 and all containing 4 and 8
      twelve possible numbers starting with 1 and all containing 5.

      Like

  • Unknown's avatar

    Jim Randell 8:50 am on 22 December 2022 Permalink | Reply
    Tags:   

    Teaser 2693: Let the dog see the rabbit 

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

    Each of Messrs Burrows, Cook, Field, Skinner and Warren has a pet dog, and each of the men’s wives has a pet rabbit. Each dog bit one of the rabbits, with no two dogs biting the same rabbit. Mrs Warren’s rabbit was bitten by Mr Skinner’s dog. Mr Warren’s dog bit the rabbit owned by the wife of the owner of the dog that bit Mrs Field’s rabbit. Mrs Burrows’ rabbit was bitten by the dog owned by the husband of the lady whose rabbit was bitten by the dog belonging to the husband of the lady whose rabbit was bitten by the dog belonging to Mr Cook.

    Whose dog bit Mrs Skinner’s rabbit, and whose rabbit did Mr Field’s dog bite?

    There are now 800 Teaser puzzles available on the site.

    [teaser2693]

     
    • Jim Randell's avatar

      Jim Randell 8:53 am on 22 December 2022 Permalink | Reply

      Each surname has a dog and a rabbit, so we can just attach the surnames directly to the animals.

      This Python program runs in 50ms. (Internal runtime is 180µs).

      Run: [ @replit ]

      from enigma import (subsets, printf)
      
      # label the surnames
      names = (B, C, F, S, W) = (0, 1, 2, 3, 4)
      
      # choose an ordering of the names for the rabbits
      # r[X] == Y  ->  "dog X bit rabbit Y"
      for r in subsets(names, size=len, select="P"):
      
        # "rabbit W was bitten by dog S"
        if not (r[S] == W): continue
      
        # "dog W bit rabbit of family of dog that bit rabbit F"
        if not (r[r[W]] == F): continue
      
        # "rabbit B was bitten by the dog of family whose rabbit was bitten by the
        # dog of family whose rabbit was bitten by dog C"
        if not (r[r[r[C]]] == B): continue
      
        # output solution (dog -> rabbit)
        name = "BCFSW"
        for (x, y) in zip(names, r):
          printf("dog {x} bit rabbit {y}", x=name[x], y=name[y])
        printf()
      

      Solution: Cook’s dog bit Skinner’s rabbit. Field’s dog bit Cook’s rabbit.

      The full solution is:

      dog B bit rabbit F
      dog C bit rabbit S
      dog F bit rabbit C
      dog S bit rabbit W
      dog W bit rabbit B

      Like

  • Unknown's avatar

    Jim Randell 10:08 am on 20 December 2022 Permalink | Reply
    Tags:   

    Teaser 1848: Double celebration 

    From The Sunday Times, 15th February 1998 [link]

    It was quite a year. I’d already had my 60th birthday, and my wife Margaret was due to have hers later in the year. The following year was our 10th wedding anniversary. But all that is beside the point … almost.

    By her first marriage, Margaret had two daughters. By an amazing coincidence, Helen was born on Margaret’s birthday, and then, two years later, Joanna was born on Margaret’s and Helen’s birthday. Furthermore, Margaret and Helen were both born on the same day of the week… but if I had told you how old Margaret was when Helen was born you could have worked that out for yourself.

    Now, the question is…

    What was the double celebration that was due to take place later in the year?

    This puzzle was the first to use the name Teaser (which has continued up to the present). Prior to this they were named Brainteaser (1982 – 1998) (and before that Brain teaser (1981 – 1982) and Brain-Teaser (1961 – 1981)).

    [teaser1848]

     
    • Jim Randell's avatar

      Jim Randell 10:10 am on 20 December 2022 Permalink | Reply

      Originally I considered that the setter (recounting the story) and his wife must be born sometime between 1875 and 1937. But if we consider the full range the puzzle becomes unsolvable. So instead I started from 1901.

      Since the only possible multiple celebration we know about is Margaret’s 60th birthday with one (or more) of the daughters, I am assuming we need to work out possible ages for the daughters.

      Instead of testing every day in every year I choose two representative dates, one before any potential leap day (I chose 1st January) and one after any potential leap day (I chose 1st March).

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

      Run: [ @replit ]

      from datetime import date
      from enigma import (cproduct, irange, diff, printf)
      
      # record ages that have the same day of the week, and those that do not
      same_dow = set()
      diff_dow = set()
      
      # consider birth dates for M
      for (y, m) in cproduct([irange(1901, 1937), (1, 3)]):
        M = date(y, m, 1)
        M_dow = M.isoweekday()
        # consider M's age at the time of H's birth
        for d in irange(18, 51):
          H = date(y + d, m, 1)
          if H is None: continue
          if H.isoweekday() == M_dow:
            same_dow.add(d)
          else:
            diff_dow.add(d)
      
      # look for ages that always give the same day of the week
      for d in diff(same_dow, diff_dow):
        H = 60 - d
        J = H - 2
        printf("d={d}: M=60 -> H={H} J={J}")
      

      Solution: The double celebration was Margaret’s 60th birthday and Joanna’s 30th birthday.

      The only possible age difference that guarantees the mother and child are born on the same day is 28 years (providing we don’t span 1900, which was not a leap year).

      So Helen was born on Margaret’s 28th birthday, and Joanna was born 2 years later on Margaret’s 30th birthday.

      Hence when Margaret is 60, Helen will be 32 and Joanna will be 30.

      Like

  • Unknown's avatar

    Jim Randell 11:05 am on 18 December 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 409: House number 

    From The Sunday Times, 9th March 1969 [link]

    I live in a long street in which the houses are all on one side numbered consecutively.

    If I add my house number to the sum of a certain number of consecutive house numbers on my immediate left, the answer is equal to the sum of the same number of consecutive house numbers on my immediate right.

    Moreover, if I add the square of my house number to the sum of the squares of a different number of consecutive house numbers on my immediate left, the result is equal to the sum of the squares of the same number of consecutive house numbers on my immediate right.

    I do not live at number 12, and perhaps I should add that my house number is less than 14,000.

    What is it?

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

    [teaser409]

     
    • Jim Randell's avatar

      Jim Randell 11:13 am on 18 December 2022 Permalink | Reply

      Here is a constructive solution.

      For each possible house number value n, it look for left/right sequences that give equal sums (the left sequence includes n and the right sequence does not).

      It runs in 223ms.

      Run: [ @replit ]

      from enigma import (irange, identity, sq, printf)
      
      # find the size of left/right sequences with equal sums
      # return the size of the right sequence (left is 1 more)
      def check(n, fn=identity):
        (sl, sr, a, b) = (fn(n), 0, n, n)
        while sl > sr and a > 1:
          a -= 1
          b += 1
          sl += fn(a)
          sr += fn(b)
        if sl == sr: return (b - n)
      
      # consider values for n
      for n in irange(1, 13999):
        # check for sum
        k1 = check(n)
        if not k1: continue
        # check for sum of squares
        k2 = check(n, sq)
        if not k2: continue
        # output solution
        printf("n={n}: k1={k1} k2={k2}")
      

      Solution: The house number is: 420.

      And we have:

      sum([400 .. 420]) = sum([421 .. 440]) = 8610
      sumsq([406 .. 420]) = sumsq([421 .. 434]) = 2558815


      With some analysis we can have a faster program.

      If we consider the setter at house n, and the k consecutive houses immediately before and after him we have:

      [(n − k) + (n − k + 1) + … + (n − 1) + n] = [(n + 1) + (n + 2) + … + (n + k)]

      There are (k + 1) terms of the left and k terms on the right, so matching them up pairwise, except the n term on the left, we get:

      n = [(n + 1) − (n − k)] + [(n + 2) − (n − k + 1)] + … + [(n + k) − (n − 1)]
      n = (k + 1) + (k + 1) + … + (k + 1)
      n = k(k + 1)

      So the house number n is the product of 2 consecutive integers.

      We have a similar situation with the squares of the numbers:

      [(n − m)² + (n − k + 1)² + … + (n − 1)² + n] = [(n + 1)² + (n + 2)² + … + (n + m)²]

      Which we can pair up like this:

      n² = [(n + 1)² − (n − 1)²] + [(n + 2)² − (n − 2)²] + … + [(n + m)² − (n − m)²]
      n² = (4n) + (8n) + … + (4mn)
      n² = 4n(1 + 2 + … + m)
      n = 2m(m + 1)

      So the house number n is also twice the product of 2 consecutive integers.

      The following Python program finds the required answer using this analysis, and also verifies that the sums of the sequences are equal.

      It runs in 59ms. (Internal runtime is 317µs).

      Run: [ @replit ]

      from enigma import (irange, inf, sumsq, printf)
      
      # verify left/right sequences
      def verify(ls, rs, fn):
        # construct the sequences
        (ls, rs) = (list(ls), list(rs))
        # and their sums
        (sl, sr) = (fn(ls), fn(rs))
        # output results
        printf("-> [{l0}..{l1}] = {sl}; [{r0}..{r1}] = {sr}", l0=ls[0], l1=ls[-1], r0=rs[0], r1=rs[-1])
        if sl != sr: printf("=> FAIL")
      
      # collect values: k -> k * (k + 1)
      d = dict()
      for k in irange(1, inf):
        n = k * (k + 1)
        if not (n < 14000): break
        # have we seen half this value?
        m = d.get(n // 2)
        if m is not None:
          printf("n={n}: k={k} m={m}")
          # output solution
          verify(irange(n - k, n), irange(n + 1, n + k), sum)
          verify(irange(n - m, n), irange(n + 1, n + m), sumsq)
      
        # record this value
        d[n] = k
      

      And we find the same two candidate solutions:

      12 = 3×4 = 2×(2×3)
      420 = 20×21 = 2×(14×15)

      The next two candidates would be:

      14280 = 119×120 = 2×(84×85)
      485112 = 696×697 = 2×(492×493)

      Which is OEIS A098602 [ @oeis.org ].

      And can be generated using the following recurrence relation:

      >>> fn = unpack(lambda a, b, c: 35 * (c - b) + a)
      >>> first(fib(0, 12, 420, fn=fn), 8)
      [0, 12, 420, 14280, 485112, 16479540, 559819260, 19017375312]
      

      Like

      • John Crabtree's avatar

        John Crabtree 3:26 am on 21 December 2022 Permalink | Reply

        k and m are related by a negative form of the Pell equation, ie
        (2k+1)² – 2(2m+1)² = -1
        with values of (2k+1, 2m+1) = (1, 1), (7, 5), (41, 29), (239, 169), (1393, 985) etc

        Like

      • Jim Randell's avatar

        Jim Randell 3:57 pm on 1 October 2025 Permalink | Reply

        The house number n is the product of 2 consecutive integers, and also twice the product of a different 2 consecutive integers:

        n = x(x + 1)
        n = 2y(y + 1)

        x(x + 1) = 2y(y + 1)

        setting: X = 2x + 1, Y = 2y + 1, we get:

        (X − 1)(X + 1) = 2(Y − 1)(Y + 1)
        X² − 1 = 2.Y² − 2
        X² − 2.Y² = −1

        (Which is the Pell equation noted by John Crabtree).

        We can then use the [[ diop_quad() ]] solver from the pells.py library to solve this:

        from enigma import (div, printf)
        import pells
        
        # solve X^2 - 2.Y^2 = -1
        for (X, Y) in pells.diop_quad(1, -2, -1):
          # X = 2x + 1, Y = 2y + 1
          (x, y) = (div(X - 1, 2), div(Y - 1, 2))
          if x is None or y is None: continue
          # calculate the house number (= x(x + 1) = 2y(y + 1))
          n = x * (x + 1)
          if n < 1 or n == 12: continue
          if not (n < 14000): break
          # output solution
          printf("n={n} [X={X} Y={Y}; x={x} y={y}]")
        

        Solutions to the Pell equation are generated by the following recurrence relation:

        (X, Y) = (1, 1)
        (X’, Y’) = (3X + 4Y, 2X + 3Y)

        Like

  • Unknown's avatar

    Jim Randell 4:26 pm on 16 December 2022 Permalink | Reply
    Tags:   

    Teaser 3143: Pipe fittings 

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

    A plumber had three thin metal pipes with square, rectangular and elliptical cross-sections. In order to fit them into his van, he slid the rectangular pipe inside the elliptical pipe and the elliptical pipe inside the square pipe, before placing the pipe assembly in the van. There are four points where the pipes all touch, as shown in the diagram. The maximum and minimum widths of the elliptical and rectangular pipes and the diagonal width of the square pipe were all even numbers of mm less than 1000, of which one was a perfect square.

    What were the five widths (in increasing order)?

    [teaser3143]

     
    • Jim Randell's avatar

      Jim Randell 5:20 pm on 16 December 2022 Permalink | Reply

      If we consider the diagram to be centred on the origin (0, 0), and the point where all four figures meet in the (+, +) quadrant is (x, y), then the rectangle has dimensions (2x, 2y) and the diagonal of the square is 2z where z = x + y.

      And if the ellipse has semi-major axis a and semi-minor axis b, then the equation of the tangent at the point (p, q) is:

      x(p/a²) + y(q/b²) = 1

      and this is the same as the line that defines the side of the square tube:

      x + y = z

      Hence:

      a² = x.z
      b² = y.z

      Assuming exactly one of the required widths is a perfect square gives a unique answer to the puzzle.

      This Python program runs in 134ms. (Internal runtime is 76ms).

      Run: [ @replit ]

      from enigma import (irange, decompose, is_square, icount_exactly as icount, printf)
      
      # choose a value for z
      for z in irange(5, 499):
        # consider values for x and y
        for (y, x) in decompose(z, 2, increasing=1, sep=1, min_v=1):
          # calculate values for a and b
          a = is_square(x * z)
          b = is_square(y * z)
          if a and b:
            # calculate the widths (in increasing order)
            ws = sorted(2 * n for n in (a, b, x, y, z))
            # check exactly one of the widths is a perfect square
            if icount(ws, is_square, 1):
              # output solution
              printf("ws={ws} [z={z} x={x} y={y} a={a} b={b}]")
      

      Solution: The widths are (in mm): 180, 300, 320, 400, 500.


      Swapping [[ icount_exactly() ]] for [[ icount_at_least() ]] reveals 4 further solutions, so it is necessary to assume exactly one of the widths is a perfect square.

      Solutions with at least one perfect square are:

      (36, 60, 64, 80, 100) [3]
      (144, 240, 256, 320, 400) [3]
      (180, 300, 320, 400, 500) [1]
      (100, 260, 576, 624, 676) [3]
      (324, 540, 576, 720, 900) [3]

      Like

      • Jim Randell's avatar

        Jim Randell 6:09 pm on 16 December 2022 Permalink | Reply

        Or we can use the [[ pythagorean_triples() ]] function from the enigma.py library to get a more efficient program.

        This Python program runs in 58ms. (Internal runtime is 777µs).

        Run: [ @replit ]

        from enigma import (pythagorean_triples, div, sq, is_square, icount_exactly as icount, printf)
        
        # generate values for a, b, z, st: a^2 + b^2 = z^2
        for (b, a, z) in pythagorean_triples(499):
          # calculate x and y
          (x, y) = (div(sq(n), z) for n in (a, b))
          if x is None or y is None: continue
          # calculate the widths (in increasing order)
          ws = sorted(2 * n for n in (a, b, x, y, z))
          # check exactly one of the widths is a perfect square
          if icount(ws, is_square, 1):
            # output solution
            printf("ws={ws} [z={z} a={a} b={b} x={x} y={y}]")
        

        Like

    • GeoffR's avatar

      GeoffR 4:51 pm on 17 December 2022 Permalink | Reply

      % A Solution in MiniZinc
      include "globals.mzn";
      
      % x, y = rectangle dimensions and a,b ellipse semi major/minor axes
      var 4..498:x; var 4..498:y;
      var 4..498:a; var 4..498:b;
      var 4..498:z;
      
      constraint a > b /\ z = x + y;
      
      % Using Jim's derivation and named variables
      constraint a * a == x * z /\ b * b == y * z;
      
      predicate is_sq(var int: m) =
      exists(n in 1..ceil(sqrt(int2float(ub(m))))) (n*n = m );
      
      constraint sum([is_sq(2*a), is_sq(2*b), is_sq(2*x),
       is_sq(2*y), is_sq(2*z)]) == 1;
      
      solve satisfy;
      
      output ["Five widths [2a, 2b, 2x, 2y, 2z ] = " ++ 
      show([2*a, 2*b, 2*x, 2*y, 2*z])];
      
      
      
      

      Like

  • Unknown's avatar

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

    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 1:03 pm on 13 December 2022 Permalink | Reply
    Tags:   

    Teaser 2001: Unfamiliar territory 

    From The Sunday Times, 21st January 2001 [link]

    I was forced to make an emergency landing somewhere in Latin America. I got out of the plane, crossed a terrain interrupted by a higgledy-piggledy coating of identical bright marble cubes and found myself in a town. I learnt that the citizens had 10 different characters for digits. I then spotted what I took to be the plinth for a statue; it consisted of six layers. I learnt that each layer was made up of one solid square of cubes like those I had seen earlier. Beside this edifice was what I took to be an explanatory notice; included in it was the following, which I was told was a list of the number of cubes in each successive layer:

    ICNGTC
    EEAGTR
    RTGSEG
    IIIGNAE
    ERGINNO
    RAIGGSS

    My informant told me I should be able to work out the value of each character.

    “Thank you for that”, I said, “but can you tell me the name of your country?”

    “Why yes”, he replied, “you are now in 86997 321542387

    Where did I make my forced landing?

    Teaser 2001 was originally published in the year 2001. It is the only Teaser puzzle to have this property.

    [teaser2001]

     
    • Jim Randell's avatar

      Jim Randell 1:04 pm on 13 December 2022 Permalink | Reply

      We can use the [[ SubstitutedExpression() ]] solver from the enigma.py library to find the assignments of letters to digits, and then use the [[ translate() ]] function to decode the required answer.

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

      Run: [ @replit ]

      from enigma import (SubstitutedExpression, sprintf as f, translate, printf)
      
      # the following are encoded square numbers
      sqs = "ICNGTC EEAGTR RTGSEG IIIGNAE ERGINNO RAIGGSS".split()
      
      # construct the alphametic puzzle
      p = SubstitutedExpression(list(f("is_square_p({x})") for x in sqs))
      
      # solve it
      for s in p.solve(verbose=0):
        # map digits (as strings) to letters
        r = dict((str(v), k) for (k, v) in s.items())
        # decode the phrase
        t = translate("86997 321542387", r)
        # output the solution
        printf("answer = {t}")
      

      Solution: The plane landed in: TERRA INCOGNITA.

      And the squares are:

      ICNGTC = 312481 = 559²
      EEAGTR = 667489 = 817²
      RTGSEG = 984064 = 992²
      IIIGNAE = 3334276 = 1826²
      ERGINNO = 6943225 = 2635²
      RAIGGSS = 9734400 = 3120²

      The answer published with Teaser 2003 says: “TERRA INCOGNITO (sic)”, although clearly each word of the answer must end with the same letter.

      Like

    • GeoffR's avatar

      GeoffR 5:33 pm on 13 December 2022 Permalink | Reply

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      var 1..9:I; var 0..9:C; var 0..9:N; var 0..9:G; var 0..9:T; 
      var 1..9:E; var 0..9:A; var 1..9:R; var 0..9:S; var 0..9:O; 
      
      constraint all_different ([I, C, N, G, T, E, A, R, S, O]);
      
      predicate is_sq(var int: y) =
      exists(z in 1..ceil(sqrt(int2float(ub(y))))) (z*z = y );
      
      function var int: num6 (var int: u, var int: v, var int: w, var int: x, 
      var int: y, var int: z) = 100000*u + 10000*v + 1000*w + 100*x + 10*y + z;
      
      function var int: num7 (var int: t,var int: u, var int: v, var int: w, var int: x, 
      var int: y, var int: z) = 1000000*t + 100000*u + 10000*v + 1000*w + 100*x + 10*y + z;
      
      % Three 6-digit squares
      var 100000..999999:ICNGTC = num6(I, C, N, G, T, C);
      var 100000..999999:EEAGTR = num6(E, E, A, G, T, R);
      var 100000..999999:RTGSEG = num6(R, T, G, S, E, G);
      constraint sum([is_sq(ICNGTC), is_sq(EEAGTR), is_sq(RTGSEG)]) == 3;
      
      % Three 7-digit squares
      var 1000000..9999999:IIIGNAE = num7(I, I, I, G, N, A ,E);
      var 1000000..9999999:ERGINNO = num7(E, R, G, I, N, N, O);
      var 1000000..9999999:RAIGGSS = num7(R, A, I, G, G, S, S);
      constraint sum([is_sq(IIIGNAE), is_sq(ERGINNO), is_sq(RAIGGSS)]) == 3;
      
      solve satisfy;
      
      output[ "[I,C,N,G,T,E,A,R,S,O] = " ++ show([I,C,N,G,T,E,A,R,S,O]) ];
      
      % [I, C, N, G, T, E, A, R, S, O] = 
      % [3, 1, 2, 4, 8, 6, 7, 9, 0, 5]
      % ----------
      % ==========
      % Landing Location is [8,6,9,9,7  3,2,1,5,4,2,3,8,7]
      %      -  which means [T E R R A  I N C O G N I T A]
      
      
      

      Like

    • GeoffR's avatar

      GeoffR 12:29 pm on 14 December 2022 Permalink | Reply

      A two-stage solution in Python. It finds the digits / letters representation first. It then forms a dictionary to translate the landing code, using the ‘translate’ function from the enigma.py library.

      
      from itertools import permutations
      from enigma import is_square, translate
      
      for p1 in permutations('1234567890', 5):
          I, C, N, G, T = p1
          if I == '0':continue
      
          # Three 6-digit squares
          ICNGTC = int(I + C + N + G + T + C)
          if not is_square(ICNGTC): continue
          q1 = set('1234567890').difference([I, C, N, G, T])
          for p2 in permutations(q1):
              E, A, R, S, O = p2
              if '0' in (E, R): continue
              EEAGTR = int(E + E + A + G + T + R)
              if not is_square(EEAGTR):continue
              RTGSEG = int(R + T + G + S + E + G)
              if not is_square(RTGSEG):continue
      
              # Three 7-digit squares
              IIIGNAE = int(I + I + I + G + N + A + E)
              if not is_square(IIIGNAE):continue
              ERGINNO = int(E + R + G + I + N + N + O)
              if not is_square(ERGINNO):continue
              RAIGGSS = int(R + A + I + G + G + S + S)
              if not is_square(RAIGGSS):continue
              
              # output letters/digits - for a dictionary
              print(f"'I'={I}, 'C'={C}, 'N'={N}, 'G'={G}, 'T'={T}")
              print(f"'E'={E}, 'A'={A}, 'R'={R}, 'S'={S}, 'O'={O}")
              # 'I'=3, 'C'=1, 'N'=2, 'G'=4, 'T'=8
              # 'E'=6, 'A'=7, 'R'=9, 'S'=0, 'O'=5
      
      # 2nd stage - use output to form a dictionary - digits to letters
      DL = { '1': 'C', '2': 'N', '3': 'I', '4': 'G', '5': 'O', \
             '6': 'E', '7': 'A', '8': 'T', '9': 'R', '0': 'S'}
      
      # Translate landing code
      t = translate("86997 321542387", DL)
      print(f"Answer = {t}")
      # Answer = TERRA INCOGNITA
      
      

      Like

  • Unknown's avatar

    Jim Randell 9:35 am on 11 December 2022 Permalink | Reply
    Tags:   

    Brain-Teaser 257: Brothers and sisters 

    From The Sunday Times, 3rd April 1966 [link]

    The boys and girls of a primary school have recently done a survey among themselves. They find that half the children who have brothers are girls who have no sisters, and that half the children who have sisters are girls who have no brothers. Boys who have no sisters are equal in number to boys who have sisters but not brothers. In all, there are fourteen more children with sisters than there are children with brothers.

    How many of the boys have neither brothers nor sisters?

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

    [teaser257]

     
    • Jim Randell's avatar

      Jim Randell 9:35 am on 11 December 2022 Permalink | Reply

      Each child fits into one of the following 8 categories (boy/girl; brother?; sister?):

      a = boy; no brothers; no sisters
      b = boy; brother; no sisters
      c = boy; brother; sister
      d = boy; no brother; sister
      e = girl; no brothers; no sisters
      f = girl; brother; no sisters
      g = girl; brother; sister
      h = girl; no brother; sister

      We are told:

      [1] “half the children who have brothers are girls who have no sisters”
      (b + c + f + g) / 2 = f
      ⇒ b + c + f + g = 2f
      ⇒ b + c + g = f

      [2] “half the children who have sisters are girls who have no brothers”
      (c + d + g + h) / 2 = h
      ⇒ c + d + g + h = 2h
      ⇒ c + d + g = h

      [3] “boys who have no sisters are equal in number to boys who have sisters but not brothers”
      a + b = d
      ⇒ a = d − b

      [4] “there are fourteen more children with sisters than there are children with brothers”
      (c + d + g + h) = 14 + (b + c + f + g)

      And we want to find the value of a:

      Substituting for (c + d + g + h) from [2] and (b + c + f + g) from [1] into [4]:
      (c + d + g + h) = 14 + (b + c + f + g)
      2h = 14 + 2f
      ⇒ h − f = 7

      [2] − [1]:
      (c + d + g) − (b + c + g) = h − f
      ⇒ d − b = h − f

      Hence:
      a = d − b = h − f = 7

      Solution: 7 of the boys have neither brothers or sisters.

      Like

    • GeoffR's avatar

      GeoffR 1:07 pm on 12 December 2022 Permalink | Reply

      I used Jim’s initial equations to explore possible values of variables (a..h)
      Variable ‘e’ is not used in any of the equations, so it can take any value (0..10)

      It looks as though only the following values are fixed:

      a = boy; no brothers; no sisters (a = 7)
      d = boy; no brother; sister (d = 7)

      Multiple outputs showed that all values, apart from a and d, are not constrained in value by the equations. Value h can increase above 7 by varying other values.

      One example below shows that the four equations are still satisfied for this example:

      [a, b, c, d, e, f, g, h] = [7, 0, 0, 7, 4, 2, 2, 9]

      Equation 1: 0 + 0 + 2 + 2 = 2 * 2
      Equation 2: 0 + 7 + 2 + 9 = 2 * 9
      Equation 3: 7 + 0 = 7
      Equation 4: (0 + 7 + 2 + 9) = 14 + (0 + 0 + 2 + 2)

      
      % A Solution in MiniZinc 
      include "globals.mzn";
      
      % boy/girl categories, as above
      var 0..10:a; var 0..10:b; var 0..10:c; var 0..10:d;
      var 0..10:e; var 0..10:f; var 0..10:g; var 0..10:h;
      
      % [1] “half the children who have brothers are girls who have no sisters”
      constraint b + c + f + g == 2 * f;
      
      % [2] “half the children who have sisters are girls who have no brothers”
      constraint c + d + g + h == 2 * h;
      
      % [3] “boys who have no sisters are equal in number to boys who have sisters but not brothers”
      constraint a + b == d;
      
      % [4] “there are fourteen more children with sisters than there are children with brothers”
      constraint (c + d + g + h) = 14 + (b + c + f + g);
      
      solve satisfy;
      
      output ["[a, b, c, d, e, f, g, h] = " ++ show([a, b, c, d, e, f, g, h]) ];
      
      % [a, b, c, d, e, f, g, h] = 
      % [7, 0, 0, 7, 0, 0, 0, 7]
      % ----------
      % Many other solutions are possible.
      
      

      Like

      • Jim Randell's avatar

        Jim Randell 10:20 am on 13 December 2022 Permalink | Reply

        @Geoff:

        As you say e can be any value.

        And we can also choose any value for b and c, and any value for f that not less than (b + c).

        We then have:

        a = 7
        d = b + 7
        g = f − (b + c)
        h = f + 7

        So: a = 7; d ≥ 7; h ≥ 7.

        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