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:

Enigma 161

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).


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
          # 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')

    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.

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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

%d bloggers like this: