Tagged: by: W A Thatcher Toggle Comment Threads | Keyboard Shortcuts

  • Unknown's avatar

    Jim Randell 9:06 am on 15 April 2025 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 380: [Feeler gauges] 

    From The Sunday Times, 18th August 1968 [link]

    “Feeler gauges”, says Bell at the pub, “are remarkably  interesting things. You’ve all seen garage men use them — fingers of steel fastened together at one end and you can fan them out if you want. When you measure a space you find out which lot of fingers fits in. The thicknesses are marked in thous, that’s thousandths of an inch”.

    “I’ve got a set of five gauges here which will measure any number up to 13 thous, always using either a single gauge or a touching set of them. You don’t need to use gauges which aren’t touching for a measurement like you do with the usual gauges. So mine’s better. I might patent it and make a packet”.

    Clark examined it for few minutes and said “Not bad at all. I notice you could add an extra gauge in front and then your set would give all measurements up to 18 thous”.

    How would the six gauges then read starting at the top?

    This puzzle was originally published with no title.

    [teaser380]

     
    • Jim Randell's avatar

      Jim Randell 9:08 am on 15 April 2025 Permalink | Reply

      See also: Teaser 119, Teaser 560, Teaser 777, BrainTwister #61.

      I reused the code to generate sparse rulers that I wrote for Teaser 119.

      This Python 3 program runs in 81ms. (Internal runtime is 4.1ms).

      from enigma import (irange, diff, append, tuples, csum, subsets, printf)
      
      # generate sparse rulers
      # L = length
      # M = number of marks
      # ms = existing marks (set)
      # k = number of remaining marks
      # xs = distances not yet satisfied (ordered list)
      # zl = left zone
      # zr = right zone
      def rulers(L, M, ms, k, xs, zl, zr):
        if k == 0:
          if not xs:
            yield sorted(ms)
        else:
          # perform some early termination checks:
          # are there too many unsatisfied differences remaining?
          if xs and len(xs) > (k * (k - 1)) // 2 + k * (M - k): return
          # is max distance too big?
          if xs and xs[-1] > max(zr, L - zl): return
          # can we fit k marks in the gaps?
          if zr - zl + 1 < k: return
      
          # extend left zone?
          if not (zl > L - zr):
            # try with mark at zl
            ds = set(abs(m - zl) for m in ms)
            yield from rulers(L, M, append(ms, zl), k - 1, diff(xs, ds), zl + 1, zr)
            # try without mark at zl
            yield from rulers(L, M, ms, k, xs, zl + 1, zr)
          else:
            # try without mark at zr
            yield from rulers(L, M, ms, k, xs, zl, zr - 1)
            # try with mark at zr
            ds = set(abs(m - zr) for m in ms)
            yield from rulers(L, M, append(ms, zr), k - 1, diff(xs, ds), zl, zr - 1)
      
      # generate rulers that can measure 1..N
      sparse_rulers = lambda L, M, N: rulers(L, M, {0, L}, M - 2, diff(irange(1, N), [L]), 1, L - 1)
      
      # consider possible sparse rulers with length L, to measure 1 - 13
      for L in irange(13, 19):
        for ss in sparse_rulers(L, 6, 13):
          # calculate gauge thicknesses
          ts = list(b - a for (a, b) in tuples(ss, 2))
      
          # look for an additional gauge to make all thicknesses up to 18
          for x in irange(max(18 - L, 1), 17):
            ts_ = [x] + ts
            # calculate all measures
            ms_ = set(y - x for (x, y) in subsets(csum(ts_, empty=1), size=2))
            if ms_.issuperset(irange(1, 18)):
              # output solution
              printf("[1..13] = {ts} -> [1..18] = {ts_}")
      

      Solution: The six gauges are: 14, 1, 3, 6, 2, 5.

      Gauges [1, 3, 6, 2, 5] can be use to measure 1..13 (as well as 16, 17). It corresponds to a sparse ruler of length 17 with 6 marks.

      Adding a gauge of 14 we get the set [14, 1, 3, 6, 2, 5], which can measure 1..18 (as well as 24, 26, 31). Which corresponds to a sparse ruler of length 31 with 7 marks.

      Each of the gauges in the original 5-set has a different thickness, and there is another 5-set that would also work:

      [1, 7, 3, 2, 4], which can measure 1..13 (as well as 16, 17).

      But it cannot be extended to a 6-set that measures 1..18.

      Like

    • Frits's avatar

      Frits 12:08 pm on 17 April 2025 Permalink | Reply

      Assuming that the first five gauges are single digit.
      Instead of “SubstitutedExpression” also “decompose” could have been used to find five numbers that add up to “L”.

      from enigma import SubstitutedExpression
      from itertools import combinations, accumulate
      
      # can we make numbers 1 - <n> with sequence <s> using "touching" sets 
      def check(s, n):
        # calculate all measures
        s_ = set(y - x for (x, y) in combinations(accumulate([0] + s), 2))
        return s_.issuperset(range(1, n + 1))
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [ # we have to make numbers 1 - 13 with a touching set of gauges A, B, C, D, E
          "A + B + C + D < 19",
          "12 < A + B + C + D + E < 20",  # Jim: 13 <= L <= 19
          "check([A, B, C, D, E], 13)",
          
          # we have to make numbers 1 - 18 with a touching set of gauges XY, A, B, C, D, E
          "XY < 18",                  # Jim: 18 - L <= XY <= 17
          "check([XY, A, B, C, D, E], 18)"
        ],
        answer="[XY, A, B, C, D, E]",
        d2i=dict([(0, "ABCDE")] +
                 [(k, "X") for k in range(2, 10)]),
        distinct="",
        env=dict(check=check),
        reorder=0,
        verbose=0,    # use 256 to see the generated code
      )
      
      # print answers
      for ans in p.answers():
        print(f"{ans}")
      

      Like

      • Frits's avatar

        Frits 12:41 pm on 17 April 2025 Permalink | Reply

        The decomposition version (without assuming single digits).

        from itertools import combinations, accumulate
        
        # can we make numbers 1 - <n> with sequence <s> using "touching" sets 
        def check(s, n):
          # calculate all measures
          s_ = set(y - x for (x, y) in combinations(accumulate([0] + s), 2))
          return s_.issuperset(range(1, n + 1))
          
        # decompose <t> into <k> numbers from <ns> so that sum(numbers) equals <t>
        def decompose(t, k, ns, s=[]):
          if k == 1:
            if t in ns:
              yield s + [t]
          else:
            for n in ns:
              # <ns> must be a sorted list
              if n > t - (k - 1) * ns[0]: break
              yield from decompose(t - n, k - 1, ns, s + [n])
        
        # consider possible sparse rulers with length L, to measure 1 - 13
        for L in range(13, 20):
          # find five numbers that up to <L>
          for s in decompose(L, 5, range(1, L - 3)):
            # can we make numbers 1 - 13 with sequence <s> using "touching" sets
            if not check(s, 13): continue
            # look for an additional gauge to make all thicknesses up to 18
            for x in range(max(1, 18 - L), 18):
              # can we make numbers 1 - 18 using "touching" sets 
              if not check([x] + s, 18): continue
              # output solution
              print(f"[1..13] = {s} -> [1..18] = {[x] + s}")  
        

        Like

  • Unknown's avatar

    Jim Randell 4:48 pm on 2 June 2024 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 443: Space ship 

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

    Says Bell at the pub:

    I’ve been reading about a space ship. They fly the thing round playing the usual Cops and Robbers stuff, but the way they name the crew is interesting. Using A, B, C and so on right up to I, no gaps, they make, once only, every possible pair of different letters; so there’s no AA, BB and so on, and they take no notice of the order in a pair; s0 BC is the same as CB.

    Four, including AD and BC, are on a list of officers and on that list no letter appears more than once. All the rest are just proles but do they have an A initial they call themselves A proles.

    All proles work in shifts with the same number on each shift — not just two shifts; more than that — and at the end of a shift every prole hands over to another whose initials are each one farther on in the alphabet than his own, so AB would hand over to BC. Of course I is followed by A.

    The shifts are picked so that when all shifts have been worked, every prole has been on duty once only.

    Now you tell me who’s on the first shift. I want the biggest possible list. Easier than it looks. Usual prize. One pint.

    Which A proles, if any, are on the first shift?

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

    [teaser443]

     
    • Jim Randell's avatar

      Jim Randell 4:48 pm on 2 June 2024 Permalink | Reply

      A manual solution follows from a bit of analysis:

      There are C(9, 2) = 36 names, and 4 officers, so there are 32 proles, which we must form into shifts, and we want the shifts to be as large as possible (and more than 2 of them).

      So we are looking at:

      4 shifts, each of 8 proles; or:
      8 shifts, each of 4 proles

      If we start with a name we can consider the successor names until we get back to where we started, which forms the names into 4 groups:

      AB → BC → CD → DE → EF → FG → GH → HI → AI (→ AB) [adjacent letters]
      AC → BD → CE → DF → EG → FH → GI → AH → BI (→ AC) [separated by 1 letter (or 6)]
      AD → BE → CF → DG → EH → FI → AG → BH → CI (→ AD) [separated by 2 letters (or 5)]
      AE → BF → CG → DH → EI → AF → BG → CH → DI (→ AE) [separated by 3 letters (or 4)]

      Any officer (in bold) must be preceded by a member of the final shift and working backwards gives us members of previous shifts, giving a chain that is 8 long, which consists of 1 member for each of 8 shifts, or 2 members for each of 4 shifts.

      Each officer must appear in a different group, and as we have used ABCD, we must choose 2 officers from EFGHI.

      In the final group only EI is available to be an officer, so the remaining officer must be FH in the second group.

      And so we can construct 4 shifts, each of 8 proles:

      shift 4: (AB, EG, CI, DH) + (FG, AC, EH, DI)
      shift 3: (AI, DF, BH, CG) + (EF, BI, DG, CH)
      shift 2: (HI, CE, AG, BF) + (DE, AH, CF, BG)
      shift 1: (GH, BD, FI, AE) + (CD, GI, BE, AF)

      (And the two halves could be split to make 8 shifts, each of 4 proles = (1b, 2b, 3b, 4b, 1a, 2a, 3a, 4a)).

      Solution: AE and AF are on the first shift.


      Here is a program that performs the same steps:

      It runs in 64ms. (Internal runtime is 351µs).

      Run: [ @replit ]

      from enigma import (
        irange, subsets, diff, tuples, repeat, join,
        intersect, disjoint_union, cache, printf
      )
      
      # letters
      letters = "ABCDEFGHI"
      
      # construct possible crew names
      names = list(x + y for (x, y) in subsets(letters, size=2))
      
      # constrict successor names
      adj = dict(tuples(letters, 2, circular=1))
      succ = cache(lambda xs: join((adj[x] for x in xs), sort=1))
      
      # group names into successor chains
      def group(names):
        while names:
          # make the chain for the next name
          g = list(repeat(succ, names[0], 8))
          yield g
          names = diff(names, g)
      
      # collect groups
      gs = list(group(names))
      
      # choose an officer from each group
      def officers(gs, offs=[]):
        # are we done?
        if not gs:
          yield offs
        else:
          g = gs[0]
          # AD and BC are officers
          xs = intersect([{'AD', 'BC'}, g])
          if not xs:
            # choose officers that don't include ABCD
            xs = list(x for x in g if not intersect(['ABCD', x]))
          # process the candidates
          for x in xs:
            offs_ = offs + [x]
            if disjoint_union(offs_):
              yield from officers(gs[1:], offs_)
      
      # choose the officers
      for offs in officers(gs):
        # find the first shift
        shift = set()
        for (g, off) in zip(gs, offs):
          j = g.index(off)
          shift.update(g[(j + i) % 9] for i in [-4, -8])
        shift = sorted(shift)
        # output shifts 1-4
        for k in irange(1, 4):
          printf("shift {k} = {shift}", shift=join(shift, sep=" "))
          if k == 4: break
          # hand on to the next shift
          shift = list(succ(x) for x in shift)
      

      Like

  • Unknown's avatar

    Jim Randell 10:21 am on 2 May 2024 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 553: Grand Vizier 

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

    Sever-Nek, the Grand Vizier, was suspicious of the influence which Azi-Muth. the Sultan’s wily tutor, was gaining over his young pupil, and had determined to have him removed, when chance played into his hands.

    The Sultan had a collection of nearly 2,000 small square tiles, all the same size in various colours, and one day to please his tutor had arranged some of them into a square with a pretty central design and a border. Then he announced that he had made two smaller equal squares using the same pieces. “But that is impossible, said Muth, “there must be some left over”.

    There was one over, and Sever-Nek, happening to pass by put his foot over it. “Surely not”, he said. “His Serene Highness has made a most interesting discovery. No doubt the same thing can be done again. Let us add some extra tiles from the box to those already used”. Nothing loath, the Sultan soon produced a larger square from which he almost made two smaller identical squares. This time he was one tile short.

    “Your Serene Highness must have dropped this one on the floor”, said the Grand Vizier, moving his foot. Allow me to complete the design”. The discomfiture of the outwitted tutor was no less complete than his disgrace.

    How many extra tiles were taken from the box to make the larger square?

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

    [teaser553]

     
    • Jim Randell's avatar

      Jim Randell 10:22 am on 2 May 2024 Permalink | Reply

      We can suppose the requirement for a “pretty central design” is to preclude very small squares. I assumed the initial square was at least 5×5.

      This Python program runs in 62ms. (Internal runtime is 399µs).

      Run: [ @replit ]

      from enigma import (irange, inf, sq, is_square, printf)
      
      # record (x, y) where x^2 = 2 y^2 + d for d in [+1, -1]
      ss = { +1: [], -1: [] }
      
      for j in irange(2, inf):
        n = sq(j)
        if n > 1000: break
      
        # look for deltas
        for d in ss.keys():
          i = is_square(2*n + d)
          if i:
            printf("[{d:+d}] {i}^2 = 2x {j}^2 {d:+d}")
            ss[d].append((i, j))
      
      # look for an initial +1, with a size at least 5
      for (x1, y1) in ss[+1]:
        if x1 < 5: continue
        # and a larger -1
        for (x2, y2) in ss[-1]:
          if not (x2 > x1): continue
          # calculate the number of extra tiles (remembering one is hidden)
          d = sq(x2) - sq(x1) + 1
          # output solution
          printf("{d} extra tiles [{x1}^2 = 2 {y1}^2 + 1 -> {x2}^2 = 2 {y2}^2 - 1]")
      

      Solution: 1393 extra tiles were taken to make the larger square.

      The initial square was 17×17 (= 289 tiles), which can be rearranged as two 12×12 squares (= 144 tiles each) with one tile left over (and hidden).

      With an extra 1393 tiles we get a total of 288 + 1393 = 1681 which can be arranged into a 41×41 square.

      The 1681 tiles can also be arranged into two 29×29 squares (= 841 tiles each), except one of them is one tile short, and completed by the hidden tile.


      If a small initial square is allowed then there is a further solution, starting with a 3×3 square (9 tiles), this is rearranged into two 2×2 squares (8 tiles each), with one tile left over. We can then add 41 extra tiles to make a 7×7 square which can split into two 5×5 squares, one of which requires the hidden tile. (Or an extra 1673 tiles could be added to make two 29×29 squares, as above).

      Like

    • John Crabtree's avatar

      John Crabtree 6:13 pm on 2 May 2024 Permalink | Reply

      The sides of the larger and smaller squares are given in the sequences
      https://oeis.org/A001333 Numerators of continued fraction convergents to sqrt(2), and
      https://oeis.org/A001329 Denominators of continued fraction convergents to sqrt(2).
      Then all one needs to do is to find the side of the biggest larger square < sqrt(2000), ie 41, and work from there.

      Like

  • Unknown's avatar

    Jim Randell 5:21 pm on 6 August 2023 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 606: Pints all round 

    From The Sunday Times, 18th February 1973 [link]

    “Little puzzle here”, says Bell at the pub, squeezing himself in. “Move up. Now, we are seven sat round the table. I am No. 1, naturally, and you are 2, 3, up to 7 and back to No. 1 clockwise”.

    “These cards numbered 1 to 7. I shuffle and deal. If you have your own number, say ‘Hit!’. Only one hit? Good! Pass the cards to your left one place. Double hit? Bad”.

    “The trick is to find out how the cards should be dealt for a single hit each time the cards are passed. I’ll make it easy. I always get the first hit and then numbers 2 and 6 get their hits as early as possible after that. Everything’s clockwise, numbering, dealing and passing”.

    “Usual prize one pint”.

    “Difficult? It’s a pushover. If you’re thirsty I’ll give you a hint. Let’s have a look at your cards. Anybody can see number 6 is going to come up last. Swap them round a bit. Hold on! Now you’ve got 5 then 2 then 7, so when 5 gets a hit so does 7. You’ve got to avoid that kind of thing”.

    In what order should the cards be dealt, beginning with No. 1 and proceeding clockwise?

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

    [teaser606]

     
    • Jim Randell's avatar

      Jim Randell 5:22 pm on 6 August 2023 Permalink | Reply

      This Python program considers all possible arrangements of cards, and runs the process looking for a single hit on each turn. And it reports those with 7 turns that each give a single hits, and the first hit is 1. And I look for cases where the index sum of cards 2 and 6 in minimised.

      It runs in 63ms. (Internal runtime is 11.2ms).

      Run: [ @replit ]

      from enigma import (Accumulator, irange, rotate, subsets, singleton, printf)
      
      # find sequences of single hits
      def hits(ss):
        hs = list()
        while True:
          # we want a single hit on each turn
          h = singleton(x for (x, y) in enumerate(ss, start=1) if x == y)
          if h is None: break
          hs.append(h)
          if len(hs) == len(ss): break
          # pass the cards to the left
          ss = rotate(ss, -1)
        # return the order of the hits
        return hs
      
      r = Accumulator(fn=min, collect=1)
      
      # consider possible arrangements of the cards
      for ss in subsets(irange(1, 7), size=len, select='P'):
        hs = hits(ss)
        # single hit each time
        if not (len(hs) == 7): continue
        # the first hit is 1
        if not (hs[0] == 1): continue
        # accumulate values by positions for 2 and 6
        r.accumulate_data(hs.index(2) + hs.index(6), ss)
      
      # output solution
      for ss in r.data:
        printf("{ss}")
      

      Solution: The cards should be dealt in the order: 1, 5, 7, 3, 6, 4, 2.

      Like

  • Unknown's avatar

    Jim Randell 11:09 am on 11 June 2023 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 276: Electing a Chairman 

    From The Sunday Times, 14th August 1966 [link]

    The ten directors of the Everlasting Mutual Trust Company, Chairman (C), Secretary (S), Treasurer (T), Directors 1, 2, 3, 4, 5, 6, and Vice-Chairman (V), sat in that order to elect a new chairman.

    They all got one vote. No director voted for himself, or for his neighbour, or for the man who voted for him. No officer voted for an officer. C voted for the man who voted for the man who voted for 2, i.e. C vote vote vote 2 for short; and T vote vote vote 4, and V vote vote vote 5, and S vote vote vote 6. Director 4 did not vote for an officer. Neither C nor S voted for 3. The man getting a vote from 6 did not vote for 3.

    For whom did C, S, T, 1, 2, 3, 4, 5, 6, V respectively vote?

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

    [teaser276]

     
    • Jim Randell's avatar

      Jim Randell 11:09 am on 11 June 2023 Permalink | Reply

      Presumably each of them cast one vote, and received one vote.

      We can write “X vote vote vote Y” even more compactly as “X vote^3 Y”.

      This Python 3 program generates possible voting patterns, taking direct voting restrictions into account, and then checks the restrictions on voting chains.

      In Python 3.7+ dict() objects remember their insertion order, so the output should be in the required order.

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

      Run: [ @replit ]

      from enigma import (tuples, remove, update, map2str, printf)
      
      # multiple lookups on dict <d>
      def nget(d, k, n):
        while n > 0:
          k = d[k]
          n -= 1
        return k
      
      # the voters
      voters = "CST123456V"
      
      # determine invalid votes:
      invalid = dict((k, set()) for k in voters)
      # no-one votes for themselves
      for k in voters:
        invalid[k].add(k)
      # no-one votes for their neighbours
      for (x, y, z) in tuples(voters, 3, circular=1):
        invalid[y].update((x, z))
      # officers (CSTV) don't vote for other officers
      officers = "CSTV"
      for k in officers:
        invalid[k].update(officers)
      
      # generate possible votes
      # return <m>, map of <voter> -> <vote>
      def votes(ks, vs, m=dict()):
        # are we done?
        if not ks:
          yield m
        else:
          k = ks[0]
          xs = invalid[k]
          for v in vs:
            # reject invalid votes and reciprocal votes
            if v not in xs and (v not in m or m[v] != k):
              yield from votes(ks[1:], remove(vs, v), update(m, [(k, v)]))
      
      # 4 did not vote for an officer
      invalid['4'].update(officers)
      # neither C nor S voted for 3
      invalid['C'].add('3')
      invalid['S'].add('3')
      
      # who votes for who?
      for v in votes(voters, set(voters)):
        # check vote chains:
        # the man getting a vote from 6 did not vote for 3
        if nget(v, '6', 2) == '3': continue
        # C vote^3 2
        if nget(v, 'C', 3) != '2': continue
        # T vote^3 4
        if nget(v, 'T', 3) != '4': continue
        # V vote^3 5
        if nget(v, 'V', 3) != '5': continue
        # S vote^3 6
        if nget(v, 'S', 3) != '6': continue
        # output solution
        printf("{v}", v=map2str(v, sort=0))
      

      Solution: The votes are: C→6, S→2, T→3, 1→5, 2→C, 3→V, 4→1, 5→T, 6→S, V→4.

      Or as cycles:

      C → 6 → S → 2 (→ C)
      T → 3 → V → 4 → 1 → 5 (→ T)

      Like

    • Frits's avatar

      Frits 4:35 pm on 11 June 2023 Permalink | Reply

       
      from enigma import SubstitutedExpression
      
      # invalid digit / symbol assignments
      d2i = dict()
      for d in range(0, 10):
        vs = set()
        # no officer voted for an officer
        # director 4 did not vote for an officer 
        
        if d < 1 or d > 6: vs.update('NCSTV')
        else:  # 1 <= d <= 6
          # no director voted for himself or for his neighbour ..
          vs.update("KLMNOP"[max(0, d - 2):d + 1])
        
        if d == 0:
          vs.update("K")
        
        if d == 7:
          vs.update("P")
        
        # neither C nor S voted for 3
        if d == 3:
          vs.update("CS")   
        
        d2i[d] = vs
      
      # return index of person in sequence {seq> who voted <n>
      voted = lambda seq, n: seq.index(n)
      # return the man who voted for the man who voted for <n>
      vote3 = lambda seq, n: voted(seq, voted(seq, n))
      
      # officers CSTV and directors KLMNOP
      # VKLMNOPCST
      # 0123456789
      
      # the alphametic puzzle
      p = SubstitutedExpression(
        [
         # choose assignment to T as C and S have fewer candidate values
         # T vote^3 4
         "vote3([V, K, L, M, N, O, P, C, S, -1], 4) = T",
      
         # the man getting a vote from 6 did not vote for 3
         "(r := [V, K, L, M, N, O, P, C, S, T])[P] != 3",
         
         # C vote^3 2 (C voted for the man who voted for the man who voted for 2)
         "vote3(r, 2) == C",
         # V vote^3 5
         "vote3(r, 5) == V",
         # S vote^3 6
         "vote3(r, 6) == S",
         
         # no director voted for the man who voted for him
         "all(x != voted(r, i) for i, x in enumerate([K, L, M, N, O, P], 1))",
        ],
        answer="[C, S, T, K, L, M, N, O, P, V]",
        d2i=d2i,
        env=dict(vote3=vote3, voted=voted),
        reorder=0,
        verbose=0,    # use 256 to see the generated code
      )
      
      # print answers
      for (_, ans) in p.solve():
        print(f"answer: {ans}")
      

      Like

  • Unknown's avatar

    Jim Randell 11:05 am on 18 December 2022 Permalink | Reply
    Tags: by: W A Thatcher   

    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 8:51 am on 4 December 2022 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 252: Blocks 

    From The Sunday Times, 27th February 1966 [link]

    “See these eleven blocks?”, says a so-called friend. “Four of them of 8 inch thickness, two of them of 4 inch thickness, three of 3 inch and two of 1 inch thickness”.

    “Pile them in a column 51 inches high with a 3 inch block at the bottom so that, remaining in position, individual blocks or combinations of adjacent blocks can be used to measure every thickness in exact inches from 1 inch to 48 inches”.

    In what order do they stand?

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

    There are now 790 Teaser puzzles available on the site. And this accounts for 25% of all the Teaser puzzles published (to date).

    [teaser252]

     
    • Jim Randell's avatar

      Jim Randell 8:54 am on 4 December 2022 Permalink | Reply

      (See also: Teaser 119, Teaser 560).

      This Python program performs an exhaustive search of all possible stacks of blocks.

      It runs in 268ms.

      Run: [ @replit ]

      from enigma import (multiset, subsets, csum, irange, printf)
      
      # the blocks
      blocks = multiset.from_dict({8: 4, 4: 2, 3: 3, 1: 2})
      assert blocks.sum() == 51
      
      # extract a 3 block
      blocks.remove(3)
      
      # and consider possible arrangements of the remaining blocks
      for bs in subsets(blocks, size=len, select="mP", fn=list):
        bs.insert(0, 3)
        # find what thicknesses can be measured
        ts = set(b - a for (a, b) in subsets(csum(bs, empty=1), size=2))
        # can we measure all values from 1 to 48
        if all(x in ts for x in irange(1, 48)):
          printf("{bs}")
      

      Solution: The pile of blocks (bottom to top) is one of the following two sequences:

      (3, 4, 3, 8, 8, 8, 8, 4, 1, 1, 3)
      (3, 1, 1, 4, 8, 8, 8, 8, 3, 4, 3)

      One being the reverse of the other.

      Like

  • Unknown's avatar

    Jim Randell 9:47 am on 27 November 2022 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 222: British triangles 

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

    British Triangles, naturally, stand on horizontal bases with their points upwards. All their sides, never more than a  sensible 60 inches, and their heights measure an exact number of inches. No B.T. is isosceles or right angled.

    You can often put more than one B.T. on a common base. On a base of 28 inches 8 B.T.s are erected.

    What are their heights?

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

    [teaser222]

     
    • Jim Randell's avatar

      Jim Randell 9:47 am on 27 November 2022 Permalink | Reply

      If the 3 sides of a triangle are a, b, c, then the area A is given by Heron’s formula:

      s = (a + b + c) / 2
      A = √(s(s − a)(s − b)(s − c))

      And if the height (from side a) is h, then we also have:

      A = ha / 2

      Hence the height h can be determined from the sides a, b, c:

      h = 2A / a
      h = (2/a)√(s(s − a)(s − b)(s − c))

      This Python program considers possible values for the 2nd and 3rd sides of the triangle, and then looks for values where the height is an integer.

      It runs in 53ms. (Internal runtime is 1.8ms).

      Run: [ @replit ]

      from enigma import (irange, div, is_square, subsets, sq, printf)
      
      # check for a viable triangle
      def is_triangle(a, b, c):
        # check triangle inequalities
        if not (a + b > c and a + c > b and b + c > a): return
        # no triangle is isosceles
        if len({a, b, c}) < 3: return
        # no triangle is right angled
        sqs = (sq(a), sq(b), sq(c))
        if sum(sqs) == 2 * max(sqs): return
        # looks OK
        return (a, b, c)
      
      # calculate integer height from side a
      def height(a, b, c):
        p = a + b + c
        x = div(p * (p - 2 * a) * (p - 2 * b) * (p - 2 * c), 4)
        if not x: return
        x = is_square(x)
        if not x: return
        h = div(x, a)
        return h
      
      # base
      a = 28
      # consider possible integer length for the remaining sides, b, c
      for (b, c) in subsets(irange(1, 60), size=2, select="R"):
        if not is_triangle(a, b, c): continue
        # calculate the height of the triangle
        h = height(a, b, c)
        if not h: continue
        # output viable triangles
        printf("a={a} b={b} c={c} -> h={h}")
      

      Solution: The heights of the triangles are 9″ (2 triangles), 15″ (4 triangles), 24″ (2 triangles).

      The four triangles found by the program are shown below. And they can be mirrored to produce the remaining four triangles.

      Like

    • Hugh Casement's avatar

      Hugh Casement 7:41 am on 28 November 2022 Permalink | Reply

      Alternatively we can think of it as two right-angled triangles joined together.
      If their bases are d and e, then d² = b² – h² and e² = c² – h².
      d + e, or |d – e| in the case of the obtuse-angled triangles, must equal a = 28.
      b must not equal c, for then the overall triangle would be isosceles
      (that is the case when h = 48, b = c = 50, and d = e = 14).

      Like

      • Hugh Casement's avatar

        Hugh Casement 9:09 am on 28 November 2022 Permalink | Reply

        Joined together along the line of their common height h, of course.
        A base a = 21 also allows eight resultant triangles, including reflections.

        Like

  • Unknown's avatar

    Jim Randell 10:53 am on 25 September 2022 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 418: Bell’s weights 

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

    “Now”, says Bell at the pub, “look intelligent and imaginative even if you don’t look beautiful by any means”. We swallow the insult and look solemn. Bell likes his jokes and we like his puzzles.

    “Imagine you have some scales, but only three weights. However, you have a heap of Grade A sand, and a couple of bags; so you make two extra weights, one as heavy as all the first three put together, and the other twice as heavy as all the first three. Whereupon all the remaining sand is removed to a great distance”.

    “With these five weights you must be able to weigh 1 ounce, 2 ounces, 3, 4, and so on, as far as possible. No gaps in that lot. It’s how far you can to the first gap I’m after. Usual prize — one pint for the best score before closing time”.

    What should Bell’s five weights be to give the highest possible score?

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

    [teaser418]

     
    • Jim Randell's avatar

      Jim Randell 10:54 am on 25 September 2022 Permalink | Reply

      This Python program considers increasing values for the total of the first 3 weights, from 3 to 40.

      It runs in 351ms.

      Run: [ @replit ]

      from enigma import (irange, inf, decompose, subsets, peek, Accumulator, printf)
      
      def unreachable(ws):
        # collect possible weights
        vs = set()
        # choose multipliers for each weight
        for ms in subsets((1, 0, -1), size=len(ws), select="M"):
          v = sum(m * w for (m, w) in zip(ms, ws))
          if v > 0: vs.add(v)
        # find the first unreachable value
        return peek(v for v in irange(1, inf) if v not in vs)
      
      # record maximum unreachable weight
      r = Accumulator(fn=max, collect=1)
      
      # consider t = a + b + c
      for t in irange(3, 40):
        for (a, b, c) in decompose(t, 3, increasing=1, sep=0, min_v=1):
          # calculate the first unreachable value
          ws = (a, b, c, t, 2 * t)
          v = unreachable(ws)
          r.accumulate_data(v, ws)
          if v == r.value: printf("[t={t}: {ws} -> unreachble = {v}]")
      
      printf("values = 1 .. {x}", x=r.value - 1)
      for ws in r.data:
        printf("-> {ws}")
      

      Solution: The 5 weights are: 4, 6, 9, 19, 38.

      And this set of weights can be used to weigh all values from 1 to 64.


      Using a set of balancing scales each weight can go in the left pan, right pan, or neither.

      For for n weights there are 3^n possibilities.

      But these include, not using any weights (all in neither pan), and each combination has a mirror image.

      So the total maximum possible number of different weights would be:

      W(n) = (3^n − 1) / 2

      For 5 weights we have: W(5) = 121, and using a set consisting of increasing powers of 3 we can achieve this maximum and weigh all values between 1 and 121:

      1, 3, 9, 27, 81

      But for a set of the form:

      (a, b, c, a + b + c, 2(a + b + c))

      there are 70 different arrangements. So the maximum number of different values we could achieve would be no more than this. And we can use the program to perform an exhaustive check up to this total, but there are no better solutions.

      Like

  • Unknown's avatar

    Jim Randell 10:09 am on 18 September 2022 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 560: Ribbon counter 

    From The Sunday Times, 26th March 1972 [link]

    “Puzzle here”, says Bell at the pub. “Chap has a ribbon shop, sells the stuff by the inch, no commercial sense. He’s barmy anyway; look how he measures it. His counter is exactly 100 inches long and he’s marked it off into 16 bits; 6 of 11 inches, 2 of 6 inches, 3 of 5 inches, 1 of 3 inches and 4 of 1 inch, and he can measure any number of inches up to a hundred, that is, by picking the right pair of marks.

    “You have to sort the spaces out; but I’ll tell you, all the 11 inches are together round about the middle — well, a bit to the right, but not as much as 4 inches off centre. You get the idea? For most measurements he’s using a kind of feet and inches with eleven inches to the foot”.

    “Young Green is nearly right: he can’t measure 99 inches unless there’s a 1-inch space at one end, but he doesn’t need a 1-inch at the other end for 98 inches; he does it with two 1-inch spaces at the same end; but there might be a 1-inch at the other end, all the same, and there might not”.

    “In answer to two foolish questions, the ribbon must be measured single thickness, no folding; and it’s a straight counter, it’s not circular”.

    “Usual prize, one pint”.

    How were the spaces arranged from left to right?

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

    [teaser560]

     
    • Jim Randell's avatar

      Jim Randell 10:10 am on 18 September 2022 Permalink | Reply

      The puzzle is describing a sparse ruler of length 100 with 17 marks. (See: Teaser 119).

      However, in this case we are told that one end of the ruler has two 1-segments (lets put them at the start), and the six 11-segments all occur together in the middle-ish (displaced slightly to the right, but not by more than 3 inches), so we can look for arrangements of the remaining segments that give a viable ruler.

      This Python program runs in 104ms. (Internal runtime is 49ms).

      Run: [ @replit ]

      from enigma import (multiset, subsets, csum, reverse, printf)
      
      # segment groups we are given
      ss1 = (1, 1)  # start with (1, 1)
      ss2 = (11,) * 6  # 11s are in the middle
      
      # remaining segments
      segs = multiset.from_dict({ 6: 2, 5: 3, 3: 1, 1: 2 })
      n = segs.size()
      
      # consider the remaining segments
      for rs in subsets(segs, size=n, select="mP"):
        # accumulate sufficient segments to place the 11s close to the middle
        (t, i) = (rs[0] + 35, 1)
        while True:
          t += rs[i]
          i += 1
          if t < 47 or t == 50: continue
          if t > 53: break
          # construct the sequence of segments
          ss = ss1 + rs[:i] + ss2 + rs[i:]
          # construct the sequence of marks
          ms = list(csum(ss, empty=1))
          # check all 100 differences are achievable
          if len(set(b - a for (a, b) in subsets(ms, size=2))) == 100:
            # do we want the mirror image?
            if t < 50: (ss, ms) = (reverse(ss), reverse(ms))
            # output segments and marks
            printf("{ss} -> {ms}")
      

      Solution: The segments are as follows:

      (1, 1, 3, 5, 5, 5, 11, 11, 11, 11, 11, 11, 6, 6, 1, 1)

      The centre of the 11’s is 53 inches from the left edge.


      Using the code I wrote for Teaser 119 we find there are only 2 sparse rulers of length 100 with 17 marks (and it is not possible to construct a length 100 ruler with fewer marks):

      (0, 1, 2, 5, 10, 15, 20, 31, 42, 53, 64, 75, 86, 92, 98, 99, 100)
      (0, 1, 2, 8, 14, 25, 36, 47, 58, 69, 80, 85, 90, 95, 98, 99, 100)

      {+1^2, +3, +5^3, +11^6, +6^2, +1^2}
      {+1^2, +6^2, +11^6, +5^3, +3, +1^2}

      The second being the mirror image of the first (which is clear from the representation in difference format).

      Like

      • Frits's avatar

        Frits 11:38 am on 19 September 2022 Permalink | Reply

        @Jim, you still have to put the latest version of enigma.py to the magwag site.

        Like

        • Jim Randell's avatar

          Jim Randell 3:57 pm on 19 September 2022 Permalink | Reply

          I’ve uploaded enigma.py version 2022-09-17, which has all my recent changes in it.

          The most interesting change is that SubstitutedExpression.split_sum() can now take multiple simultaneous sums to split. In general it is now faster to use split_sum() than it is to use the specialised SubstitutedSum solver.

          Like

    • Frits's avatar

      Frits 3:15 pm on 19 September 2022 Permalink | Reply

      Similar reusing parts of Jim’s code.

         
      from itertools import permutations
      
      # put bits (1, 1) up front 
      
      # list of numbers to use besides (1, 1) and (11, 11, 11, 11, 11, 11)
      bits = (1, 1, 3, 5, 5, 5, 6, 6)
      
      # segment groups we are given
      ss1 = (1, 1)     # start with (1, 1)
      ss2 = (11,) * 6  # 11s are in the middle
      
      ps = set()
      target = list(range(1, 101))
      
      for p in permutations(bits):
        if p in ps: continue
        # determine where to put the six 11s
        t = 2    # first 2 bits
        for i, b in enumerate(p):
          t += b
          # 1 to 3 inches off centre (to the right) meaning 51 - 53
          if t < 18: continue
          if t > 20: break
          
          # construct the sequence of segments
          ss = ss1 + p[:i+1] + ss2 + p[i+1:]
          
          # collect lenghts of all possible consecutive bits
          ls = set(sum(ss[i:j]) for i in range(len(ss) - 1) 
                   for j in range(i + 1, len(ss) + 1))
          
          if sorted(ls) == target:
            print(ss)
        ps.add(p)
      

      Like

      • Frits's avatar

        Frits 3:50 pm on 19 September 2022 Permalink | Reply

        This only handles the situation if the two 1-segments appear at the start (at the left).

        Like

  • Unknown's avatar

    Jim Randell 8:36 am on 11 September 2022 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 119: Weights 

    From The Sunday Times, 7th July 1963 [link]

    In the time of the Great Caliph a large annual tax was one day levied on shopkeepers for each weight used in their shops. The ingenious Al Gebra contrived to use very few weights but he often had weights in both his scale pans. The exchequer hit back by making it compulsory to make every weighing by using two weights only, one in each pan. Al now contrived with 20 weights to weigh up to 118 lb. in 1 lb. steps. Using 1 lb. as the least weight he found various ways of doing this. “But”, he said, “I’m getting old and I’m going to have the lightest possible set”.

    Which set was this?

    [teaser119]

     
    • Jim Randell's avatar

      Jim Randell 8:38 am on 11 September 2022 Permalink | Reply

      This puzzle was published with a note that the previous weeks puzzle, Teaser 118 (also involving finding a set of weights), had solicited “no correct solution”.

      However, as we found, the published solution for Teaser 118 is incorrect.

      In this puzzle we are also asked to find a set of weights, and the originally published solution is also incorrect (although a correction was later made with one optimal set of weights, although it turns out this set is not unique).


      If the lightest weight is 1 lb, then the heaviest weight we need is 119 lb (to create a difference of 119 − 1 = 118), and the remaining 18 weights can fit in between.

      This is equivalent to constructing a sparse ruler [@wikipedia] of length 118, with 20 marks.

      And as we want to cover all differences 1 … 118, the ruler must be complete.

      I started by writing a program to find complete sparse rulers, and then adapted it to produce rulers with a minimal total sum for the set of marks, and from these we construct sets of weights to solve the puzzle.

      For a set of 20 weights weighing values 1 … 118 the program takes 29m28s to find the 5 optimal solutions (although the solutions are found much faster than this (in 5.4s), the rest of the time is spent completing the search to ensure there are no better solutions).

      from enigma import (irange, append, diff, empty, reverse, Accumulator, arg, printf)
      
      # generate sparse rulers
      # L = length
      # M = number of marks
      # ms = existing marks (as a set)
      # k = number of remaining marks
      # xs = distances not yet satisfied (as an ordered list)
      # zl = left zone
      # zr = right zone
      # r = Accumulator (on the sum of the marks)
      def rulers(L, M, ms, k, xs, zl, zr, r):
        if k == 0:
          if not xs:
            ms = sorted(ms)
            t = sum(ms)
            # is the mirror image better?
            if L * M < 2 * t:
              ms = reverse(L - x for x in ms)
              t = L * M - t
            # record the value
            r.accumulate_data(t, ms)
            if r.value == t: printf("[{ms} -> {t}]")
        else:
          # perform some early termination checks:
          # are there too many unsatisfied differences remaining?
          if xs and len(xs) > (k * (k - 1)) // 2 + k * (M - k): return
          # is max distance too big?
          if xs and xs[-1] > max(zr, L - zl): return
          # will the additional marks exceed the current best?
          if r.value and sum(ms) + sum(irange(zl, zl + k - 1)) > r.value: return
          # can we fit k marks in the gaps?
          if zr - zl + 1 < k: return
      
          # extend left zone?
          if not (zl > L - zr):
            # try with mark at zl
            ds = set(abs(m - zl) for m in ms)
            rulers(L, M, append(ms, zl), k - 1, diff(xs, ds), zl + 1, zr, r)
            # try without mark at zl
            rulers(L, M, ms, k, xs, zl + 1, zr, r)
          else:
            # try without mark at zr
            rulers(L, M, ms, k, xs, zl, zr - 1, r)
            # try with mark at zr
            ds = set(abs(m - zr) for m in ms)
            rulers(L, M, append(ms, zr), k - 1, diff(xs, ds), zl, zr - 1, r)
      
      # generate complete rulers
      complete_rulers = lambda L, M, r: rulers(L, M, {0, L}, M - 2, list(irange(1, L - 1)), 1, L - 1, r)
      
      # ruler length and marks
      L = arg(118, 0, int)
      M = arg(20, 1, int)
      printf("[L={L} M={M}]")
      
      # generate complete rulers with length L, and M marks, and recorded minimal total
      r = Accumulator(fn=min, collect=1)
      complete_rulers(L, M, r)
      
      # use optimal rulers
      printf("best = {r.value}")
      for ss in r.data:
        printf("-> ruler = {ss}")
        # make the weights
        ws = list(x + 1 for x in ss)
        printf("-> weights = {ws}; total = {t}", t=sum(ws))
      

      Solution: There are five possible sets of weights, each set weighs 712 lb:

      size = 20: sum = 712
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 35, 48, 60, 73, 84, 96, 108, 119)
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 35, 49, 59, 73, 84, 96, 108, 119)
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 36, 47, 60, 72, 85, 96, 108, 119)
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 36, 48, 60, 71, 85, 96, 108, 119)
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 37, 47, 59, 72, 85, 96, 108, 119)

      The originally published solution (with Teaser 120) is:

      size = 20: sum = 759
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 21, 32, 43, 54, 65, 76, 87, 98, 109, 119)

      which is not optimal.

      But this was later revised (with Teaser 122) to:

      size = 20: sum = 712
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 23, 36, 47, 60, 72, 85, 96, 108, 119)

      which is the third of the optimal sets I give above.


      The puzzle says the tax is levied for each weight, so it may be preferable to use fewer weights.

      Each pair of weights corresponds to potential actual value, and C(15, 2) = 105, does not give enough pairs. But C(16, 2) = 120 does. So it is potentially possible that a set of 16 weights could be used to achieve our goal of weighing each of the weights 1 … 118.

      I checked sets of 16, 17, 18 (that include a 1 lb weight), but didn’t find any viable sets.

      But for a set of 19 weights I found:

      size = 19: sum = 901
      (1, 2, 3, 6, 7, 9, 12, 13, 18, 31, 44, 57, 70, 83, 96, 103, 110, 117, 119)

      This is heavier than the optimal solution sets with 20 weights (sum = 712), but if the tax per weight was high it may be preferable.

      However, by using more weights we can come up with a lighter set:

      Here are the optimal sets of size 21 to 25:

      size = 21: sum = 629
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 29, 45, 60, 76, 90, 105, 119)
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 29, 46, 59, 76, 90, 105, 119)

      size = 22: sum = 634
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 31, 42, 57, 73, 88, 104, 119)

      size = 23: sum = 609
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 35, 49, 67, 84, 102, 119)

      size = 24: sum = 615
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 29, 47, 65, 83, 101, 119)

      size = 25: sum = 605
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 39, 59, 79, 99, 119)
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 41, 58, 78, 99, 119)

      after which the total weight starts going up again.

      So the lightest set of weights is one of the sets of size 25.

      But presumably, the intricacies of the tax system have led Al Gebra to conclude that the extra weight for an optimal set of 20 weights is offset by the difference in tax on the lighter set of 25 weights.

      Like

    • Frits's avatar

      Frits 2:08 pm on 11 September 2022 Permalink | Reply

      The numbers 20 and 118 were probably chosen as it leads to a solution which it is relatively easy to check for completeness.

       
      in this case we can formulate L + 1 as (n + 1) * n + n - 1 where n = M / 2
      
           1-n       n+1 - 2n   2n+2 - 3n+1   (n-1)n+n-1 - n*n+n-2    
      1,2,3,..., n ,   2n+1,       3n+2, ....... ,  n*n+n-1,      (n+1)n+n-1  
      
      We can already start with minimum n*(n+1)//2 + ((n+2)*(n+1)//2 - 1)*n + n*(n+1)//2 - 1 = M**3 / 16 + 5*M**2 / 8 + M / 2 - 1 = 759 ( originally published solution)
      

      Like

    • Frits's avatar

      Frits 12:19 pm on 12 September 2022 Permalink | Reply

      This program runs in 11.5 seconds with PyPY (with inc set to 3).

      I am not sure if we can rely on the weights always to contain number 1 so we have to see this program as a way to get a low total weight.

        
      from itertools import combinations
      
      M = 20
      L = 118
      
      # can we make number 1,...,N with numbers from <seq>
      def check(seq):
        s = set()
        for c1, c2 in combinations(seq, 2):
          d = c2 - c1
          if 0 < d < L + 1 and d not in s:     
            s.add(d)
            if len(s) == L: 
              return True
        return False
        
      # pick one value from each entry of a <k>-dimensional list <ns>
      def pickOneFromEach(k, ns, s=[]):
        if k == 0:
          yield s
        else:
          for n in ns[k - 1]:
            yield from pickOneFromEach(k - 1, ns, s + [n])
        
      n = 10
      M = 20
      break_next = 0
      
      # assume the first sequence of consecutive weights is 1,2,3, ..,m
      # then the last 2 weight must be L + 1 - m, L + 1
      # we have to choose M - m - 2 weights between m and L + 1 - m
      best = 99999
      inc = 3           # allowed deviance from calculated middle value
      for m in range(17, 0, -1):
        print()
        print("suppose the first consecutive weights are", list(range(1, m + 1)))
        print("then the last 2 weight must be", [L + 1 - m, L + 1])
        print("we have to choose", M - m - 2, "weights between", m, "and", 
              L + 1 - m, "with", M - m - 1, "intervals")
        
        # calculate groups of allowed values per missing weight
        ws = [[m + i + x * (L + 1 - 2 * m ) // (M - m - 1) 
                  for i in range(-inc, inc + 1)] for x in range(1, M - m - 1)]
        print("choose weights from", *ws)
        
        for p in pickOneFromEach(M - m - 2, ws):
          # rebuild full set of weights
          ns = list(range(1, m + 1)) + sorted(p) + [L + 1 - m, L + 1]
          if sum(ns) > best: continue
          # check if we can make all numbers 1 - 118
          if not check(ns): continue
            
          best = sum(ns)
          print(ns, "with total weight", sum(ns))
       
        if break_next: break
          
        if best < 99999:
          # we assume that a lower <m> results in a higher total weight
          # perform one extra run with a lower <m>  anyway
          break_next = 1   
      

      Like

      • Jim Randell's avatar

        Jim Randell 5:07 pm on 12 September 2022 Permalink | Reply

        We are told in the puzzle text that the lowest weight is 1lb.

        Even if we weren’t told, given any set of weights all that matters is the set of differences. So all the weights can be reduced (or increased) by the same amount to give another set.

        Specifically if we reduce the lowest weight to 1 we will get the minimal possible set based on the original set.

        In fact we can reduce the lowest weight to 0, and then we end up with the corresponding ruler.

        Like

  • Unknown's avatar

    Jim Randell 8:47 am on 10 March 2022 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 682: Jigsaw 

    From The Sunday Times, 11th August 1974 [link]

    “Bend me your ears”, says Bell wittily at the pub. “I have here six cardboard squares all of different sizes and one extra rectangular piece, which is bigger than the smallest square but smaller than any of the other five”.

    “Now I fit them together as a jigsaw puzzle to make a complete rectangle. All the pieces measure an exact number of inches and nobody can make a smaller jigsaw out of any such seven pieces. Bigger and smaller, of course, means by areas”.

    “To help you I will tell you this table is 20 inches square, and the jigsaw, you can see, is smaller than that. For the first solution I shall be happy to award one pint”.

    What are the measurements of:

    (a) the six squares?
    (b) the extra piece?
    (c) the jigsaw?

    The setter is given as “the late W A Thatcher”.

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

    [teaser682]

     
    • Jim Randell's avatar

      Jim Randell 8:48 am on 10 March 2022 Permalink | Reply

      This Python program uses the rectangle packing code rectpack.py, last seen in Teaser 2806.

      It considers possible values for the total area of the pieces, then sees if that area can be made from 6 squares and a rectangular piece, and then attempts to pack them into a rectangle that fits within the table.

      It runs in 261ms. (Internal runtime is 207ms).

      Run: [ @replit ]

      from enigma import (irange, inf, divisors_pairs, multiply, printf)
      from rectpack import (pack, output_grid)
      
      # construct a set of <k> square pieces and a rectangular piece of total area <t>
      # s = minimal side of square
      # rs = current rectangles
      def pieces(t, k, s=1, rs=[]):
        # are we done?
        if k == 0:
          if len(rs) > 1:
            # final piece is a rectangle, between the smallest squares
            if multiply(rs[0]) < t < multiply(rs[1]):
              for (x, y) in divisors_pairs(t):
                if x < y < 21:
                  yield rs + [(x, y)]
        else:
          # add in another square
          for x in irange(s, inf):
            x2 = x * x
            if t < k * x2 + 2: break
            yield from pieces(t - x2, k - 1, x + 1, rs + [(x, x)])
      
      # find a minimal solution
      def solve():
        # consider possible total areas
        for t in irange(93, 380):
          for (x, y) in divisors_pairs(t):
            if y > 20: continue
      
            for rs in pieces(t, 6):
              for ps in pack(y, x, rs):
                # output solution
                printf("t={t} x={x} y={y} -> {rs}")
                printf()
                output_grid(y, x, ps)
                printf()
      
                # we only need 1 packing
                return
      
      solve()
      

      Solution: (a) The six squares measure: 1×1, 4×4, 5×5, 6×6, 7×7, 8×8. (b) The extra piece is a 1×4 rectangle. (c) The jigsaw is 13×15.

      Here is the layout found by the program:

      Note that the 5×5, 4×4, 1×4 pieces form sub-rectangles that can be rearranged to give additional layouts.

      There are also solutions for a 15×17 jigsaw and a 17×19 jigsaw.

      Like

  • Unknown's avatar

    Jim Randell 12:15 pm on 25 July 2019 Permalink | Reply
    Tags: by: W A Thatcher   

    Brain-Teaser 489: [Railway journeys] 

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

    The All-Go railway runs from Alby to Goby over 20 miles away. Let us say it goes from A to G, and on the way you stop at B, C, D, E and F in that order.

    All the distances between stops are different from one another and all are less than 8 miles, yet it is possible to make journeys of 1 mile, 2 miles, 3 miles and so on up to 18 miles by picking the right stations.

    The distance from A to B is less than the distance from F to G.

    How many miles from A are B, C, D, E, F and G?

    This puzzle was originally published with no title.

    [teaser489]

     
    • Jim Randell's avatar

      Jim Randell 12:15 pm on 25 July 2019 Permalink | Reply

      (See also: Teaser 1986).

      This Python program runs in 220ms.

      Run: [ @replit ]

      from enigma import (subsets, irange, tuples, csum, join, sprintf as f, printf)
      
      # required distances
      rs = set(irange(1, 18))
      
      # choose the lengths of the 6 segments, AB BC CD DE EF FG
      for ss in subsets(irange(1, 7), size=6, select='P'):
        (AB, BC, CD, DE, EF, FG) = ss
        if not (AB < FG): continue
      
        # collect together adjacent segments from 1 to 6
        ds = set(sum(t) for k in irange(1, 6) for t in tuples(ss, k))
        if not rs.issubset(ds): continue
      
        printf("{cs} [ss={ss}]", cs=join((f("A{x}={d}") for (x, d) in zip("BCDEFG", csum(ss))), sep=" "))
      

      Solution: The distances (in miles) are: A→B=2, A→C=7, A→D=14, A→E=15, A→F=18, A→G=24.

      The distances between stations are:

      [A] 2 [B] 5 [C] 7 [D] 1 [E] 3 [F] 6 [G]

      So as well as being able to make all the distances from 1 to 18, we can also make distances of 22 (B to G) and 24 (A to G).

      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