{
  "id": "P013",
  "slug": "bill-splitter",
  "key": "P013-bill-splitter",
  "title": "Bill splitter",
  "summary": "Share expenses among up to eight people - each paid by one, shared equally or in shares, split to the cent by largest remainder - see who owes whom, and settle up in the fewest transfers possible, found exactly by trying every group - from a text menu.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P013 - Bill splitter\n\nPeople share expenses: each expense is paid by one of them and shared by some\nor all of them, equally or in shares. The program splits every expense to the\ncent, shows what each person has paid and owes, and settles up in the fewest\ntransfers possible. Up to eight people; everything lives while the program\nruns.\n\n- `main.eml` - the menu and its questions, with their checks, and what the\n  screen shows\n- `split.eml` - splitting one expense to the cent, and each person's paid and\n  share totals\n- `settle.eml` - the fewest transfers that make everyone even\n- `text.eml` - trimming, lower case, whole numbers, words, and money\n\nSplitting to the cent: every part is first rounded down, and the cents left\nover go one each to the parts that lost the most in the rounding (largest\nremainder); on a tie - always the case for equal shares - to the person listed\nfirst. So the parts always add up to the expense, and the screen says who got\nthe leftover cents: 10.01 split three ways is 3.34, 3.34 and 3.33, and 64.31\nin shares 1:1:2:1 gives its one leftover cent to the double share, whose\nremainder is the largest.\n\nSettling up: people whose balances add up to zero among themselves can settle\ninside their own group, and a group of g people needs g - 1 transfers. So the\nfewest transfers is the number of people with a balance minus the largest\nnumber of zero-sum groups they can be split into. With at most eight people\nthat is found exactly by trying every subset (at most 256), and each group\nthen settles by the largest debt paying the largest credit. Paying the largest\ndebt to the largest credit across everyone at once is not always enough: when\nit needs more transfers, the screen says how many.\n\nWhat is checked: a name has at most 20 characters and is not already taken in\nany case; an expense needs at least two people, a description of at most 30\ncharacters, and an amount written like 12.50 that is more than 0 and at most\n100,000.00; the payer is a person's number; the sharers are people's numbers,\neach once (nothing means everyone); the shares are one whole number from 1 to\n100 per sharer (nothing means equal). Anything else asks again; an empty\ndescription, amount or payer cancels.\n\nSessions: `sessions/basic.in` has four friends share a dinner equally, a taxi\nthree ways (two leftover cents), groceries in shares 1:1:2:1 (one leftover\ncent, to the largest remainder) and a museum visit two ways, then lists the\nexpenses, the balances and three transfers; `sessions/fewest-transfers.in`\nhas five people whose balances fall into two groups that each add up to zero,\nso three transfers settle everyone where paying largest debt to largest\ncredit would take four; `sessions/bad-input.in` asks for an expense, the\nbalances and settling up too early, types names that are empty, too long or\ntaken, amounts that are words, 0, negative or with three decimals, payers and\nsharers that do not exist or repeat, shares of the wrong number or out of\nrange, and goes past eight people.\n\nBuilt on the verified corpus cases `money-in-cents` (amounts kept in whole\ncents so the parts equal the whole) and `apportionment-remainder` (largest\nremainder: parts that add back up to the whole, with the tie broken in a\nstated order).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P013 bill splitter: people share expenses - each paid by one of them and\n# shared by some or all, equally or in shares, to the cent. The balances say\n# who is owed and who owes, and settling up finds the fewest transfers that\n# make everyone even.\nimport split\nimport settle\nimport text\n\n8 => most_people\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n\ndef contains(xs, v):\n    for x in xs:\n        if x == v:\n            return True\n    return False\n\ndef join_names(names):\n    # \"Ana\", \"Ana and Ben\", \"Ana, Ben and Chen\".\n    if len(names) == 1:\n        return names[0]\n    \"\" => out\n    for i in [0:len(names) - 2]:\n        if i > 0:\n            out + \", \" => out\n        out + names[i] => out\n    return out + \" and \" + names[len(names) - 1]\n\ndef show_expense(n, e, people):\n    (\"  \" + str(n) + \") \" + e[0] + \" \" + text.money(e[1]) + \", paid by \" + people[e[2]]) ^0\n    \"\" => line\n    False => weighted\n    for k in [0:len(e[3]) - 1]:\n        if k > 0:\n            line + \", \" => line\n        line + people[e[3][k]] + \" \" + text.money(e[5][k]) => line\n        if e[4][k] != 1:\n            True => weighted\n    if weighted:\n        \"\" => ratio\n        for k in [0:len(e[4]) - 1]:\n            if k > 0:\n                ratio + \":\" => ratio\n            ratio + str(e[4][k]) => ratio\n        line + \" (shares \" + ratio + \")\" => line\n    (\"     \" + line) ^0\n    [] => lucky\n    for k in [0:len(e[3]) - 1]:\n        if e[6][k]:\n            lucky + [people[e[3][k]]] => lucky\n    if len(lucky) > 0:\n        (\"     \" + plural(len(lucky), \"leftover cent\") + \" to \" + join_names(lucky)) ^0\n\ndef ask_text(prompt, longest):\n    # Text of at most longest characters, asked again until it fits; \"\" cancels.\n    while True:\n        text.trim(input(prompt)) => answer\n        if len(answer) <= longest:\n            return answer\n        (\"At most \" + str(longest) + \" characters.\") ^0\n\ndef ask_name(people):\n    # A new person's name, asked again until a good one is typed; \"\" cancels.\n    while True:\n        ask_text(\"name> \", 20) => name\n        if name == \"\":\n            return \"\"\n        0 => clash\n        for p in people:\n            if text.lower(p) == text.lower(name):\n                1 => clash\n                (\"There is already someone called \" + p + \".\") ^0\n        if clash == 0:\n            return name\n\ndef ask_amount():\n    # An amount in cents, asked again until a good one is typed; -1 cancels.\n    while True:\n        text.trim(input(\"amount> \")) => answer\n        if answer == \"\":\n            return 0 - 1\n        text.parse_cents(answer) => c\n        if c == 0 - 1:\n            \"Type an amount like 12.50, or nothing to cancel.\" ^0\n        elif c == 0:\n            \"An amount is more than 0.\" ^0\n        elif c > 10000000:\n            \"An amount is at most 100,000.00.\" ^0\n        else:\n            return c\n\ndef ask_payer(people):\n    # The index of the person who paid; -1 cancels.\n    while True:\n        text.trim(input(\"paid by> \")) => answer\n        if answer == \"\":\n            return 0 - 1\n        text.number(answer) => n\n        if n >= 1 and n <= len(people):\n            return n - 1\n        (\"Type a person's number from 1 to \" + str(len(people)) + \", or nothing to cancel.\") ^0\n\ndef ask_sharers(people):\n    # The indexes of the people who shared it, in the order typed; nothing\n    # typed means everyone.\n    while True:\n        text.words(input(\"shared by (numbers, nothing = everyone)> \")) => ws\n        [] => out\n        if len(ws) == 0:\n            for i in [0:len(people) - 1]:\n                out + [i] => out\n            return out\n        True => good\n        for w in ws:\n            text.number(w) => n\n            if n < 1 or n > len(people) or contains(out, n - 1):\n                False => good\n            else:\n                out + [n - 1] => out\n        if good:\n            return out\n        (\"Type the numbers of the people who shared it, 1 to \" + str(len(people)) + \", each once - or nothing for everyone.\") ^0\n\ndef ask_shares(count):\n    # One share for each of count people; nothing typed means equal shares.\n    [] => equal\n    for i in [1:count]:\n        equal + [1] => equal\n    if count == 1:\n        return equal\n    while True:\n        text.words(input(\"shares (nothing = equal)> \")) => ws\n        if len(ws) == 0:\n            return equal\n        [] => out\n        for w in ws:\n            text.number(w) => n\n            if n >= 1 and n <= 100:\n                out + [n] => out\n        if len(ws) == count and len(out) == count:\n            return out\n        (\"Type one share from 1 to 100 for each of the \" + str(count) + \" people, or nothing for equal shares.\") ^0\n\n[] => people\n[] => expenses\nTrue => running\nwhile running:\n    \"\" ^0\n    \"== Bill splitter ==\" ^0\n    \"1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\" ^0\n    text.trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        if len(people) == most_people:\n            (\"At most \" + str(most_people) + \" people.\") ^0\n        else:\n            ask_name(people) => name\n            if name == \"\":\n                \"Cancelled.\" ^0\n            else:\n                people + [name] => people\n                (\"Person \" + str(len(people)) + \": \" + name + \".\") ^0\n    elif choice == \"2\":\n        if len(people) < 2:\n            \"Add at least two people first.\" ^0\n        else:\n            ask_text(\"what for> \", 30) => what\n            0 - 1 => amount\n            0 - 1 => payer\n            if what != \"\":\n                ask_amount() => amount\n            if amount != 0 - 1:\n                \"\" => listing\n                for i in [0:len(people) - 1]:\n                    listing + \"  \" + str(i + 1) + \") \" + people[i] => listing\n                (\"People:\" + listing) ^0\n                ask_payer(people) => payer\n            if payer == 0 - 1:\n                \"Cancelled.\" ^0\n            else:\n                ask_sharers(people) => sharers\n                ask_shares(len(sharers)) => weights\n                split.split(amount, weights) => r\n                expenses + [[what, amount, payer, sharers, weights, r[0], r[1]]] => expenses\n                \"Added:\" ^0\n                show_expense(len(expenses), expenses[len(expenses) - 1], people)\n    elif choice == \"3\":\n        if len(expenses) == 0:\n            \"No expenses yet.\" ^0\n        else:\n            \"\" ^0\n            \"-- expenses --\" ^0\n            for i in [0:len(expenses) - 1]:\n                show_expense(i + 1, expenses[i], people)\n    elif choice == \"4\":\n        if len(people) == 0:\n            \"No people yet.\" ^0\n        else:\n            split.balances(len(people), expenses) => bals\n            \"\" ^0\n            \"-- balances --\" ^0\n            for i in [0:len(people) - 1]:\n                bals[i][0] - bals[i][1] => diff\n                \"even\" => status\n                if diff > 0:\n                    \"is owed \" + text.money(diff) => status\n                elif diff < 0:\n                    \"owes \" + text.money(0 - diff) => status\n                (\"  \" + (\"%-21s\" % people[i]) + \"paid \" + (\"%10s\" % text.money(bals[i][0])) + \"   share \" + (\"%10s\" % text.money(bals[i][1])) + \"   \" + status) ^0\n    elif choice == \"5\":\n        split.balances(len(people), expenses) => bals\n        [] => owed\n        for i in [0:len(people) - 1]:\n            if bals[i][0] != bals[i][1]:\n                owed + [[i, bals[i][0] - bals[i][1]]] => owed\n        if len(owed) == 0:\n            \"Everyone is even.\" ^0\n        else:\n            settle.fewest(owed) => plan\n            \"\" ^0\n            \"-- settle up --\" ^0\n            for t in plan:\n                (\"  \" + people[t[0]] + \" pays \" + people[t[1]] + \" \" + text.money(t[2])) ^0\n            if len(plan) == 1:\n                \"1 transfer settles everyone - the fewest possible\" => line\n            else:\n                str(len(plan)) + \" transfers settle everyone - the fewest possible\" => line\n            len(settle.pay_largest_first(owed)) => naive\n            if naive > len(plan):\n                line + \" (paying the largest debt to the largest credit first would take \" + str(naive) + \")\" => line\n            (line + \".\") ^0\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import split\nimport settle\nimport text\nmost_people = 8\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n\ndef contains(xs, v):\n    for x in xs:\n        if x == v:\n            return True\n    return False\n\ndef join_names(names):\n    if len(names) == 1:\n        return names[0]\n    out = \"\"\n    for i in range(0, len(names) - 2+1):\n        if i > 0:\n            out = out + \", \"\n        out = out + names[i]\n    return out + \" and \" + names[len(names) - 1]\n\ndef show_expense(n, e, people):\n    print(\"  \" + str(n) + \") \" + e[0] + \" \" + text.money(e[1]) + \", paid by \" + people[e[2]])\n    line = \"\"\n    weighted = False\n    for k in range(0, len(e[3])):\n        if k > 0:\n            line = line + \", \"\n        line = line + people[e[3][k]] + \" \" + text.money(e[5][k])\n        if e[4][k] != 1:\n            weighted = True\n    if weighted:\n        ratio = \"\"\n        for k in range(0, len(e[4])):\n            if k > 0:\n                ratio = ratio + \":\"\n            ratio = ratio + str(e[4][k])\n        line = line + \" (shares \" + ratio + \")\"\n    print(\"     \" + line)\n    lucky = []\n    for k in range(0, len(e[3])):\n        if e[6][k]:\n            lucky = lucky + [people[e[3][k]]]\n    if len(lucky) > 0:\n        print(\"     \" + plural(len(lucky), \"leftover cent\") + \" to \" + join_names(lucky))\n\ndef ask_text(prompt, longest):\n    while True:\n        answer = text.trim(input(prompt))\n        if len(answer) <= longest:\n            return answer\n        print(\"At most \" + str(longest) + \" characters.\")\n\ndef ask_name(people):\n    while True:\n        name = ask_text(\"name> \", 20)\n        if name == \"\":\n            return \"\"\n        clash = 0\n        for p in people:\n            if text.lower(p) == text.lower(name):\n                clash = 1\n                print(\"There is already someone called \" + p + \".\")\n        if clash == 0:\n            return name\n\ndef ask_amount():\n    while True:\n        answer = text.trim(input(\"amount> \"))\n        if answer == \"\":\n            return 0 - 1\n        c = text.parse_cents(answer)\n        if c == 0 - 1:\n            print(\"Type an amount like 12.50, or nothing to cancel.\")\n        elif c == 0:\n            print(\"An amount is more than 0.\")\n        elif c > 10000000:\n            print(\"An amount is at most 100,000.00.\")\n        else:\n            return c\n\ndef ask_payer(people):\n    while True:\n        answer = text.trim(input(\"paid by> \"))\n        if answer == \"\":\n            return 0 - 1\n        n = text.number(answer)\n        if n >= 1 and n <= len(people):\n            return n - 1\n        print(\"Type a person's number from 1 to \" + str(len(people)) + \", or nothing to cancel.\")\n\ndef ask_sharers(people):\n    while True:\n        ws = text.words(input(\"shared by (numbers, nothing = everyone)> \"))\n        out = []\n        if len(ws) == 0:\n            for i in range(0, len(people)):\n                out = out + [i]\n            return out\n        good = True\n        for w in ws:\n            n = text.number(w)\n            if n < 1 or n > len(people) or contains(out, n - 1):\n                good = False\n            else:\n                out = out + [n - 1]\n        if good:\n            return out\n        print(\"Type the numbers of the people who shared it, 1 to \" + str(len(people)) + \", each once - or nothing for everyone.\")\n\ndef ask_shares(count):\n    equal = []\n    for i in range(1, count+1):\n        equal = equal + [1]\n    if count == 1:\n        return equal\n    while True:\n        ws = text.words(input(\"shares (nothing = equal)> \"))\n        if len(ws) == 0:\n            return equal\n        out = []\n        for w in ws:\n            n = text.number(w)\n            if n >= 1 and n <= 100:\n                out = out + [n]\n        if len(ws) == count and len(out) == count:\n            return out\n        print(\"Type one share from 1 to 100 for each of the \" + str(count) + \" people, or nothing for equal shares.\")\n\npeople = []\nexpenses = []\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"== Bill splitter ==\")\n    print(\"1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\")\n    choice = text.trim(input(\"choice> \"))\n    if choice == \"1\":\n        if len(people) == most_people:\n            print(\"At most \" + str(most_people) + \" people.\")\n        else:\n            name = ask_name(people)\n            if name == \"\":\n                print(\"Cancelled.\")\n            else:\n                people = people + [name]\n                print(\"Person \" + str(len(people)) + \": \" + name + \".\")\n    elif choice == \"2\":\n        if len(people) < 2:\n            print(\"Add at least two people first.\")\n        else:\n            what = ask_text(\"what for> \", 30)\n            amount = 0 - 1\n            payer = 0 - 1\n            if what != \"\":\n                amount = ask_amount()\n            if amount != 0 - 1:\n                listing = \"\"\n                for i in range(0, len(people)):\n                    listing = listing + \"  \" + str(i + 1) + \") \" + people[i]\n                print(\"People:\" + listing)\n                payer = ask_payer(people)\n            if payer == 0 - 1:\n                print(\"Cancelled.\")\n            else:\n                sharers = ask_sharers(people)\n                weights = ask_shares(len(sharers))\n                r = split.split(amount, weights)\n                expenses = expenses + [[what, amount, payer, sharers, weights, r[0], r[1]]]\n                print(\"Added:\")\n                show_expense(len(expenses), expenses[len(expenses) - 1], people)\n    elif choice == \"3\":\n        if len(expenses) == 0:\n            print(\"No expenses yet.\")\n        else:\n            print(\"\")\n            print(\"-- expenses --\")\n            for i in range(0, len(expenses)):\n                show_expense(i + 1, expenses[i], people)\n    elif choice == \"4\":\n        if len(people) == 0:\n            print(\"No people yet.\")\n        else:\n            bals = split.balances(len(people), expenses)\n            print(\"\")\n            print(\"-- balances --\")\n            for i in range(0, len(people)):\n                diff = bals[i][0] - bals[i][1]\n                status = \"even\"\n                if diff > 0:\n                    status = \"is owed \" + text.money(diff)\n                elif diff < 0:\n                    status = \"owes \" + text.money(0 - diff)\n                print(\"  \" + \"%-21s\" % people[i] + \"paid \" + \"%10s\" % text.money(bals[i][0]) + \"   share \" + \"%10s\" % text.money(bals[i][1]) + \"   \" + status)\n    elif choice == \"5\":\n        bals = split.balances(len(people), expenses)\n        owed = []\n        for i in range(0, len(people)):\n            if bals[i][0] != bals[i][1]:\n                owed = owed + [[i, bals[i][0] - bals[i][1]]]\n        if len(owed) == 0:\n            print(\"Everyone is even.\")\n        else:\n            plan = settle.fewest(owed)\n            print(\"\")\n            print(\"-- settle up --\")\n            for t in plan:\n                print(\"  \" + people[t[0]] + \" pays \" + people[t[1]] + \" \" + text.money(t[2]))\n            if len(plan) == 1:\n                line = \"1 transfer settles everyone - the fewest possible\"\n            else:\n                line = str(len(plan)) + \" transfers settle everyone - the fewest possible\"\n            naive = len(settle.pay_largest_first(owed))\n            if naive > len(plan):\n                line = line + \" (paying the largest debt to the largest credit first would take \" + str(naive) + \")\"\n            print(line + \".\")\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "split.eml",
      "eml": "# P013 bill splitter - sharing one expense out to the cent, and what everyone\n# has paid and owes. Money is whole cents.\n\ndef split(amount, weights):\n    # The parts of amount in proportion to weights, adding up to amount\n    # exactly, as [parts, extra]. Every part is first rounded down; the cents\n    # left over then go one each to the parts that lost the most in the\n    # rounding (largest remainder), and on a tie to the one listed first.\n    # extra[i] says whether part i got one of those cents.\n    0 => total\n    for w in weights:\n        total + w => total\n    [] => parts\n    [] => rems\n    [] => extra\n    0 => given\n    for w in weights:\n        amount * w => x\n        int((x - x % total) / total) => part\n        parts + [part] => parts\n        rems + [x % total] => rems\n        extra + [False] => extra\n        given + part => given\n    amount - given => left\n    while left > 0:\n        0 - 1 => best\n        for i in [0:len(weights) - 1]:\n            if not extra[i] and (best == 0 - 1 or rems[i] > rems[best]):\n                i => best\n        parts[best] + 1 => parts[best]\n        True => extra[best]\n        left - 1 => left\n    return [parts, extra]\n\ndef balances(count, expenses):\n    # [paid, share] for each of count people. An expense is\n    # [what, amount, payer, sharers, weights, parts, extra].\n    [] => out\n    for i in [0:count - 1]:\n        out + [[0, 0]] => out\n    for e in expenses:\n        out[e[2]][0] + e[1] => out[e[2]][0]\n        for k in [0:len(e[3]) - 1]:\n            out[e[3][k]][1] + e[5][k] => out[e[3][k]][1]\n    return out\n",
      "python": "def split(amount, weights):\n    total = 0\n    for w in weights:\n        total = total + w\n    parts = []\n    rems = []\n    extra = []\n    given = 0\n    for w in weights:\n        x = amount * w\n        part = int((x - x % total) / total)\n        parts = parts + [part]\n        rems = rems + [x % total]\n        extra = extra + [False]\n        given = given + part\n    left = amount - given\n    while left > 0:\n        best = 0 - 1\n        for i in range(0, len(weights)):\n            if not extra[i] and (best == 0 - 1 or rems[i] > rems[best]):\n                best = i\n        parts[best] = parts[best] + 1\n        extra[best] = True\n        left = left - 1\n    return [parts, extra]\n\ndef balances(count, expenses):\n    out = []\n    for i in range(0, count):\n        out = out + [[0, 0]]\n    for e in expenses:\n        out[e[2]][0] = out[e[2]][0] + e[1]\n        for k in range(0, len(e[3])):\n            out[e[3][k]][1] = out[e[3][k]][1] + e[5][k]\n    return out\n"
    },
    {
      "name": "settle.eml",
      "eml": "# P013 bill splitter - settling up in as few transfers as possible. People\n# whose balances add up to zero among themselves can settle inside their own\n# group, and a group of g people needs g - 1 transfers. So the fewest\n# transfers is the number of people with a balance minus the largest number\n# of groups they can be split into, each adding up to zero. With at most\n# eight people that is found exactly by trying every subset: a subset is a\n# whole number whose bits say who is in it, and pow2[i] is person i's bit.\n\ndef has(mask, i, pow2):\n    return int(mask / pow2[i]) % 2 == 1\n\ndef groups(bal):\n    # bal is a list of [person, cents], none of them zero, adding up to 0.\n    # The largest set of zero-sum groups it splits into, as lists of entries.\n    len(bal) => k\n    [1] => pow2\n    for i in [1:k]:\n        pow2 + [pow2[i - 1] * 2] => pow2\n    pow2[k] => size\n    # sums[mask]: the balances in mask added up; most[mask]: the most\n    # zero-sum groups that mask can be split into, counting mask itself.\n    [0] => sums\n    [0] => most\n    for mask in [1:size - 1]:\n        0 => low\n        while not has(mask, low, pow2):\n            low + 1 => low\n        sums + [sums[mask - pow2[low]] + bal[low][1]] => sums\n        0 => b\n        for j in [0:k - 1]:\n            if has(mask, j, pow2) and most[mask - pow2[j]] > b:\n                most[mask - pow2[j]] => b\n        if sums[mask] == 0:\n            b + 1 => b\n        most + [b] => most\n    # Walk back from everyone, taking one person out at a time along a best\n    # path; each time what is left adds up to zero, the people taken out\n    # since the last time form one group.\n    [] => found\n    [] => current\n    size - 1 => mask\n    while mask > 0:\n        most[mask] => target\n        if sums[mask] == 0:\n            target - 1 => target\n        0 => j\n        while not (has(mask, j, pow2) and most[mask - pow2[j]] == target):\n            j + 1 => j\n        current + [bal[j]] => current\n        mask - pow2[j] => mask\n        if sums[mask] == 0:\n            found + [current] => found\n            [] => current\n    return found\n\ndef pay_largest_first(group):\n    # Transfers [from, to, cents] that settle a zero-sum group: the largest\n    # debt pays the largest credit, again and again, until all are even.\n    [] => left\n    for g in group:\n        left + [[g[0], g[1]]] => left\n    [] => out\n    while True:\n        0 - 1 => c\n        0 - 1 => d\n        for i in [0:len(left) - 1]:\n            if left[i][1] > 0 and (c == 0 - 1 or left[i][1] > left[c][1]):\n                i => c\n            if left[i][1] < 0 and (d == 0 - 1 or left[i][1] < left[d][1]):\n                i => d\n        if c == 0 - 1:\n            return out\n        left[c][1] => amount\n        if 0 - left[d][1] < amount:\n            0 - left[d][1] => amount\n        out + [[left[d][0], left[c][0], amount]] => out\n        left[c][1] - amount => left[c][1]\n        left[d][1] + amount => left[d][1]\n\ndef fewest(bal):\n    # The fewest transfers that settle bal: every zero-sum group settles\n    # inside itself.\n    [] => out\n    for g in groups(bal):\n        out + pay_largest_first(g) => out\n    return out\n",
      "python": "def has(mask, i, pow2):\n    return int(mask / pow2[i]) % 2 == 1\n\ndef groups(bal):\n    k = len(bal)\n    pow2 = [1]\n    for i in range(1, k+1):\n        pow2 = pow2 + [pow2[i - 1] * 2]\n    size = pow2[k]\n    sums = [0]\n    most = [0]\n    for mask in range(1, size):\n        low = 0\n        while not has(mask, low, pow2):\n            low = low + 1\n        sums = sums + [sums[mask - pow2[low]] + bal[low][1]]\n        b = 0\n        for j in range(0, k):\n            if has(mask, j, pow2) and most[mask - pow2[j]] > b:\n                b = most[mask - pow2[j]]\n        if sums[mask] == 0:\n            b = b + 1\n        most = most + [b]\n    found = []\n    current = []\n    mask = size - 1\n    while mask > 0:\n        target = most[mask]\n        if sums[mask] == 0:\n            target = target - 1\n        j = 0\n        while not (has(mask, j, pow2) and most[mask - pow2[j]] == target):\n            j = j + 1\n        current = current + [bal[j]]\n        mask = mask - pow2[j]\n        if sums[mask] == 0:\n            found = found + [current]\n            current = []\n    return found\n\ndef pay_largest_first(group):\n    left = []\n    for g in group:\n        left = left + [[g[0], g[1]]]\n    out = []\n    while True:\n        c = 0 - 1\n        d = 0 - 1\n        for i in range(0, len(left)):\n            if left[i][1] > 0 and (c == 0 - 1 or left[i][1] > left[c][1]):\n                c = i\n            if left[i][1] < 0 and (d == 0 - 1 or left[i][1] < left[d][1]):\n                d = i\n        if c == 0 - 1:\n            return out\n        amount = left[c][1]\n        if 0 - left[d][1] < amount:\n            amount = 0 - left[d][1]\n        out = out + [[left[d][0], left[c][0], amount]]\n        left[c][1] = left[c][1] - amount\n        left[d][1] = left[d][1] + amount\n\ndef fewest(bal):\n    out = []\n    for g in groups(bal):\n        out = out + pay_largest_first(g)\n    return out\n"
    },
    {
      "name": "text.eml",
      "eml": "# P013 bill splitter - reading what is typed and writing money. Money is\n# whole cents from the moment it is typed: \"12.50\" is read digit by digit into\n# 1250 and never passes through a float. The interpreter that checks every\n# session does not run string methods yet, so the text handling is written\n# out here.\n\ndef trim(s):\n    # s without the spaces at either end.\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 lower(s):\n    # s with A-Z turned into a-z; everything else as it was.\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => upper_letters\n    \"abcdefghijklmnopqrstuvwxyz\" => lower_letters\n    \"\" => out\n    for c in s:\n        c => d\n        for i in [0:25]:\n            if upper_letters[i] == c:\n                lower_letters[i] => d\n        out + d => out\n    return out\n\ndef number(s):\n    # The value of s if it is digits only (at least one), otherwise -1.\n    if s == \"\":\n        return 0 - 1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return 0 - 1\n        n * 10 + int(c) => n\n    return n\n\ndef words(s):\n    # The words of s, split at spaces and commas.\n    [] => out\n    \"\" => word\n    for c in s:\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out + [word] => out\n            \"\" => word\n        else:\n            word + c => word\n    if word != \"\":\n        out + [word] => out\n    return out\n\ndef parse_cents(s):\n    # The amount in s in whole cents, or -1 if s is not written like 4, 4.5\n    # or 4.50: digits, then a point and one or two digits if any.\n    0 => whole\n    0 => whole_digits\n    0 => frac\n    0 => frac_digits\n    False => point\n    for c in s:\n        if c == \".\":\n            if point:\n                return 0 - 1\n            True => point\n        elif c in \"0123456789\":\n            if point:\n                frac * 10 + int(c) => frac\n                frac_digits + 1 => frac_digits\n            else:\n                whole * 10 + int(c) => whole\n                whole_digits + 1 => whole_digits\n        else:\n            return 0 - 1\n    if whole_digits == 0:\n        return 0 - 1\n    if point and (frac_digits == 0 or frac_digits > 2):\n        return 0 - 1\n    if frac_digits == 1:\n        frac * 10 => frac\n    return whole * 100 + frac\n\ndef money(c):\n    # Whole cents (not negative) as 1,234.50, built from the digits.\n    str(c) => s\n    while len(s) < 3:\n        \"0\" + s => s\n    s[0:len(s) - 2] => whole\n    s[len(s) - 2:len(s)] => cents\n    \"\" => grouped\n    len(whole) => i\n    while i > 3:\n        \",\" + whole[i - 3:i] + grouped => grouped\n        i - 3 => i\n    return whole[0:i] + grouped + \".\" + cents\n",
      "python": "def 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 lower(s):\n    upper_letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    lower_letters = \"abcdefghijklmnopqrstuvwxyz\"\n    out = \"\"\n    for c in s:\n        d = c\n        for i in range(0, 26):\n            if upper_letters[i] == c:\n                d = lower_letters[i]\n        out = out + d\n    return out\n\ndef number(s):\n    if s == \"\":\n        return 0 - 1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return 0 - 1\n        n = n * 10 + int(c)\n    return n\n\ndef words(s):\n    out = []\n    word = \"\"\n    for c in s:\n        if c == \" \" or c == \",\":\n            if word != \"\":\n                out = out + [word]\n            word = \"\"\n        else:\n            word = word + c\n    if word != \"\":\n        out = out + [word]\n    return out\n\ndef parse_cents(s):\n    whole = 0\n    whole_digits = 0\n    frac = 0\n    frac_digits = 0\n    point = False\n    for c in s:\n        if c == \".\":\n            if point:\n                return 0 - 1\n            point = True\n        elif c in \"0123456789\":\n            if point:\n                frac = frac * 10 + int(c)\n                frac_digits = frac_digits + 1\n            else:\n                whole = whole * 10 + int(c)\n                whole_digits = whole_digits + 1\n        else:\n            return 0 - 1\n    if whole_digits == 0:\n        return 0 - 1\n    if point and (frac_digits == 0 or frac_digits > 2):\n        return 0 - 1\n    if frac_digits == 1:\n        frac = frac * 10\n    return whole * 100 + frac\n\ndef money(c):\n    s = str(c)\n    while len(s) < 3:\n        s = \"0\" + s\n    whole = s[0:len(s) - 2]\n    cents = s[len(s) - 2:len(s)]\n    grouped = \"\"\n    i = len(whole)\n    while i > 3:\n        grouped = \",\" + whole[i - 3:i] + grouped\n        i = i - 3\n    return whole[0:i] + grouped + \".\" + cents\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\n2\n4\n5\n1\n\n1\nA name that is much too long\nAna\n1\nana\nBen\n2\n\n2\nLunch\nabc\n0\n12.345\n-5\n12.5\n3\nx\n1\n1 1\n3\n2,1\n1\n0 1\na b\n\n3\n4\n5\n1\nChen\n1\nDev\n1\nEve\n1\nFay\n1\nGus\n1\nHal\n1\n6\n",
      "screen": "\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nAdd at least two people first.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 4\nNo people yet.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 5\nEveryone is even.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> \nCancelled.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> A name that is much too long\nAt most 20 characters.\nname> Ana\nPerson 1: Ana.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> ana\nThere is already someone called Ana.\nname> Ben\nPerson 2: Ben.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> \nCancelled.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Lunch\namount> abc\nType an amount like 12.50, or nothing to cancel.\namount> 0\nAn amount is more than 0.\namount> 12.345\nType an amount like 12.50, or nothing to cancel.\namount> -5\nType an amount like 12.50, or nothing to cancel.\namount> 12.5\nPeople:  1) Ana  2) Ben\npaid by> 3\nType a person's number from 1 to 2, or nothing to cancel.\npaid by> x\nType a person's number from 1 to 2, or nothing to cancel.\npaid by> 1\nshared by (numbers, nothing = everyone)> 1 1\nType the numbers of the people who shared it, 1 to 2, each once - or nothing for everyone.\nshared by (numbers, nothing = everyone)> 3\nType the numbers of the people who shared it, 1 to 2, each once - or nothing for everyone.\nshared by (numbers, nothing = everyone)> 2,1\nshares (nothing = equal)> 1\nType one share from 1 to 100 for each of the 2 people, or nothing for equal shares.\nshares (nothing = equal)> 0 1\nType one share from 1 to 100 for each of the 2 people, or nothing for equal shares.\nshares (nothing = equal)> a b\nType one share from 1 to 100 for each of the 2 people, or nothing for equal shares.\nshares (nothing = equal)> \nAdded:\n  1) Lunch 12.50, paid by Ana\n     Ben 6.25, Ana 6.25\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 3\n\n-- expenses --\n  1) Lunch 12.50, paid by Ana\n     Ben 6.25, Ana 6.25\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 4\n\n-- balances --\n  Ana                  paid      12.50   share       6.25   is owed 6.25\n  Ben                  paid       0.00   share       6.25   owes 6.25\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 5\n\n-- settle up --\n  Ben pays Ana 6.25\n1 transfer settles everyone - the fewest possible.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Chen\nPerson 3: Chen.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Dev\nPerson 4: Dev.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Eve\nPerson 5: Eve.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Fay\nPerson 6: Fay.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Gus\nPerson 7: Gus.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Hal\nPerson 8: Hal.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nAt most 8 people.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nAna\n1\nBen\n1\nChen\n1\nDev\n2\nDinner\n100\n1\n\n\n2\nTaxi\n10.01\n2\n1 2 3\n\n2\nGroceries\n64.31\n3\n\n1 1 2 1\n2\nMuseum\n45\n4\n2 4\n\n3\n4\n5\n6\n",
      "screen": "\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Ana\nPerson 1: Ana.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Ben\nPerson 2: Ben.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Chen\nPerson 3: Chen.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Dev\nPerson 4: Dev.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Dinner\namount> 100\nPeople:  1) Ana  2) Ben  3) Chen  4) Dev\npaid by> 1\nshared by (numbers, nothing = everyone)> \nshares (nothing = equal)> \nAdded:\n  1) Dinner 100.00, paid by Ana\n     Ana 25.00, Ben 25.00, Chen 25.00, Dev 25.00\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Taxi\namount> 10.01\nPeople:  1) Ana  2) Ben  3) Chen  4) Dev\npaid by> 2\nshared by (numbers, nothing = everyone)> 1 2 3\nshares (nothing = equal)> \nAdded:\n  2) Taxi 10.01, paid by Ben\n     Ana 3.34, Ben 3.34, Chen 3.33\n     2 leftover cents to Ana and Ben\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Groceries\namount> 64.31\nPeople:  1) Ana  2) Ben  3) Chen  4) Dev\npaid by> 3\nshared by (numbers, nothing = everyone)> \nshares (nothing = equal)> 1 1 2 1\nAdded:\n  3) Groceries 64.31, paid by Chen\n     Ana 12.86, Ben 12.86, Chen 25.73, Dev 12.86 (shares 1:1:2:1)\n     1 leftover cent to Chen\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Museum\namount> 45\nPeople:  1) Ana  2) Ben  3) Chen  4) Dev\npaid by> 4\nshared by (numbers, nothing = everyone)> 2 4\nshares (nothing = equal)> \nAdded:\n  4) Museum 45.00, paid by Dev\n     Ben 22.50, Dev 22.50\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 3\n\n-- expenses --\n  1) Dinner 100.00, paid by Ana\n     Ana 25.00, Ben 25.00, Chen 25.00, Dev 25.00\n  2) Taxi 10.01, paid by Ben\n     Ana 3.34, Ben 3.34, Chen 3.33\n     2 leftover cents to Ana and Ben\n  3) Groceries 64.31, paid by Chen\n     Ana 12.86, Ben 12.86, Chen 25.73, Dev 12.86 (shares 1:1:2:1)\n     1 leftover cent to Chen\n  4) Museum 45.00, paid by Dev\n     Ben 22.50, Dev 22.50\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 4\n\n-- balances --\n  Ana                  paid     100.00   share      41.20   is owed 58.80\n  Ben                  paid      10.01   share      63.70   owes 53.69\n  Chen                 paid      64.31   share      54.06   is owed 10.25\n  Dev                  paid      45.00   share      60.36   owes 15.36\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 5\n\n-- settle up --\n  Ben pays Ana 53.69\n  Dev pays Chen 10.25\n  Dev pays Ana 5.11\n3 transfers settle everyone - the fewest possible.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "fewest-transfers",
      "input": "1\nAna\n1\nBen\n1\nChen\n1\nDev\n1\nEve\n2\nConcert tickets\n60\n2\n2 3\n\n2\nBoat trip\n60\n1\n1 4 5\n\n2\nSnacks for Dev\n5\n5\n4\n4\n5\n6\n",
      "screen": "\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Ana\nPerson 1: Ana.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Ben\nPerson 2: Ben.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Chen\nPerson 3: Chen.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Dev\nPerson 4: Dev.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 1\nname> Eve\nPerson 5: Eve.\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Concert tickets\namount> 60\nPeople:  1) Ana  2) Ben  3) Chen  4) Dev  5) Eve\npaid by> 2\nshared by (numbers, nothing = everyone)> 2 3\nshares (nothing = equal)> \nAdded:\n  1) Concert tickets 60.00, paid by Ben\n     Ben 30.00, Chen 30.00\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Boat trip\namount> 60\nPeople:  1) Ana  2) Ben  3) Chen  4) Dev  5) Eve\npaid by> 1\nshared by (numbers, nothing = everyone)> 1 4 5\nshares (nothing = equal)> \nAdded:\n  2) Boat trip 60.00, paid by Ana\n     Ana 20.00, Dev 20.00, Eve 20.00\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 2\nwhat for> Snacks for Dev\namount> 5\nPeople:  1) Ana  2) Ben  3) Chen  4) Dev  5) Eve\npaid by> 5\nshared by (numbers, nothing = everyone)> 4\nAdded:\n  3) Snacks for Dev 5.00, paid by Eve\n     Dev 5.00\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 4\n\n-- balances --\n  Ana                  paid      60.00   share      20.00   is owed 40.00\n  Ben                  paid      60.00   share      30.00   is owed 30.00\n  Chen                 paid       0.00   share      30.00   owes 30.00\n  Dev                  paid       0.00   share      25.00   owes 25.00\n  Eve                  paid       5.00   share      20.00   owes 15.00\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 5\n\n-- settle up --\n  Dev pays Ana 25.00\n  Eve pays Ana 15.00\n  Chen pays Ben 30.00\n3 transfers settle everyone - the fewest possible (paying the largest debt to the largest credit first would take 4).\n\n== Bill splitter ==\n1) add person  2) add expense  3) expenses  4) balances  5) settle up  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "money-in-cents",
      "caseId": "191-money-in-cents",
      "title": "The same invoice, in floats and in cents"
    },
    {
      "slug": "apportionment-remainder",
      "caseId": "231-apportionment-remainder",
      "title": "Every part rounded correctly, and the total is wrong"
    }
  ],
  "updated": "2026-10-03"
}
