# Enigmatic Code

Programming Enigma Puzzles

## Enigma 161: Two times table

From New Scientist #1306, 20th May 1982 [link]

Lunchtime at Bramfield School is currently being taken up with a five-a-side soccer competition, in which five teams are playing in a league and will eventually play each other just once. Young James, who has shown himself to be more capable of tackling sums than footballers, is keeping a record of the results from the touchline, and has the table below as his latest check:

It is surprising, even with James’s keen eye for numbers, that he has spotted that the sum of all the numbers in this table is twice what it was earlier in the competition (when the league had no clear leader[*]).

What had been the results at that stage?

[*] This phrase is intended to imply two or more teams had the same number of points at the top.

I don’t think there is a unique solution to this puzzle (as the problem statement implies).

[enigma161]

### One response to “Enigma 161: Two times table”

1. Jim Randell 14 January 2014 at 8:25 am

I always find these football puzzle fiddly to program, even using the Football() class from the enigma.py library to make things a bit less confusing.

This Python program starts by find possible results that match the table given in the problem statement, and then examines subsets of those results that give a table where the numbers involved sum to half the sum of the number in the original table. It runs in 124ms.

```from enigma import Football, subsets, sprintf, printf

# initialise the scoring rules
football = Football(points={ 'w': 2, 'd': 1 })

# there are 5 teams: At, Ro, Wa, Al, St.

# each team plays each other team once, so each team plays 4 matches

# output played matches
def output(ms, text):
r = []
labels = ('At v Ro', 'At v Wa', 'At v Al', 'At v St', 'Ro v Wa', 'Ro v Al', 'Ro v St', 'Wa v Al', 'Wa v St', 'Al v St')
for (label, s) in zip(labels, ms):
if s is None: continue
r.append(sprintf("{label}: {s[0]}-{s[1]}"))
printf("[{text}] {r}", r=', '.join(r))

# generate possible scorelines for the table as it stands
def generate():
# Ro has played all 4 of their matches
for (AtRo, RoWa, RoAl, RoSt) in football.games('wdl', repeat=4):
# and won 1, drew 2, lost 1
Ro = football.table([AtRo, RoWa, RoAl, RoSt], [1, 0, 0, 0])
if not(Ro.w == 1 and Ro.d == 2 and Ro.l == 1): continue
# possible scorelines
for (sAtRo, sRoWa, sRoAl, sRoSt) in football.scores([AtRo, RoWa, RoAl, RoSt], [1, 0, 0, 0], 2, 2):

# remaining games for St
for (AtSt, WaSt, AlSt) in football.games(repeat=3):
# and won 0, drew 1, lost 1
St = football.table([AtSt, RoSt, WaSt, AlSt], [1, 1, 1, 1])
if not(St.w == 0 and St.d == 1 and St.l == 1): continue
# possible scorelines
for (sAtSt, sWaSt, sAlSt) in football.scores([AtSt, WaSt, AlSt], [1, 1, 1], 0, 1, [sRoSt], [1]):

# remaining games for Wa
for (AtWa, WaAl) in football.games(repeat=2):
# and won 1, drew 0, lost 1
Wa = football.table([AtWa, RoWa, WaAl, WaSt], [1, 1, 0, 0])
if not(Wa.w == 1 and Wa.d == 0 and Wa.l == 1): continue
# possible scorelines
for (sAtWa, sWaAl) in football.scores([AtWa, WaAl], [1, 0], 1, 2, [sRoWa, sWaSt], [1, 0]):

# remaining game
for AtAl in football.games():
# At has won 2, drawn 1, lost 0
At = football.table([AtRo, AtWa, AtAl, AtSt], [0, 0, 0, 0])
if not(At.w == 2 and At.d == 1 and At.l == 0): continue
# Al has won 0, drawn 2, lost 1
Al = football.table([AtAl, RoAl, WaAl, AlSt], [1, 1, 1, 0])
if not(Al.w == 0 and Al.d == 2 and Al.l == 1): continue
# possible scorelines (for Al)
for (sAtAl,) in football.scores([AtAl], [1], 1, 3, [sRoAl, sWaAl, sAlSt], [1, 1, 0]):
# and check At's for/against
if not(football.goals([sAtRo, sAtWa, sAtAl, sAtSt], [0, 0, 0, 0]) == (6, 2)): continue

yield (sAtRo, sAtWa, sAtAl, sAtSt, sRoWa, sRoAl, sRoSt, sWaAl, sWaSt, sAlSt)

# consider possible scorelines
for ss in generate():
# find the indices of the played games
ps = list(i for (i, s) in enumerate(ss) if s is not None)
# and choose subsets of the played games
for gs in subsets(ps, min_size=1):
# and select those games
qs = list((s if i in gs else None) for (i, s) in enumerate(ss))

# sum the numbers in the table
(sAtRo, sAtWa, sAtAl, sAtSt, sRoWa, sRoAl, sRoSt, sWaAl, sWaSt, sAlSt) = qs
(n, ts) = (0, [])
for (x, y) in (([sAtRo, sAtWa, sAtAl, sAtSt], [0, 0, 0, 0]),
([sAtRo, sRoWa, sRoAl, sRoSt], [1, 0, 0, 0]),
([sAtWa, sRoWa, sWaAl, sWaSt], [1, 1, 0, 0]),
([sAtAl, sRoAl, sWaAl, sAlSt], [1, 1, 1, 0]),
([sAtSt, sRoSt, sWaSt, sAlSt], [1, 1, 1, 1])):
# calculate the table
t = football.table(football.outcomes(x), y)
n += t.w + t.d + t.l + t.points
ts.append(t)
# and goals for and against
(f, a) = football.goals(x, y)
n += f + a

# find when the numbers sum to 24
if not(n == 24): continue

# sort the table by points
ts = sorted(ts, key=lambda t: t.points, reverse=True)

# there must be a tie at the top
if not(ts[0].points == ts[1].points): continue

output(ss, 'Full')
output(qs, 'Half')
print()
```

Solution: The published solution is:

Athletics vs. Albion: 3-1
Rovers vs. Wanderers: 0-1
Rovers vs. Strikers: 1-0

But there is a second solution that satisfies the conditions of the puzzle:

Athletics vs. Wanderers: 2-0
Rovers vs. Wanderers: 0-1
Rovers vs. Strikers: 1-0
Albion vs. Strikers: 0-0

Both these solutions have the same collection of results for the table given in the puzzle, namely:

Athletics vs. Rovers: 1-1
Athletics vs. Wanderers: 2-0
Athletics vs. Albion: 3-1
Rovers vs. Wanderers: 0-1
Rovers vs. Albion: 0-0
Rovers vs. Strikers: 1-0
Albion vs. Strikers: 0-0

There isn’t any condition given in the puzzle to exclude the second solution (even though one could fairly easily have been included, or we could have been asked, for example, for the results of Rovers matches (if any)), so I have marked the puzzle as flawed.

This site uses Akismet to reduce spam. Learn how your comment data is processed.