From The Sunday Times, 8th February 1981 [link]
My old school friends, Rose and May, both live on the sunny side of Woodland Grove in a row of five houses. I know that May lives with her parents, Mr and Mrs Joiner, in the house called “The Oaks”, but Rose had never told me her surname nor the name of her house.
I wanted to send both of them invitations to my 21st birthday party, so I asked the local paper boy if he knew which house Rose lived in or the name of her parents. He was not sure, but came up with the following facts:
1. Each of the five houses is occupied by a married couple with one unmarried daughter.
2. The last of the five houses on the right is called “Fir Trees”.
3. The Turner family lives in the house immediately to the right of the house called “The Willows”.
4. The Carpenters live next door but one to the house called “Silver Birches”.
5. The second house from the left is the home of the Sawyer family.
6. Cherry is the daughter of the couple in the middle house.
7. Ivy lives with her parents at The Elms”.
8. Hazel is the daughter of Mr and Mrs Woodman.
From this information I was able to deduce Rose’s surname and the name of her house.
What are they?
This puzzle is included in the book The Sunday Times Book of Brainteasers (1994).
[teaser968]
Jim Randell 7:01 am on 26 April 2026 Permalink |
I used the characterisation of a tangential polygon given here [@wikipedia].
Namely:
For positive solutions to exist this means:
So we can bracket possible x0 values by constructing the alternating sum of the candidate side lengths, and then look for non-empty intervals at the end of the process. Any x0 value in this interval will give a viable tangential octagon.
This Python program runs in 68ms. (Internal runtime is 861µs).
from enigma import ( defaultdict, irange, inf, tri, first, lt, div, diff, subsets, cproduct, union, nsplit, printf ) # 2-digit triangular numbers ns = first((tri(n) for n in irange(1, inf)), count=lt(100), skip=lt(10)) # is there a tangential octagon with sides <ss>? def is_tangential(ss): xs = None # for a valid arrangement, the sum of the even numbered sides # is the same as the sum of the odd numbered sides s = div(sum(ss), 2) if s is None: return # choose the even numbered sides a0 = min(ss) ssx = diff(ss, {a0}) for ss0 in subsets(ssx, size=3, fn=list): if a0 + sum(ss0) != s: continue # arrange the sides ss1 = diff(ssx, ss0) for ((a2, a4, a6), (a1, a3, a5, a7)) in cproduct(subsets(xs, size=len, select='P') for xs in [ss0, ss1]): if not (a1 < a7): continue # bracket possible values for x0 by constructing the alternating sum of the sides lo = max(0, a0 - a1, a0 - a1 + a2 - a3, a0 - a1 + a2 - a3 + a4 - a5) hi = min(a0, a0 - a1 + a2, a0 - a1 + a2 - a3 + a4, a0 - a1 + a2 - a3 + a4 - a5 + a6) # is the bracket non-empty? if lo < hi: return (a0, a1, a2, a3, a4, a5, a6, a7) # map odd side lengths to perimeter d = defaultdict(set) # choose 8 of the numbers for ss in subsets(ns, size=8): # all 10 digits should be used ds = union(nsplit(n) for n in ss) if len(ds) < 10: continue # is there a tangential octagon? rs = is_tangential(ss) if rs is None: continue # record perimeter by side length p = sum(ss) printf("[{rs} -> {p}]") for n in ss: d[n].add(p) # look for odd side lengths with only one possible perimeter for n in sorted(d.keys()): vs = d[n] if n % 2 == 1 and len(vs) == 1: printf("{n} -> {vs}")Solution: The perimeter of the octagon is 358 mm.
There are three candidate sets of sides that permit a tangential octagon to be constructed. Example arrangements for these sets are:
(Note that these are not the only arrangements of the sides that permit a tangential octagon to be constructed. For the first and second set there are 20 possible arrangements, and for the third set there are 24. So not all candidate arrangements are viable).
15 only appears in the first of these, and is the only odd valued side that uniquely identifies a candidate (the one with perimeter 358).
There is also one even valued side which only appears in one of the candidates, namely 66, which identifies the candidate with perimeter 402.
LikeLike
Jim Randell 11:13 pm on 26 April 2026 Permalink |
Here is my program modified to use the [[
find_max()]] solver from the enigma.py library to ensure the equations have a positive solution, and to find the corresponding tangent lengths, which allows candidate tangential octagons to be constructed.This Python program runs in 69ms. (Internal runtime is 924µs).
from enigma import ( defaultdict, irange, inf, tri, first, lt, div, diff, subsets, cproduct, find_max, union, nsplit, printf ) # 2-digit triangular numbers ns = first((tri(n) for n in irange(1, inf)), count=lt(100), skip=lt(10)) # is there a tangential octagon with sides <ss>? def is_tangential(ss): # for a valid arrangement, the sum of the even numbered sides # is the same as the sum of the odd numbered sides s = div(sum(ss), 2) if s is None: return # choose the even numbered sides a0 = min(ss) ssx = diff(ss, {a0}) for ss0 in subsets(ssx, size=3, fn=list): if a0 + sum(ss0) != s: continue # arrange the sides ss1 = diff(ssx, ss0) for ((a2, a4, a6), (a1, a3, a5, a7)) in cproduct(subsets(xs, size=len, select='P') for xs in [ss0, ss1]): if not (a1 < a7): continue # can we solve the equations for positive <x> values? def solve(x0): x1 = a0 - x0 x2 = a1 - x1 x3 = a2 - x2 x4 = a3 - x3 x5 = a4 - x4 x6 = a5 - x5 x7 = a6 - x6 return min(x0, x1, x2, x3, x4, x5, x6, x7) # look for the maximum smallest <x> value r = find_max(solve, 0, a0) if r.fv > 0: # return viable solution return (a0, a1, a2, a3, a4, a5, a6, a7) # map odd side lengths to perimeter d = defaultdict(set) # choose 8 of the numbers for ss in subsets(ns, size=8): # all 10 digits should be used ds = union(nsplit(n) for n in ss) if len(ds) < 10: continue # is there a tangential octagon? rs = is_tangential(ss) if rs is None: continue # record perimeter by side length p = sum(ss) printf("[{rs} -> {p}]") for n in ss: d[n].add(p) # look for odd side lengths with only one possible perimeter for n in sorted(d.keys()): ps = d[n] if n % 2 == 1 and len(ps) == 1: printf("{n} -> {ps}")If the equations admit a positive solution, then there are infinitely many, so the program finds the polygon where the closest tangent point to a vertex is as far as possible.
We can then use the data to plot viable tangential octagons for candidate arrangements.
Here are the three viable candidate arrangements, each side of the octagon is shown with its tangent point and side length. (Some of the angles at the vertices are very close to 180°).
LikeLiked by 2 people
Frits 10:59 am on 26 April 2026 Permalink |
from itertools import combinations # collect possible triangular side lengths n, t, sides = 0, 0, [] while True: t = n * (n + 1) // 2 if t > 99: break if t > 9: sides.append(t) n += 1 # claim from Google AI: # a convex octagon with the sum of its odd-numbered sides' lengths equal to # the sum of its even-numbered sides' lengths has an inscribed circle. # thus s1 + s3 + s5 + s7 must be equal to s2 + s4 + s6 + s8 sols = [] # select eight sides of the octagon for c8 in combinations(sides, 8): h, r = divmod(sum(c8), 2) if r: continue # all digits should be present if len(set("".join(str(n) for n in c8))) != 10: continue # pick four sides s(i) from <c8> with sum <h> c1 = c8[0] for c3 in combinations(c8[1:], 3): if sum(c3) + c1 != h: continue sols.append(c8) # collect possible solutions per odd number cands = [[s for s in sols if n in s] for n in range(11, 100, 2)] # select odd numbers that only occur in one solution sols = [str(sum(vs[0])) for vs in cands if len(vs) == 1] print("answer:", ' or '.join(sols))LikeLiked by 1 person
Frits 12:02 pm on 26 April 2026 Permalink |
Wikipedia mentions that in a tangential polygon with an even number of sides, the sum of the odd numbered sides’ lengths is equal to the sum of the even numbered sides’ lengths. Google AI claims that the reverse is also true (at least for a convex octagonal).
LikeLike
Jim Randell 5:10 pm on 26 April 2026 Permalink |
@Frits: Yes. I can use that condition to reduce the number of side arrangements that are considered by my program. (Although Wikipedia does not claim it is an “if and only if” condition, and I would require a more trustworthy source than Google AI to verify the sufficiency condition).
LikeLike
Jim Randell 5:33 pm on 26 April 2026 Permalink |
This paper [link] seems to suggest that the condition is not sufficient for 2n-gons larger than quadrilaterals.
I think a (2, 2, 2, 2, 100, 3, 3, 100) octagon would be a counterexample.
LikeLike
Frits 7:28 pm on 26 April 2026 Permalink |
@Jim, Thanks for debunking the claim.
I haven’t looked at your program yet (beside the use of Matrix.linear).
Probably you can use your method to check if a sequence of 8 octagon side lengths (with property sum(odd) = sum(even)) has an inscribed circle.
LikeLiked by 1 person
Jim Randell 4:01 pm on 4 May 2026 Permalink |
Instead of attempting to solve the equations (and looking for non-inconsistent sets) I’ve rejigged my program to determine the interval for x0 where the equations give positive x_i values, and if this interval is non-empty then it is possible to construct a tangential octagon using the given side arrangement.
[I am yet to see another solution that checks if a candidate arrangement of sides can actually form a tangential octagon].
LikeLike
Brian Gladman 10:28 pm on 27 April 2026 Permalink |
@Jim Nice work on the three candidates shapes. The radius of the incircle is 2.A / sum(sides) where A is the polygon’s area but is there a direct route to this radius from the a and x values?
LikeLike
Jim Randell 7:58 am on 28 April 2026 Permalink |
I took the
xvalues determined by my second program, and for a given radiusryou can calculate the angles at each vertex of the octagon.You can then vary
runtil the sum of the vertex angles is 1080° (or 6𝝅 radians).I already had a program (written for Teaser 2438) to plot a cyclic polygon (given the angles subtended at the centre by the sides), and then construct a tangential polygon from that.
So I just needed to calculate the angles at the centre of the octagon, these are supplementary to the angles at the vertices.
This is the program I used to plot the diagrams:
from math import (atan2, fsum, pi, degrees) from enigma import (find_value, run, arg, printf) # possible tangents xss = { 1: [6.5, 3.5, 11.5, 24.5, 3.5, 51.5, 39.5, 38.5], # arrangement 1 2: [5, 5, 16, 20, 8, 37, 18, 73], # arrangement 2 3: [5, 5, 16, 29, 7, 48, 18, 73], # arrangement 3 } xs = xss[arg(1, 0, int)] printf("xs = {xs}") # calculate vertex angles for radius r def angles(r): return list(2 * atan2(r, x) for x in xs) # find radius that gives an angle sum of 6.pi r = find_value((lambda r: fsum(angles(r))), 6 * pi, 0, 1000).v printf("inradius = {r}") # angles at the centre are supplementary to the vertex angles args = list(degrees(pi - a) for a in angles(r)) run("cyclic-polygon.py", *args)LikeLiked by 1 person
Brian Gladman 8:40 am on 28 April 2026 Permalink |
Thanks Jim, I hoped that you might have found a direct method to avoid the search for the inradius.
LikeLike
Frits 9:44 am on 28 April 2026 Permalink |
@Jim, where can we find the program “cyclic-polygon.py”?
LikeLike
Jim Randell 11:00 am on 28 April 2026 Permalink |
I’ve uploaded it here [@github].
(Make sure you are running the latest version of enigma.py).
LikeLike
Brian Gladman 12:14 pm on 28 April 2026 Permalink |
@Frits, you will also need Jim’s very nice 2D drawing program plot.py
LikeLike
Ruud 7:55 am on 28 April 2026 Permalink |
Based on @Frits’s solution:
import istr sides = [i for i in istr.range(10, 100) if i.is_triangular()] from itertools import combinations occurs = {istr(i): [] for i in range(10, 100)} sides = [i for i in istr.range(10, 100) if i.is_triangular()] for sel_sides in istr.combinations(sides, 8): if len(set(istr.join(sel_sides))) == 10: for sides3 in istr.combinations(sel_sides[1:], 3): if (sel_sides[0] + sum(sides3)) * 2 == sum(sel_sides): for i in sel_sides: occurs[i].append(sum(sel_sides)) for i, sum_sides in occurs.items(): if i.is_odd() and len(sum_sides) == 1: print(*sum_sides)LikeLike