The situation
The night market closes at four. The stallholder has spent six hours taking money and writing what she sold on whatever paper was nearest, and the whole lot is now on a spike next to the till. Some of it is smudged past reading. Some of it says things like lanterns x.
She wants three numbers by dawn: what sold, what sold best, and what she has to buy tomorrow before opening. And she wants the same report every time she runs it — not a list that shuffles itself because a HashMap felt like it.
The receipts
Each readable receipt is exactly two whitespace-separated fields: item then quantity.
lanterns 3 ← good
dumplings 12 ← good
smudged ← one field, not a receipt
lanterns x ← the quantity isn't a number
silk 1 extra ← three fields, not a receiptAnything that isn't exactly <item> <number> gets ignored. Silently — the stallholder does not want a program that panics at 4 a.m.
Your mission
1. tally(receipts: &[&str]) -> HashMap<String, u32>
Sum the quantities per item, skipping the scribbles.
["tea 4", "tea 8", "silk 2", "smudged"] -> { "tea": 12, "silk": 2 }
[] -> { }2. ranked(totals: &HashMap<String, u32>) -> Vec<(String, u32)>
Every entry as a row, biggest quantity first. Equal quantities are ordered alphabetically by name. This is the function that makes the report reproducible: a HashMap has no order of its own, so the order has to come from you.
{ tea: 12, silk: 3, incense: 9 } -> [("tea", 12), ("incense", 9), ("silk", 3)]
{ tea: 5, incense: 5, silk: 5 } -> [("incense", 5), ("silk", 5), ("tea", 5)]3. best_seller(totals: &HashMap<String, u32>) -> Option<(String, u32)>
The top row — same tie-breaking as ranked. An empty till is None, not ("", 0).
4. restock(totals: &HashMap<String, u32>, threshold: u32) -> Vec<String>
Names whose total is strictly below threshold, sorted alphabetically. restock(t, 3) does not include an item that sold exactly 3.
How you're graded
tally_sums_repeated_itemstally_ignores_scribbles← including the three-field lineranked_sorts_by_quantity_then_nameranked_breaks_ties_alphabeticallybest_seller_is_the_top_rowbest_seller_of_an_empty_till_is_nonerestock_lists_whats_below_the_threshold← the threshold is exclusive
The tie-breaking tests are the ones that catch a solution which "works" — if you rank by quantity alone, three items at 5 each come out in whatever order the map felt like, and the test fails differently on different runs.
Stretch goals
- Print the report as an aligned table using what you learned about
format!widths in module 1. - Make
tallyreturn(HashMap<String, u32>, Vec<String>)— the totals and the receipts it couldn't read. - Swap
HashMapforBTreeMapand see which of your sorts you can delete. What did you pay for that? - Add
fn takings(totals: &HashMap<String, u32>, price: &HashMap<String, u32>) -> u32.