{
  "id": "P058",
  "slug": "round-robin",
  "key": "P058-round-robin",
  "title": "Teams and round robin",
  "summary": "Split up to 16 rated players into two teams as even as possible - a backtracking search that drops any branch past half - or into three or four by a quick rule; then a league where everyone meets everyone once, scheduled by the circle method, with results entered as they come and a table sorted by points, goal difference and goals.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P058 - Teams and round robin\n\nSplit up to 16 rated players into teams of even strength, then play a\nleague where every entrant meets every other once. The schedule comes from\nthe circle method, results are entered as they come in, and the table\nfollows the usual rules.\n\n- `main.eml` - the menu, players, teams, the league and its messages\n- `teams.eml` - splitting players into two teams by search, or into\n  three or four by a quick rule\n- `league.eml` - the schedule and the table\n\nHow each part works:\n\n- Two teams are split as evenly as possible. One side's rating sum must\n  not pass half the total and should come as close to it as it can.\n  Backtracking finds it, as in the corpus case `subset-sum-backtracking`:\n  each player goes in or stays out, and any branch whose sum has passed\n  half is dropped at once. That is only safe because every rating is\n  positive. With an odd count the lighter side may have fewer players or\n  more, so both team sizes are searched.\n- Three or four teams use a quick rule instead: the strongest players\n  first, each to the team with the fewest players and then the lowest sum.\n  It is close, but not always the best split, and the program says so.\n- The schedule uses the circle method. Entrants sit around a circle with\n  the first fixed, pairs face each other across it, and the others turn\n  one place each round. Everyone meets everyone once in n - 1 rounds. With\n  an odd count a rest takes the extra seat, so each entrant rests once and\n  there are n rounds.\n- The table gives 3 points for a win and 1 for a draw. Ties are broken by\n  goal difference, then goals scored, then name.\n\nWhat is checked: menu choices 1 to 8; players as a name of up to 12\ncharacters, a space and a rating from 1 to 100, each name once, at most\n16; 2 to 4 teams with at least two players each; 3 to 8 league entrants,\neach name once; a round and a match that exist and are not a rest; scores\n0 to 99 written like 2-1. An empty answer cancels.\n\nSessions: `sessions/basic.in` loads ten sample players.\n- Two teams of exactly 325 each.\n- Three teams by the quick rule, 34 apart, the four-player team the\n  strongest.\n- A league of those three teams: one match a round, one team resting.\n- A league of five named teams, four results, the schedule with the\n  scores, and the table.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- teams, a league, a result and the table before any exist;\n- player lines: \"Ben\", \"Cy 0\", \"Dee 101\", a long name, Ana twice, 5 and x;\n- two teams of four players, 127 against 131, the closest there is;\n- leagues of two entrants, and with A twice, then A, B, C;\n- rounds 0, 4 and x; match 3; the rest in round 1; and the scores \"2-\",\n  a-b and 100-1;\n- the empty table.\n\nBuilt on the verified corpus case `subset-sum-backtracking` (subsets that\nreach a target, with branches pruned once they overshoot).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P058 teams and round robin: split players into teams of even strength,\n# then play a league where everyone meets everyone once - a schedule by the\n# circle method, results as they come in, and the table.\nimport teams\nimport league\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef number(s):\n    if s == \"\" or len(s) > 6:\n        return -1\n    0 => n\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef padded(s, width):\n    while len(s) < width:\n        s + \" \" => s\n    return s\n\ndef right(s, width):\n    while len(s) < width:\n        \" \" + s => s\n    return s\n\ndef add_players(state):\n    \"One player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.\" ^0\n    while len(state[0]) < 16:\n        trim(input(\"player> \")) => line\n        if line == \"\":\n            break\n        len(line) - 1 => k\n        while k > 0 and line[k] != \" \":\n            k - 1 => k\n        trim(line[0:k]) => name\n        number(line[k + 1:len(line)]) => rating\n        False => taken\n        for p in state[0]:\n            if p[0] == name:\n                True => taken\n        if k <= 0 or name == \"\" or rating < 1 or rating > 100:\n            \"Type a name, a space and a rating from 1 to 100.\" ^0\n        elif len(name) > 12:\n            \"Keep names to 12 characters.\" ^0\n        elif taken:\n            (\"There is a player called \" + name + \" already.\") ^0\n        else:\n            state[0] + [[name, rating]] => state[0]\n    if len(state[0]) == 16:\n        \"16 players is the most.\" ^0\n    (str(len(state[0])) + \" players.\") ^0\n\ndef show_team(label, members):\n    \"\" => s\n    for p in members:\n        if s != \"\":\n            s + \", \" => s\n        s + p[0] + \" \" + str(p[1]) => s\n    (padded(label, 8) + right(str(teams.total(members)), 4) + \"  \" + s) ^0\n\ndef make_teams(state):\n    if len(state[0]) < 4:\n        \"Add at least 4 players first.\" ^0\n        return\n    while True:\n        trim(input(\"how many teams (2 to 4)> \")) => answer\n        if answer == \"\":\n            \"Cancelled.\" ^0\n            return\n        number(answer) => k\n        if k >= 2 and k <= 4 and k * 2 <= len(state[0]):\n            break\n        \"Type 2, 3 or 4 - at least two players a team.\" ^0\n    if k == 2:\n        teams.two_teams(state[0]) => ts\n        \"The most even split there is, found by trying the choices and dropping those that pass half:\" ^0\n    else:\n        teams.many_teams(state[0], k) => ts\n        \"Strongest first, each to the weakest team so far - close, though not always the best:\" ^0\n    ts => state[1]\n    for t in [0:len(ts) - 1]:\n        show_team(\"Team \" + str(t + 1), ts[t])\n    teams.total(ts[0]) => lo\n    lo => hi\n    for t in ts:\n        if teams.total(t) < lo:\n            teams.total(t) => lo\n        if teams.total(t) > hi:\n            teams.total(t) => hi\n    (\"Strongest team minus weakest: \" + str(hi - lo) + \".\") ^0\n\ndef start_league(state):\n    trim(input(\"entrants, separated by commas (nothing to use the teams)> \")) => line\n    [] => names\n    if line == \"\":\n        if len(state[1]) == 0:\n            \"There are no teams yet - make some, or type the entrants.\" ^0\n            return\n        for t in [1:len(state[1])]:\n            names + [\"Team \" + str(t)] => names\n    else:\n        \"\" => word\n        for c in line + \",\":\n            if c == \",\":\n                trim(word) => w\n                if w != \"\":\n                    names + [w] => names\n                \"\" => word\n            else:\n                word + c => word\n    for i in [0:len(names) - 1]:\n        if len(names[i]) > 12:\n            \"Keep names to 12 characters.\" ^0\n            return\n        for j in [0:i - 1]:\n            if names[j] == names[i]:\n                (names[i] + \" is there twice.\") ^0\n                return\n    if len(names) < 3 or len(names) > 8:\n        \"A league needs 3 to 8 entrants.\" ^0\n        return\n    names => state[2]\n    league.schedule(names) => state[3]\n    {} => state[4]\n    int(len(names) / 2) => per_round\n    str(len(names)) + \" entrants: \" + str(len(state[3])) + \" rounds, each with \" + str(per_round) + \" match\" => s\n    if per_round > 1:\n        s + \"es\" => s\n    if len(names) % 2 == 1:\n        s + \" and one entrant resting\" => s\n    (s + \".\") ^0\n    show_schedule(state)\n\ndef entrant(state, i):\n    if i == -1:\n        return \"(rest)\"\n    return state[2][i]\n\ndef show_schedule(state):\n    if len(state[2]) == 0:\n        \"No league yet.\" ^0\n        return\n    for r in [0:len(state[3]) - 1]:\n        (\"Round \" + str(r + 1)) ^0\n        for m in [0:len(state[3][r]) - 1]:\n            state[3][r][m] => match\n            \"\" => score\n            r * 10 + m => key\n            if key in state[4]:\n                \"  \" + str(state[4][key][0]) + \"-\" + str(state[4][key][1]) => score\n            if match[0] == -1 or match[1] == -1:\n                (\"  \" + str(m + 1) + \". \" + entrant(state, match[0] + match[1] + 1) + \" rests\") ^0\n            else:\n                entrant(state, match[1]) => away\n                if score != \"\":\n                    padded(away, 12) + score => away\n                (\"  \" + str(m + 1) + \". \" + padded(entrant(state, match[0]), 12) + \" - \" + away) ^0\n\ndef enter_result(state):\n    if len(state[2]) == 0:\n        \"No league yet.\" ^0\n        return\n    while True:\n        trim(input(\"round (1 to \" + str(len(state[3])) + \")> \")) => answer\n        if answer == \"\":\n            \"Cancelled.\" ^0\n            return\n        number(answer) => r\n        if r >= 1 and r <= len(state[3]):\n            break\n        (\"Type a round from 1 to \" + str(len(state[3])) + \".\") ^0\n    state[3][r - 1] => matches\n    while True:\n        trim(input(\"match (1 to \" + str(len(matches)) + \")> \")) => answer\n        if answer == \"\":\n            \"Cancelled.\" ^0\n            return\n        number(answer) => m\n        if m >= 1 and m <= len(matches):\n            if matches[m - 1][0] == -1 or matches[m - 1][1] == -1:\n                \"That one is a rest, with nothing to score.\" ^0\n            else:\n                break\n        else:\n            (\"Type a match from 1 to \" + str(len(matches)) + \".\") ^0\n    matches[m - 1] => match\n    while True:\n        trim(input(entrant(state, match[0]) + \" - \" + entrant(state, match[1]) + \", score (like 2-1)> \")) => answer\n        if answer == \"\":\n            \"Cancelled.\" ^0\n            return\n        0 => k\n        while k < len(answer) and answer[k] != \"-\":\n            k + 1 => k\n        number(trim(answer[0:k])) => a\n        -1 => b\n        if k < len(answer):\n            number(trim(answer[k + 1:len(answer)])) => b\n        if a >= 0 and b >= 0 and a <= 99 and b <= 99:\n            break\n        \"Type the two scores with - between, like 2-1.\" ^0\n    [a, b] => state[4][(r - 1) * 10 + m - 1]\n    (\"Round \" + str(r) + \": \" + entrant(state, match[0]) + \" \" + str(a) + \"-\" + str(b) + \" \" + entrant(state, match[1]) + \".\") ^0\n\ndef table(state):\n    if len(state[2]) == 0:\n        \"No league yet.\" ^0\n        return\n    [] => results\n    for r in [0:len(state[3]) - 1]:\n        for m in [0:len(state[3][r]) - 1]:\n            r * 10 + m => key\n            if key in state[4]:\n                state[3][r][m] => match\n                results + [[match[0], match[1], state[4][key][0], state[4][key][1]]] => results\n    league.standings(state[2], results) => rows\n    \"     team          P  W  D  L   GF  GA   GD  Pts\" ^0\n    for i in [0:len(rows) - 1]:\n        rows[i] => row\n        str(row[5] - row[6]) => gd\n        if row[5] - row[6] > 0:\n            \"+\" + gd => gd\n        (right(str(i + 1), 3) + \". \" + padded(row[0], 12) + right(str(row[1]), 3) + right(str(row[2]), 3) + right(str(row[3]), 3) + right(str(row[4]), 3) + right(str(row[5]), 5) + right(str(row[6]), 4) + right(gd, 5) + right(str(row[7]), 5)) ^0\n    (str(len(results)) + \" of \" + str(int(len(state[2]) * (len(state[2]) - 1) / 2)) + \" matches played. Win 3, draw 1; ties go to goal difference, then goals scored.\") ^0\n\ndef sample(state):\n    [[\"Ana\", 72], [\"Ben\", 55], [\"Chloe\", 81], [\"Dev\", 64], [\"Ema\", 47],\n     [\"Finn\", 90], [\"Gus\", 38], [\"Hana\", 69], [\"Ivan\", 58], [\"Jo\", 76]] => state[0]\n    [] => state[1]\n    \"Ten sample players, rated 38 to 90.\" ^0\n\n\"== Teams and round robin ==\" ^0\n\"Split players into even teams, then play a league where everyone meets everyone once.\" ^0\n# [players, teams, league entrants, schedule, results by round * 10 + match]\n[[], [], [], [], {}] => state\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        add_players(state)\n    elif choice == \"2\":\n        make_teams(state)\n    elif choice == \"3\":\n        start_league(state)\n    elif choice == \"4\":\n        show_schedule(state)\n    elif choice == \"5\":\n        enter_result(state)\n    elif choice == \"6\":\n        table(state)\n    elif choice == \"7\":\n        sample(state)\n    elif choice == \"8\":\n        False => running\n    else:\n        \"Pick a number from 1 to 8.\" ^0\n\"Bye.\" ^0\n",
      "python": "import teams\nimport league\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef number(s):\n    if s == \"\" or len(s) > 6:\n        return -1\n    n = 0\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef padded(s, width):\n    while len(s) < width:\n        s = s + \" \"\n    return s\n\ndef right(s, width):\n    while len(s) < width:\n        s = \" \" + s\n    return s\n\ndef add_players(state):\n    print(\"One player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.\")\n    while len(state[0]) < 16:\n        line = trim(input(\"player> \"))\n        if line == \"\":\n            break\n        k = len(line) - 1\n        while k > 0 and line[k] != \" \":\n            k = k - 1\n        name = trim(line[0:k])\n        rating = number(line[k + 1:len(line)])\n        taken = False\n        for p in state[0]:\n            if p[0] == name:\n                taken = True\n        if k <= 0 or name == \"\" or rating < 1 or rating > 100:\n            print(\"Type a name, a space and a rating from 1 to 100.\")\n        elif len(name) > 12:\n            print(\"Keep names to 12 characters.\")\n        elif taken:\n            print(\"There is a player called \" + name + \" already.\")\n        else:\n            state[0] = state[0] + [[name, rating]]\n    if len(state[0]) == 16:\n        print(\"16 players is the most.\")\n    print(str(len(state[0])) + \" players.\")\n\ndef show_team(label, members):\n    s = \"\"\n    for p in members:\n        if s != \"\":\n            s = s + \", \"\n        s = s + p[0] + \" \" + str(p[1])\n    print(padded(label, 8) + right(str(teams.total(members)), 4) + \"  \" + s)\n\ndef make_teams(state):\n    if len(state[0]) < 4:\n        print(\"Add at least 4 players first.\")\n        return\n    while True:\n        answer = trim(input(\"how many teams (2 to 4)> \"))\n        if answer == \"\":\n            print(\"Cancelled.\")\n            return\n        k = number(answer)\n        if k >= 2 and k <= 4 and k * 2 <= len(state[0]):\n            break\n        print(\"Type 2, 3 or 4 - at least two players a team.\")\n    if k == 2:\n        ts = teams.two_teams(state[0])\n        print(\"The most even split there is, found by trying the choices and dropping those that pass half:\")\n    else:\n        ts = teams.many_teams(state[0], k)\n        print(\"Strongest first, each to the weakest team so far - close, though not always the best:\")\n    state[1] = ts\n    for t in range(0, len(ts)):\n        show_team(\"Team \" + str(t + 1), ts[t])\n    lo = teams.total(ts[0])\n    hi = lo\n    for t in ts:\n        if teams.total(t) < lo:\n            lo = teams.total(t)\n        if teams.total(t) > hi:\n            hi = teams.total(t)\n    print(\"Strongest team minus weakest: \" + str(hi - lo) + \".\")\n\ndef start_league(state):\n    line = trim(input(\"entrants, separated by commas (nothing to use the teams)> \"))\n    names = []\n    if line == \"\":\n        if len(state[1]) == 0:\n            print(\"There are no teams yet - make some, or type the entrants.\")\n            return\n        for t in range(1, len(state[1])+1):\n            names = names + [\"Team \" + str(t)]\n    else:\n        word = \"\"\n        for c in line + \",\":\n            if c == \",\":\n                w = trim(word)\n                if w != \"\":\n                    names = names + [w]\n                word = \"\"\n            else:\n                word = word + c\n    for i in range(0, len(names)):\n        if len(names[i]) > 12:\n            print(\"Keep names to 12 characters.\")\n            return\n        for j in range(0, i):\n            if names[j] == names[i]:\n                print(names[i] + \" is there twice.\")\n                return\n    if len(names) < 3 or len(names) > 8:\n        print(\"A league needs 3 to 8 entrants.\")\n        return\n    state[2] = names\n    state[3] = league.schedule(names)\n    state[4] = {}\n    per_round = int(len(names) / 2)\n    s = str(len(names)) + \" entrants: \" + str(len(state[3])) + \" rounds, each with \" + str(per_round) + \" match\"\n    if per_round > 1:\n        s = s + \"es\"\n    if len(names) % 2 == 1:\n        s = s + \" and one entrant resting\"\n    print(s + \".\")\n    show_schedule(state)\n\ndef entrant(state, i):\n    if i == -1:\n        return \"(rest)\"\n    return state[2][i]\n\ndef show_schedule(state):\n    if len(state[2]) == 0:\n        print(\"No league yet.\")\n        return\n    for r in range(0, len(state[3])):\n        print(\"Round \" + str(r + 1))\n        for m in range(0, len(state[3][r])):\n            match = state[3][r][m]\n            score = \"\"\n            key = r * 10 + m\n            if key in state[4]:\n                score = \"  \" + str(state[4][key][0]) + \"-\" + str(state[4][key][1])\n            if match[0] == -1 or match[1] == -1:\n                print(\"  \" + str(m + 1) + \". \" + entrant(state, match[0] + match[1] + 1) + \" rests\")\n            else:\n                away = entrant(state, match[1])\n                if score != \"\":\n                    away = padded(away, 12) + score\n                print(\"  \" + str(m + 1) + \". \" + padded(entrant(state, match[0]), 12) + \" - \" + away)\n\ndef enter_result(state):\n    if len(state[2]) == 0:\n        print(\"No league yet.\")\n        return\n    while True:\n        answer = trim(input(\"round (1 to \" + str(len(state[3])) + \")> \"))\n        if answer == \"\":\n            print(\"Cancelled.\")\n            return\n        r = number(answer)\n        if r >= 1 and r <= len(state[3]):\n            break\n        print(\"Type a round from 1 to \" + str(len(state[3])) + \".\")\n    matches = state[3][r - 1]\n    while True:\n        answer = trim(input(\"match (1 to \" + str(len(matches)) + \")> \"))\n        if answer == \"\":\n            print(\"Cancelled.\")\n            return\n        m = number(answer)\n        if m >= 1 and m <= len(matches):\n            if matches[m - 1][0] == -1 or matches[m - 1][1] == -1:\n                print(\"That one is a rest, with nothing to score.\")\n            else:\n                break\n        else:\n            print(\"Type a match from 1 to \" + str(len(matches)) + \".\")\n    match = matches[m - 1]\n    while True:\n        answer = trim(input(entrant(state, match[0]) + \" - \" + entrant(state, match[1]) + \", score (like 2-1)> \"))\n        if answer == \"\":\n            print(\"Cancelled.\")\n            return\n        k = 0\n        while k < len(answer) and answer[k] != \"-\":\n            k = k + 1\n        a = number(trim(answer[0:k]))\n        b = -1\n        if k < len(answer):\n            b = number(trim(answer[k + 1:len(answer)]))\n        if a >= 0 and b >= 0 and a <= 99 and b <= 99:\n            break\n        print(\"Type the two scores with - between, like 2-1.\")\n    state[4][(r - 1) * 10 + m - 1] = [a, b]\n    print(\"Round \" + str(r) + \": \" + entrant(state, match[0]) + \" \" + str(a) + \"-\" + str(b) + \" \" + entrant(state, match[1]) + \".\")\n\ndef table(state):\n    if len(state[2]) == 0:\n        print(\"No league yet.\")\n        return\n    results = []\n    for r in range(0, len(state[3])):\n        for m in range(0, len(state[3][r])):\n            key = r * 10 + m\n            if key in state[4]:\n                match = state[3][r][m]\n                results = results + [[match[0], match[1], state[4][key][0], state[4][key][1]]]\n    rows = league.standings(state[2], results)\n    print(\"     team          P  W  D  L   GF  GA   GD  Pts\")\n    for i in range(0, len(rows)):\n        row = rows[i]\n        gd = str(row[5] - row[6])\n        if row[5] - row[6] > 0:\n            gd = \"+\" + gd\n        print(right(str(i + 1), 3) + \". \" + padded(row[0], 12) + right(str(row[1]), 3) + right(str(row[2]), 3) + right(str(row[3]), 3) + right(str(row[4]), 3) + right(str(row[5]), 5) + right(str(row[6]), 4) + right(gd, 5) + right(str(row[7]), 5))\n    print(str(len(results)) + \" of \" + str(int(len(state[2]) * (len(state[2]) - 1) / 2)) + \" matches played. Win 3, draw 1; ties go to goal difference, then goals scored.\")\n\ndef sample(state):\n    state[0] = [[\"Ana\", 72], [\"Ben\", 55], [\"Chloe\", 81], [\"Dev\", 64], [\"Ema\", 47], [\"Finn\", 90], [\"Gus\", 38], [\"Hana\", 69], [\"Ivan\", 58], [\"Jo\", 76]]\n    state[1] = []\n    print(\"Ten sample players, rated 38 to 90.\")\n\nprint(\"== Teams and round robin ==\")\nprint(\"Split players into even teams, then play a league where everyone meets everyone once.\")\nstate = [[], [], [], [], {}]\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        add_players(state)\n    elif choice == \"2\":\n        make_teams(state)\n    elif choice == \"3\":\n        start_league(state)\n    elif choice == \"4\":\n        show_schedule(state)\n    elif choice == \"5\":\n        enter_result(state)\n    elif choice == \"6\":\n        table(state)\n    elif choice == \"7\":\n        sample(state)\n    elif choice == \"8\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 8.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "teams.eml",
      "eml": "# P058 teams and round robin - splitting players into teams of even\n# strength. A player is [name, rating], rating 1 to 100.\n\ndef total(players):\n    0 => s\n    for p in players:\n        s + p[1] => s\n    return s\n\ndef best_half(ratings, size):\n    # Which `size` of the ratings to put in the first team so that its sum\n    # comes as close to half the total as it can without passing it - the\n    # other team then has the same sum or a little more. Backtracking, as in\n    # the corpus case subset-sum-backtracking: at each player branch on in\n    # or out, and drop a branch once its sum has passed half, which only\n    # works because every rating is positive. Returns [chosen indexes, sum].\n    0 => all\n    for r in ratings:\n        all + r => all\n    int(all / 2) => cap\n    # best[0]: the chosen indexes so far, best[1]: their sum\n    [[], -1] => best\n    search(ratings, size, cap, 0, [], 0, best)\n    return best\n\ndef search(ratings, size, cap, i, chosen, sum, best):\n    if sum > cap:\n        return\n    if len(chosen) == size:\n        if sum > best[1]:\n            chosen => best[0]\n            sum => best[1]\n        return\n    if i == len(ratings) or len(chosen) + len(ratings) - i < size:\n        return\n    if best[1] == cap:\n        return\n    search(ratings, size, cap, i + 1, chosen + [i], sum + ratings[i], best)\n    search(ratings, size, cap, i + 1, chosen, sum, best)\n\ndef two_teams(players):\n    # [team one, team two], as even as the ratings allow: team one is the\n    # side whose sum is at most half, as close to half as it can be. With an\n    # odd count that side may be the one with fewer players or the one with\n    # more, so both sizes are searched.\n    [] => ratings\n    for p in players:\n        ratings + [p[1]] => ratings\n    best_half(ratings, int(len(players) / 2)) => b\n    if len(players) % 2 == 1:\n        best_half(ratings, int(len(players) / 2) + 1) => b2\n        if b2[1] > b[1]:\n            b2 => b\n    [] => one\n    [] => two\n    for i in [0:len(players) - 1]:\n        False => mine\n        for j in b[0]:\n            if j == i:\n                True => mine\n        if mine:\n            one + [players[i]] => one\n        else:\n            two + [players[i]] => two\n    return [one, two]\n\ndef many_teams(players, k):\n    # k teams by a quick rule, not a search: strongest player first, each\n    # to the team with the lowest sum so far among those with the fewest\n    # players. Close, but not always the best split.\n    players[0:len(players)] => order\n    # sort by rating, highest first (insertion sort; equal ratings keep\n    # their order)\n    for i in [1:len(order) - 1]:\n        order[i] => p\n        i => j\n        while j > 0 and order[j - 1][1] < p[1]:\n            order[j - 1] => order[j]\n            j - 1 => j\n        p => order[j]\n    [] => out\n    for t in [1:k]:\n        out + [[]] => out\n    for p in order:\n        0 => pick\n        for t in [1:k - 1]:\n            if len(out[t]) < len(out[pick]) or (len(out[t]) == len(out[pick]) and total(out[t]) < total(out[pick])):\n                t => pick\n        out[pick] + [p] => out[pick]\n    return out\n",
      "python": "def total(players):\n    s = 0\n    for p in players:\n        s = s + p[1]\n    return s\n\ndef best_half(ratings, size):\n    all = 0\n    for r in ratings:\n        all = all + r\n    cap = int(all / 2)\n    best = [[], -1]\n    search(ratings, size, cap, 0, [], 0, best)\n    return best\n\ndef search(ratings, size, cap, i, chosen, sum, best):\n    if sum > cap:\n        return\n    if len(chosen) == size:\n        if sum > best[1]:\n            best[0] = chosen\n            best[1] = sum\n        return\n    if i == len(ratings) or len(chosen) + len(ratings) - i < size:\n        return\n    if best[1] == cap:\n        return\n    search(ratings, size, cap, i + 1, chosen + [i], sum + ratings[i], best)\n    search(ratings, size, cap, i + 1, chosen, sum, best)\n\ndef two_teams(players):\n    ratings = []\n    for p in players:\n        ratings = ratings + [p[1]]\n    b = best_half(ratings, int(len(players) / 2))\n    if len(players) % 2 == 1:\n        b2 = best_half(ratings, int(len(players) / 2) + 1)\n        if b2[1] > b[1]:\n            b = b2\n    one = []\n    two = []\n    for i in range(0, len(players)):\n        mine = False\n        for j in b[0]:\n            if j == i:\n                mine = True\n        if mine:\n            one = one + [players[i]]\n        else:\n            two = two + [players[i]]\n    return [one, two]\n\ndef many_teams(players, k):\n    order = players[0:len(players)]\n    for i in range(1, len(order)):\n        p = order[i]\n        j = i\n        while j > 0 and order[j - 1][1] < p[1]:\n            order[j] = order[j - 1]\n            j = j - 1\n        order[j] = p\n    out = []\n    for t in range(1, k+1):\n        out = out + [[]]\n    for p in order:\n        pick = 0\n        for t in range(1, k):\n            if len(out[t]) < len(out[pick]) or len(out[t]) == len(out[pick]) and total(out[t]) < total(out[pick]):\n                pick = t\n        out[pick] = out[pick] + [p]\n    return out\n"
    },
    {
      "name": "league.eml",
      "eml": "# P058 teams and round robin - the schedule and the table.\n\ndef schedule(names):\n    # The circle method: put the entrants round a circle with the first one\n    # fixed in place, pair them across, and turn the others one step for\n    # each round. With an odd count a \"bye\" joins the circle, and whoever\n    # meets it rests that round. Everyone meets everyone once in n - 1\n    # rounds. Returns rounds of matches [home, away] as indexes, -1 = bye.\n    [] => idx\n    for i in [0:len(names) - 1]:\n        idx + [i] => idx\n    if len(idx) % 2 == 1:\n        idx + [-1] => idx\n    len(idx) => n\n    [] => rounds\n    for r in [1:n - 1]:\n        [] => matches\n        for m in [0:int(n / 2) - 1]:\n            idx[m] => a\n            idx[n - 1 - m] => b\n            # the fixed entrant switches home and away each round\n            if m == 0 and r % 2 == 0:\n                matches + [[b, a]] => matches\n            else:\n                matches + [[a, b]] => matches\n        rounds + [matches] => rounds\n        # turn: the last moves to second place, the rest shift along\n        [idx[0], idx[n - 1]] + idx[1:n - 1] => idx\n    return rounds\n\ndef standings(names, results):\n    # results: [home, away, home goals, away goals] for each match played.\n    # Rows [name, played, won, drawn, lost, for, against, points], sorted by\n    # points, then goal difference, then goals scored, then name.\n    [] => rows\n    for nm in names:\n        rows + [[nm, 0, 0, 0, 0, 0, 0, 0]] => rows\n    for res in results:\n        record(rows[res[0]], res[2], res[3])\n        record(rows[res[1]], res[3], res[2])\n    for i in [1:len(rows) - 1]:\n        rows[i] => row\n        i => j\n        while j > 0 and ahead(row, rows[j - 1]):\n            rows[j - 1] => rows[j]\n            j - 1 => j\n        row => rows[j]\n    return rows\n\ndef record(row, scored, conceded):\n    row[1] + 1 => row[1]\n    row[5] + scored => row[5]\n    row[6] + conceded => row[6]\n    if scored > conceded:\n        row[2] + 1 => row[2]\n        row[7] + 3 => row[7]\n    elif scored == conceded:\n        row[3] + 1 => row[3]\n        row[7] + 1 => row[7]\n    else:\n        row[4] + 1 => row[4]\n\ndef ahead(a, b):\n    if a[7] != b[7]:\n        return a[7] > b[7]\n    if a[5] - a[6] != b[5] - b[6]:\n        return a[5] - a[6] > b[5] - b[6]\n    if a[5] != b[5]:\n        return a[5] > b[5]\n    return a[0] < b[0]\n",
      "python": "def schedule(names):\n    idx = []\n    for i in range(0, len(names)):\n        idx = idx + [i]\n    if len(idx) % 2 == 1:\n        idx = idx + [-1]\n    n = len(idx)\n    rounds = []\n    for r in range(1, n):\n        matches = []\n        for m in range(0, int(n / 2)):\n            a = idx[m]\n            b = idx[n - 1 - m]\n            if m == 0 and r % 2 == 0:\n                matches = matches + [[b, a]]\n            else:\n                matches = matches + [[a, b]]\n        rounds = rounds + [matches]\n        idx = [idx[0], idx[n - 1]] + idx[1:n - 1]\n    return rounds\n\ndef standings(names, results):\n    rows = []\n    for nm in names:\n        rows = rows + [[nm, 0, 0, 0, 0, 0, 0, 0]]\n    for res in results:\n        record(rows[res[0]], res[2], res[3])\n        record(rows[res[1]], res[3], res[2])\n    for i in range(1, len(rows)):\n        row = rows[i]\n        j = i\n        while j > 0 and ahead(row, rows[j - 1]):\n            rows[j] = rows[j - 1]\n            j = j - 1\n        rows[j] = row\n    return rows\n\ndef record(row, scored, conceded):\n    row[1] = row[1] + 1\n    row[5] = row[5] + scored\n    row[6] = row[6] + conceded\n    if scored > conceded:\n        row[2] = row[2] + 1\n        row[7] = row[7] + 3\n    elif scored == conceded:\n        row[3] = row[3] + 1\n        row[7] = row[7] + 1\n    else:\n        row[4] = row[4] + 1\n\ndef ahead(a, b):\n    if a[7] != b[7]:\n        return a[7] > b[7]\n    if a[5] - a[6] != b[5] - b[6]:\n        return a[5] - a[6] > b[5] - b[6]\n    if a[5] != b[5]:\n        return a[5] > b[5]\n    return a[0] < b[0]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n3\n4\n5\n6\n1\nAna 72\nBen\nCy 0\nDee 101\nA very long name here 50\nAna 60\nEve 55\nFay 61\n\n2\n1\n5\nx\n\n1\nGil 70\n\n2\n2\n3\nA, B\n3\nA, B, A\n3\nA, B, C\n5\n0\n4\nx\n\n5\n1\n3\n\n5\n1\n1\n2\n2-\na-b\n100-1\n\n6\n8\n",
      "screen": "== Teams and round robin ==\nSplit players into even teams, then play a league where everyone meets everyone once.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 0\nPick a number from 1 to 8.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> x\nPick a number from 1 to 8.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 2\nAdd at least 4 players first.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 3\nentrants, separated by commas (nothing to use the teams)> 4\nA league needs 3 to 8 entrants.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nNo league yet.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 6\nNo league yet.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 1\nOne player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.\nplayer> Ana 72\nplayer> Ben\nType a name, a space and a rating from 1 to 100.\nplayer> Cy 0\nType a name, a space and a rating from 1 to 100.\nplayer> Dee 101\nType a name, a space and a rating from 1 to 100.\nplayer> A very long name here 50\nKeep names to 12 characters.\nplayer> Ana 60\nThere is a player called Ana already.\nplayer> Eve 55\nplayer> Fay 61\nplayer> \n3 players.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 2\nAdd at least 4 players first.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 1\nOne player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.\nplayer> 5\nType a name, a space and a rating from 1 to 100.\nplayer> x\nType a name, a space and a rating from 1 to 100.\nplayer> \n3 players.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 1\nOne player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.\nplayer> Gil 70\nplayer> \n4 players.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 2\nhow many teams (2 to 4)> 2\nThe most even split there is, found by trying the choices and dropping those that pass half:\nTeam 1   127  Ana 72, Eve 55\nTeam 2   131  Fay 61, Gil 70\nStrongest team minus weakest: 4.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 3\nentrants, separated by commas (nothing to use the teams)> A, B\nA league needs 3 to 8 entrants.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 3\nentrants, separated by commas (nothing to use the teams)> A, B, A\nA is there twice.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 3\nentrants, separated by commas (nothing to use the teams)> A, B, C\n3 entrants: 3 rounds, each with 1 match and one entrant resting.\nRound 1\n  1. A rests\n  2. B            - C\nRound 2\n  1. C            - A\n  2. B rests\nRound 3\n  1. A            - B\n  2. C rests\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nround (1 to 3)> 0\nType a round from 1 to 3.\nround (1 to 3)> 4\nType a round from 1 to 3.\nround (1 to 3)> x\nType a round from 1 to 3.\nround (1 to 3)> \nCancelled.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nround (1 to 3)> 1\nmatch (1 to 2)> 3\nType a match from 1 to 2.\nmatch (1 to 2)> \nCancelled.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nround (1 to 3)> 1\nmatch (1 to 2)> 1\nThat one is a rest, with nothing to score.\nmatch (1 to 2)> 2\nB - C, score (like 2-1)> 2-\nType the two scores with - between, like 2-1.\nB - C, score (like 2-1)> a-b\nType the two scores with - between, like 2-1.\nB - C, score (like 2-1)> 100-1\nType the two scores with - between, like 2-1.\nB - C, score (like 2-1)> \nCancelled.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 6\n     team          P  W  D  L   GF  GA   GD  Pts\n  1. A             0  0  0  0    0   0    0    0\n  2. B             0  0  0  0    0   0    0    0\n  3. C             0  0  0  0    0   0    0    0\n0 of 3 matches played. Win 3, draw 1; ties go to goal difference, then goals scored.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 8\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "7\n2\n2\n2\n3\n3\n\n3\nLions, Tigers, Bears, Wolves, Hawks\n5\n1\n2\n2-1\n5\n1\n3\n0-0\n5\n2\n1\n3-1\n5\n2\n3\n1-1\n4\n6\n8\n",
      "screen": "== Teams and round robin ==\nSplit players into even teams, then play a league where everyone meets everyone once.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 7\nTen sample players, rated 38 to 90.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 2\nhow many teams (2 to 4)> 2\nThe most even split there is, found by trying the choices and dropping those that pass half:\nTeam 1   325  Ana 72, Ben 55, Dev 64, Ivan 58, Jo 76\nTeam 2   325  Chloe 81, Ema 47, Finn 90, Gus 38, Hana 69\nStrongest team minus weakest: 0.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 2\nhow many teams (2 to 4)> 3\nStrongest first, each to the weakest team so far - close, though not always the best:\nTeam 1   239  Finn 90, Dev 64, Ema 47, Gus 38\nTeam 2   205  Chloe 81, Hana 69, Ben 55\nTeam 3   206  Jo 76, Ana 72, Ivan 58\nStrongest team minus weakest: 34.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 3\nentrants, separated by commas (nothing to use the teams)> \n3 entrants: 3 rounds, each with 1 match and one entrant resting.\nRound 1\n  1. Team 1 rests\n  2. Team 2       - Team 3\nRound 2\n  1. Team 3       - Team 1\n  2. Team 2 rests\nRound 3\n  1. Team 1       - Team 2\n  2. Team 3 rests\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 3\nentrants, separated by commas (nothing to use the teams)> Lions, Tigers, Bears, Wolves, Hawks\n5 entrants: 5 rounds, each with 2 matches and one entrant resting.\nRound 1\n  1. Lions rests\n  2. Tigers       - Hawks\n  3. Bears        - Wolves\nRound 2\n  1. Hawks        - Lions\n  2. Wolves rests\n  3. Tigers       - Bears\nRound 3\n  1. Lions        - Wolves\n  2. Hawks        - Bears\n  3. Tigers rests\nRound 4\n  1. Bears        - Lions\n  2. Wolves       - Tigers\n  3. Hawks rests\nRound 5\n  1. Lions        - Tigers\n  2. Bears rests\n  3. Wolves       - Hawks\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nround (1 to 5)> 1\nmatch (1 to 3)> 2\nTigers - Hawks, score (like 2-1)> 2-1\nRound 1: Tigers 2-1 Hawks.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nround (1 to 5)> 1\nmatch (1 to 3)> 3\nBears - Wolves, score (like 2-1)> 0-0\nRound 1: Bears 0-0 Wolves.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nround (1 to 5)> 2\nmatch (1 to 3)> 1\nHawks - Lions, score (like 2-1)> 3-1\nRound 2: Hawks 3-1 Lions.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 5\nround (1 to 5)> 2\nmatch (1 to 3)> 3\nTigers - Bears, score (like 2-1)> 1-1\nRound 2: Tigers 1-1 Bears.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 4\nRound 1\n  1. Lions rests\n  2. Tigers       - Hawks         2-1\n  3. Bears        - Wolves        0-0\nRound 2\n  1. Hawks        - Lions         3-1\n  2. Wolves rests\n  3. Tigers       - Bears         1-1\nRound 3\n  1. Lions        - Wolves\n  2. Hawks        - Bears\n  3. Tigers rests\nRound 4\n  1. Bears        - Lions\n  2. Wolves       - Tigers\n  3. Hawks rests\nRound 5\n  1. Lions        - Tigers\n  2. Bears rests\n  3. Wolves       - Hawks\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 6\n     team          P  W  D  L   GF  GA   GD  Pts\n  1. Tigers        2  1  1  0    3   2   +1    4\n  2. Hawks         2  1  0  1    4   3   +1    3\n  3. Bears         2  0  2  0    1   1    0    2\n  4. Wolves        1  0  1  0    0   0    0    1\n  5. Lions         1  0  0  1    1   3   -2    0\n4 of 10 matches played. Win 3, draw 1; ties go to goal difference, then goals scored.\n\n1) add players  2) teams  3) start a league  4) schedule  5) result  6) table  7) sample  8) quit\nchoice> 8\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "subset-sum-backtracking",
      "caseId": "123-subset-sum-backtracking",
      "title": "Subset sum (backtracking)"
    }
  ],
  "updated": "2026-10-10"
}
