Enigmatic Code

Programming Enigma Puzzles

Enigma 1438: Soccer stats

From New Scientist #2599, 14th April 2007

There are five teams in our local league, and each team plays each of the others once a season. Below is the league table at the end of last season.

Enigma 1438

Unfortunately three of those numerical entries are incorrect. What were the scores in Arnsley’s four games?

[enigma1438]

One response to “Enigma 1438: Soccer stats

  1. Jim Randell 22 April 2013 at 2:23 pm

    A bit of analysis tells us in which part of the table the errors must lie, and this Python program does the rest. It’s a bit messy, but it runs in 71ms.

    # consider the sums of the columns:
    # won = 8, drawn = 7, lost = 5, for = 7, against = 7
    #
    # each won game for one team is lost for another team, so
    # it should be that "won" = "lost", but they are different
    # hence there is at least one error in the "won" and "lost"
    # columns.
    #
    # also each drawn game is drawn for two teams, so "drawn" should
    # be a multiple of 2, and it isn't so there is at least one
    # error in the "drawn" column.
    #
    # each goal scored for a team is scored against another team
    # so these columns should be equal - and they are, so if there
    # was an error in one column there would have to be an error
    # in the other column to bring the values back into line, hence
    # there cannot be exactly one error in the "for" and "against"
    # columns (which is the maximum possibility), so these columns
    # must contain no errors.
    #
    # hence all three errors are in the "won", "lost", "drawn" columns.
    
    from collections import namedtuple
    from itertools import product
    from enigma import irange, printf
    
    # the 10 matches are:
    # AvB AvC AvD AvE BvC BvD BvE CvD CvE DvE
    # each is either won, lost or drawn ('d')
    
    Table = namedtuple('Table', 'w l d')
    
    # generate the "won", "drawn", "lost" entries in the table
    def table(t, games):
      w, l, d = 0, 0, 0
      for g in games:
        if g == 'd':
          d += 1
        elif g == t:
          w += 1
        else:
          l += 1
      return Table(w, d, l)
    
    # generate possible scores for team <t> in games <games>
    # with total goals for <f> and total goals against <a>
    def scores(t, games, f, a):
      if len(games) == 0:
        if f == a == 0: yield []
      else:
        g = games[0]
        if g == 'd':
          for n in irange(0, min(f, a)):
            for s in scores(t, games[1:], f - n, a - n):
              yield [(n, n)] + s
        elif g == t:
          for n in irange(1, f):
            for m in irange(0, min(n - 1, a)):
              for s in scores(t, games[1:], f - n, a - m):
                yield [(n, m)] + s
        else:
          for n in irange(0, f):
            for m in irange(n + 1, a):
              for s in scores(t, games[1:], f - n, a - m):
                yield [(n, m)] + s
    
    # make the table for A
    for (AB, AC, AD, AE) in product('ABd', 'ACd', 'ADd', 'AEd'):
      A = table('A', (AB, AC, AD, AE))
      # count the discrepancies
      dA = sum(1 for (p, q) in zip(A, (2, 1, 1)) if p != q)
      # make the table for B
      for (BC, BD, BE) in product('BCd', 'BDd', 'BEd'):
        B = table('B', (AB, BC, BD, BE))
        # count the discrepancies
        dB = sum(1 for (p, q) in zip(B, (3, 1, 0)) if p != q)
        if dA + dB > 3: continue
        # table for C
        for (CD, CE) in product('CDd', 'CEd'):
          C = table('C', (AC, BC, CD, CE))
          dC = sum(1 for (p, q) in zip(C, (1, 3, 0)) if p != q)
          if dA + dB + dC > 3: continue
          # table for D and E
          for DE in 'DEd':
            D = table('D', (AD, BD, CD, DE))
            E = table('E', (AE, BE, CE, DE))
            dD = sum(1 for (p, q) in zip(D, (1, 2, 1)) if p != q)
            dE = sum(1 for (p, q) in zip(E, (1, 0, 3)) if p != q)
            d = dA + dB + dC + dD + dE
            if d != 3: continue
    
            # generate scores that match up to the "for" against "columns"
            for (sAB, sAC, sAD, sAE) in scores('A', [AB, AC, AD, AE], 2, 2):
              for (sBC, sBD, sBE) in scores('B', [BC, BD, BE], 2 - sAB[1], 1 - sAB[0]):
                for (sCD, sCE) in scores('C', [CD, CE], 1 - sAC[1] - sBC[1], 0 - sAC[0] - sBC[0]):
                  for (sDE,) in scores('D', [DE], 1 - sAD[1] - sBD[1] - sCD[1], 1 - sAD[0] - sBD[0] - sCD[0]):
                    if (sAE[1] + sBE[1] + sCE[1] + sDE[1], sAE[0] + sBE[0] + sCE[0] + sDE[0]) != (1, 3): continue
    
                    printf("AvB: {sAB}, AvC: {sAC}, AvD: {sAD}, AvE: {sAE}")
                    printf("BvC: {sBC}, BvD: {sBD}, BvE: {sBE}")
                    printf("CvD: {sCD}, CvE: {sCE}")
                    printf("DvE: {sDE}")
                    print('')
    

    Solution: The scores in Arnsley’s four games were – Arnsley vs. Boldham: 0 – 2; Arnsley vs. Cleeds: 0 – 0; Arnsley vs. Drochdale: 1 – 0; Arnsley vs. Erby: 1 – 0.

    The incorrect values are the “won”, “drawn” and “lost” values for Boldham.

Leave a Comment

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