Teaser 2494: [Council election]
From The Sunday Times, 11th July 2010 [link] [link]
George and Martha’s council has 40 seats shared between Labour, Conservatives and Democrats. Before a recent election, Labour had an overall majority, with Conservatives second. After it, the order was Conservatives first, Labour second, Democrats third, both Conservatives and Democrats having improved their numbers of seats. To achieve a majority, the Conservatives formed a coalition with the Democrats. Each party had a whole two-figure number percentage change in its number of seats; the Democrats’ percentage increase was an odd number.
How many seats did each party win?
This puzzle was originally published with no title.
[teaser2494]
Jim Randell 10:10 am on 21 October 2025 Permalink |
Here is a solution using the [[
SubstitutedExpression()]] solver from the enigma.py library.It runs in 87ms. (Internal runtime of the generated code is 6.8ms).
from enigma import (SubstitutedExpression, irange, div, sprintf as f, printf) # calculate (integer) percentage change def change(before, after): return div(100 * (after - before), before) # 2-digit numbers for change percentages d2 = irange(10, 99) d2o = irange(11, 99, step=2) # odd numbers # symbols for each party before (= X1) and after (= X2) (L1, C1, D1, L2, C2, D2) = "ABCXYZ" # constraints for the solver exprs = [ # total number of seats is 40 f("{L1} + {C1} + {D1} == 40"), f("{L2} + {C2} + {D2} == 40"), # before: Lab had an overall majority, with Con second f("{L1} > {C1} + {D1}"), f("{C1} > {D1}"), # after: Con was first, Lab second, Dem third f("{C2} > {L2} > {D2}"), # both Con and Dem increased their numbers of seats f("{C2} > {C1}"), f("{D2} > {D1}"), # after: Con formed a coalition with Dem to get a majority f("not ({C2} > {L2} + {D2})"), f("{C2} + {D2} > {L2}"), # each party had a 2-digit percentage change in its number of seats f("abs(change({L1}, {L2})) in d2"), f("abs(change({C1}, {C2})) in d2"), f("abs(change({D1}, {D2})) in d2o"), # Dem's change is odd ] # construct the solver p = SubstitutedExpression( exprs, base=41, # each value is 0 .. 40 distinct="", env=dict(change=change, d2=d2, d2o=d2o), answer=f("({L1}, {C1}, {D1}, {L2}, {C2}, {D2})"), ) # solve the puzzle for (L1, C1, D1, L2, C2, D2) in p.answers(verbose=0): # output solution printf("before (seats) -> Lab {L1:2d}, Con {C1:2d}, Dem {D1:2d}") printf("after (seats) -> Lab {L2:2d}, Con {C2:2d}, Dem {D2:2d}") (cL, cC, cD) = (L2 - L1, C2 - C1, D2 - D1) printf("change (seats) -> Lab {cL:+3d}, Con {cC:+3d}, Dem {cD:+3d}") (cL, cC, cD) = (change(L1, L2), change(C1, C2), change(D1, D2)) printf("change (percent) -> Lab {cL:+d}%, Con {cC:+d}%, Dem {cD:+d}%") printf()Solution: The result of the recent election was: Conservatives = 19 seats; Labour = 11 seats; Democrats = 10 seats.
Before the election the seats were allocated thus:
So the changes are:
The percentages don’t add up to 0% because they are the percentage change over the previous number of seats, not the percentage change in the overall share of seats. If we use this measure, then there are many solutions, but the changes will sum to 0%. (This can be seen by making the [[
change()]] function divide by the total number of seats [[40]] instead of [[before]]).LikeLike
Ruud 8:24 am on 22 October 2025 Permalink |
As brute force as possible:
import peek import itertools def check(x0, x1, has_to_be_odd=False): perc = (abs(x0 - x1) / x0) * 100 if perc % 1 or perc < 10 or perc >= 100: return False return not has_to_be_odd or perc % 2 == 1 for l0, c0, d0 in itertools.product(range(1, 41), repeat=3): if l0 + c0 + d0 == 40 and l0 > c0 + d0 and l0 > c0 > d0: for l1, c1, d1 in itertools.product(range(1, 41), repeat=3): if l1 + c1 + d1 == 40 and c1 < l1 + d1 and c1 > l1 > d1 and c1 + d1 > 20 and c1 > c0 and d1 > d0: if check(l0, l1) and check(c0, c1) and check(d0, d1, has_to_be_odd=True): peek(l0, l1, c0, c1, d0, d1, l1 - l0, c1 - c0, d1 - d0)LikeLike
Frits 4:35 pm on 22 October 2025 Permalink |
# check for a two-figure number percentage change def check(old, new): d, r = divmod(100 * abs(new - old), old) if r or not (9 < d < 100): return False return d M = 40 parties = "Labour Conservatives Democrats".split(" ") # Labour had a majority, Democrats had at least 2 seats (two-figure %) for l1 in range(M // 2 + 1, M - 4): for c1 in range((M - l1) // 2 + 1, M - l1 - 1): d1 = M - l1 - c1 # up to 99 percent increase for d2 in range(d1 + 1, min(2 * d1, M // 3)): if not (perc := check(d1, d2)): continue # the Democrats' percentage increase was an odd number if not (perc % 2): continue for c2 in range(max(d1 + 1, (M - d2) // 2 + 1), M // 2 + 1): if not check(c1, c2): continue l2 = M - d2 - c2 if l2 <= d2 or not check(l1, l2): continue print(list(zip(parties, [l2 - l1, c2 - c1, d2 - d1])))LikeLike