Statement sorter
MediumOnline Assessment
Sort a month of bank-statement lines into spending categories.
Each transaction is [memo, cents]: a free-text memo and a signed integer amount (negative for money out, positive for money in, such as a refund).
Each rule is [category, priority, includes, excludes], where includes is a non-empty list of words and excludes a (possibly empty) list of words. To compare words, split the memo into maximal runs of letters and digits (every other character is a separator) and lowercase them; rule words are compared in lowercase too, and must match a whole word ("gas" does not match "gasoline", and "7" does not match "711"). A rule matches a transaction if every include word appears in the memo's words and none of the exclude words does.
A transaction takes the category of the matching rule with the highest priority. If several matching rules share that priority, the one that appears earliest in rules wins. If no rule matches, the category is "uncategorized".
Transactions whose winning category is "ignore" (internal transfers) are left out of the report entirely. Other categories are totalled by net amount: refunds are added to the same category as the purchases they match.
Input: transactions, a list of [memo, cents], and rules, a list of [category, priority, includes, excludes].
Output: a list of [category, net_cents, count] for every category with at least one transaction, ordered by net_cents ascending (largest net spending first), then by category name ascending. count is the number of transactions in the category.
Example 1
- Input:
- [[["SHELL #4412 Gas",-4500],["Uber *Eats Order",-2300],["Uber trip",-1800],["7-Eleven 711",-650],["Savings Transfer",-50000],["Mystery Co",-999]],[["fuel",5,["gas"],[]],["fuel",5,["shell"],[]],["dining",6,["uber","eats"],[]],["transport",5,["uber"],[]],["convenience",3,["7"],[]],["ignore",9,["transfer"],[]]]]
- Output:
- [["fuel",-4500,1],["dining",-2300,1],["transport",-1800,1],["uncategorized",-999,1],["convenience",-650,1]]
- Explanation
- "SHELL #4412 Gas" matches the
gasrule (fuel). "Uber *Eats Order" matches both the dining rule (priority 6) and the transport rule (5), so dining wins. "Uber trip" is transport. "7-Eleven 711" has the word7(and711, which is a different word), so it is convenience. "Savings Transfer" is ignore and disappears. "Mystery Co" is uncategorized. Net: fuel -4500, dining -2300, transport -1800, uncategorized -999, convenience -650. Sorted by net ascending that is fuel, dining, transport, uncategorized, convenience.
Example 2
- Input:
- [[["Uber Eats refund",1200],["UBER EATS",-2600],["Uber ride",-900],["Rent May",-120000],["Rent refund",5000]],[["transport",5,["uber"],["eats"]],["dining",5,["uber"],[]],["housing",8,["rent"],["refund"]],["refunds",1,["refund"],[]]]]
- Output:
- [["housing",-120000,1],["dining",-1400,2],["transport",-900,1],["refunds",5000,1]]
- Explanation
- The transport rule excludes
eats, so "UBER EATS" and the refund line fall to the priority-5 dining rule and "Uber ride" is transport. "Uber Eats refund" (+1200) nets against "UBER EATS" (-2600) in dining: -1400, 2 transactions. "Rent refund" is excluded from housing (it hasrefund) and falls to the refunds rule. Housing -120000, dining -1400, transport -900, refunds +5000.
Example 3
- Input:
- [[["Cafe Blue Bottle",-500],["Blue Cafe",-450],["gasoline station",-3000],["Gas-N-Go",-2500]],[["coffee",4,["blue","bottle"],[]],["dining",4,["cafe"],[]],["fuel",4,["gas"],[]],["coffee",4,["blue"],[]]]]
- Output:
- [["uncategorized",-3000,1],["fuel",-2500,1],["coffee",-500,1],["dining",-450,1]]
- Explanation
- "Cafe Blue Bottle" matches the coffee rule (blue and bottle) and the dining rule at the same priority 4; coffee appears first in the list, so coffee wins. "Blue Cafe" matches the dining rule and the second coffee rule (
blue), both priority 4; dining comes first in the list, so dining wins. "gasoline station" has no wordgas, so it is uncategorized; "Gas-N-Go" is fuel.
Constraints
0 <= transactions.length <= 80,0 <= rules.length <= 12- Memos are 0 to 60 printable ASCII characters;
|cents| <= 10^12 - Rule words contain only letters and digits;
0 <= priority <= 100 - Category names are lowercase and non-empty