Bakery order tally
A bakery's counter tablet records what is set aside for a customer. Each line is a command: add <item> <qty> adds that many of an item, remove <item> <qty> takes that many away (a count never goes below zero, and removing an item that was never added does nothing), and done ends the order. After done, print every item whose count is above zero as <item>: <count>, one per line in alphabetical order of item name, or the single word empty if nothing is left.
Input
One command per line: add <item> <qty>, remove <item> <qty> or done. Item names are single lowercase words; quantities are positive integers. The last command processed is done.
Output
One line per item with a positive count, <item>: <count>, sorted by name; or empty.
Example 1
Input
add croissant 3 add rye 2 remove croissant 1 done
Output
croissant: 2 rye: 2
3 croissants minus 1 leaves 2; rye stays at 2. Names are printed in alphabetical order.
Example 2
Input
add bun 1 remove bun 5 done
Output
empty
Removing more than there is drops the count to zero, and zero counts are not printed, so the tally is empty.
Constraints
- 1 <= number of commands <= 1000
- 1 <= qty <= 1000
- Item names contain only lowercase letters
Hints
Hint 1 of 3
Split each line into words with split_whitespace().collect::<Vec<&str>>() and match on parts.as_slice() with slice patterns such as ["add", item, qty].
Hint 2 of 3
A BTreeMap<String, u32> keeps its keys sorted, so printing in alphabetical order comes for free.
Hint 3 of 3
For remove, saturating_sub stops the count at zero instead of underflowing; entry(...).or_insert(0) is the easy way to add.
Solution
Show a reference solution and explanation
use std::collections::BTreeMap;
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
let mut tally: BTreeMap<String, u32> = BTreeMap::new();
for line in stdin.lock().lines() {
let line = line.unwrap();
let parts: Vec<&str> = line.split_whitespace().collect();
match parts.as_slice() {
["done"] => break,
["add", item, qty] => {
let qty: u32 = qty.parse().unwrap();
*tally.entry(item.to_string()).or_insert(0) += qty;
}
["remove", item, qty] => {
let qty: u32 = qty.parse().unwrap();
if let Some(count) = tally.get_mut(*item) {
*count = count.saturating_sub(qty);
}
}
_ => {}
}
}
let mut printed = false;
for (item, count) in &tally {
if *count > 0 {
println!("{}: {}", item, count);
printed = true;
}
}
if !printed {
println!("empty");
}
}
Why it works
Slice patterns let one match do both the dispatch on the command word and the extraction of its arguments: ["add", item, qty] only matches a three-word line whose first word is add, and binds the other two. The wildcard arm makes the match exhaustive. A BTreeMap is chosen over a HashMap for one reason: its iteration order is the sorted key order, which is exactly the output order required, whereas a HashMap would print in an unspecified order. saturating_sub expresses the never-below-zero rule directly on u32 without a separate check, and break on done stops reading even if more lines follow.