Teaser 2469: [Football league]
From The Sunday Times, 17th January 2010 [link]
Our football league has six teams, A, B, C, D, E and F, who play each other once, earning 3 points for a win, 1 for a draw. Of last season’s 20 goals, the best were in Primadona’s hat-trick in C’s derby against D. At one stage, when C and D had played all their games, but A and B each still had two in hand, the league order was A, B, C, D, E, F (goal differences separate teams tying on points). But A’s morale was shattered because, in the end, C won the league (the number of goals scored also being needed for the final decision).
What were the scores in C’s matches? (C v A, C v B, C v D, C v E, C v F)?
This puzzle was originally published with no title.
[teaser2469]
Jim Randell 8:55 am on 2 December 2025 Permalink |
The wording of the puzzle suggests that in the final order C had the same points and goal difference as (at least) one of the other teams, and was declared winner based on goals scored.
I think this puzzle (and probably Teaser 2516) are more easily dealt with manually. But there is a certain challenge in constructing a programmatic solution.
My program adopts the following strategy:
It runs in 5.3s (using PyPy).
from enigma import (Football, subsets, update, cproduct, peek, is_decreasing, zip_eq, join, printf) # scoring system football = Football(games="wdlx", points=dict(w=3, d=1)) teams = "ABCDEF" keys = sorted(x + y for (x, y) in subsets(teams, size=2)) # find keys for each of the teams dks = dict((t, list(k for k in keys if t in k)) for t in teams) # allocate match outcomes for the "one stage" def stage1(): # C and D have played all their games for (AD, BD, CD, DE, DF) in football.games("wdl", repeat=5): D = football.table([AD, BD, CD, DE, DF], [1, 1, 1, 0, 0]) for (AC, BC, CE, CF) in football.games("wdl", repeat=4): C = football.table([AC, BC, CD, CE, CF], [1, 1, 0, 0, 0]) if not (C.points >= D.points): continue # A and B each have two games unplayed (x) for (AB, BE, BF) in football.games("wdlx", repeat=3): B = football.table([AB, BC, BD, BE, BF], [1, 0, 0, 0, 0]) if not (B.x == 2 and B.points >= C.points): continue for (AE, AF) in football.games("wdlx", repeat=2): A = football.table([AB, AC, AD, AE, AF], [0, 0, 0, 0, 0]) if not (A.x == 2 and A.points >= B.points): continue # the remaining game (may be unplayed) for EF in football.games("wdlx"): E = football.table([AE, BE, CE, DE, EF], [1, 1, 1, 1, 0]) if not (D.points >= E.points): continue F = football.table([AF, BF, CF, DF, EF], [1, 1, 1, 1, 1]) if not (E.points >= F.points): continue # return match outcomes (at "one stage") yield dict(zip(keys, [AB, AC, AD, AE, AF, BC, BD, BE, BF, CD, CE, CF, DE, DF, EF])) # allocate remaining matches def stage2(ms): # find unplayed matches xs = list(k for (k, v) in ms.items() if v == 'x') # allocate match outcomes for vs in football.games("wdl", repeat=len(xs)): ms1 = update(ms, xs, vs) # calculate the new table (A, B, C, D, E, F) = (football.extract_table(ms1, t) for t in teams) # C is now at the top of the table if not (C.points >= max(A.points, B.points, D.points, E.points, F.points)): continue # return match outcomes (final) and unplayed games (at "one stage") yield (ms1, xs) # allocate goals to the matches; return scorelines def stage3(ms): # minimum goals gs = dict(w=(1, 0), d=(0, 0), l=(0, 1)) # allocate minimum goals ss = dict((k, gs[v]) for (k, v) in ms.items()) # one side scores 3 goals in the CvD match gs = dict(w=[(3, 0), (4, 3)], d=[(3, 3)], l=[(3, 4), (0, 3)]) k = 'CD' for v in gs[ms[k]]: yield update(ss, [(k, v)]) # allocate remaining goals (to make 20 in total); return scorelines def stage4(ms, ss): # calculate number of goals remaining r = 20 - sum(sum(x) for x in ss.values()) if r == 0: yield ss elif r > 0: # chose matches and teams for the remaining goals for kts in subsets(cproduct([keys, (0, 1)]), size=r, select='R'): # make an updated set of scores (ks, ss_) = (set(), ss) for (k, t) in kts: ks.add(k) v = ss_[k] ss_ = update(ss_, [(k, update(v, [(t, v[t] + 1)]))]) # check we haven't changed any outcomes if all(peek(football.outcomes([ss_[k]], [0])) == ms[k] for k in ks): yield ss_ # calculate (<points>, <goal-difference>, <goals-for>) score for team <t> def score(t, ms, ss): T = football.extract_table(ms, t) (gf, ga) = football.extract_goals(ss, t) return (T.points, gf - ga, gf) # check a scenario def check(ms, ss, xs): # check C wins the league (A, B, C, D, E, F) = (score(t, ms, ss) for t in teams) # C is at the top of the table others = (A, B, D, E, F) if not C > max(others): return # but has the same number of points _and_ goal difference as another team if not any(zip_eq(C, X, first=2) for X in others): return # check the positions with the matches in <xs> unplayed ms = update(ms, xs, ['x'] * len(xs)) ss = update(ss, xs, [None] * len(xs)) scores = tuple(score(t, ms, ss) for t in teams) # check the order is A, B, C, D, E, F if not is_decreasing(scores, strict=1): return # this is a valid scenario return scores # put it all together ... for ms1 in stage1(): for (ms, xs) in stage2(ms1): for ss3 in stage3(ms): for ss in stage4(ms, ss3): # extract (<pts>, <diff>, <for>) scores for a valid scenario scores = check(ms, ss, xs) if scores: printf("unplayed = {xs}", xs=join(xs, sep=", ", sort=1)) printf("scores = {scores}") # scores at "one stage" football.output_matches(ms, ss) # final resultsSolution: The scores in C’s matches are: C v A = 0-1; C v B = 0-1; C v D = 3-3; C v E = 1-0; C v F = 2-0.
The full set of results is:
The final order was:
i.e. C > A > B = E > D > F.
Note that B and E cannot be separated in the final order by points, goal difference, or goals scored.
At the intermediate stage the following games were unplayed:
Giving the following orders:
In either case the order is: A > B > C > D > E > F.
LikeLike