RustArena

Step 6 of 6

Closures at work: sorting, filtering, defaults

Most closures you'll write at work are tiny arguments to standard library methods. You've used several of these methods already; now you know what they take.

struct Ticket {
    id: u32,
    title: String,
    priority: u8,
    open: bool,
}

fn main() {
    let mut tickets = vec![
        Ticket { id: 7, title: String::from("Login fails"), priority: 1, open: true },
        Ticket { id: 3, title: String::from("Typo on homepage"), priority: 3, open: true },
        Ticket { id: 9, title: String::from("Old bug"), priority: 2, open: false },
        Ticket { id: 4, title: String::from("Slow search"), priority: 1, open: true },
    ];

    tickets.retain(|t| t.open);                  // keep only open tickets
    tickets.sort_by_key(|t| (t.priority, t.id)); // by priority, then by id

    for t in &tickets {
        println!("#{} [P{}] {}", t.id, t.priority, t.title);
    }

    let top = tickets.first().map(|t| t.title.to_uppercase());
    println!("{}", top.unwrap_or_else(|| String::from("nothing to do")));
}
#4 [P1] Slow search
#7 [P1] Login fails
#3 [P3] Typo on homepage
SLOW SEARCH
Method The closure gets and returns Bound
v.retain(f) &T bool: keep this element? FnMut
v.sort_by_key(f) &T a key to sort by (anything Ord: numbers, tuples, Strings) FnMut
v.sort_by(f) &T, &T an Ordering, usually from a.cmp(b) FnMut
opt.map(f) the value inside Some the new value FnOnce
opt.unwrap_or_else(f) nothing the fallback value FnOnce
map.entry(k).or_insert_with(f) nothing the value to insert FnOnce

Tricks worth knowing:

Common mistakes

struct Player {
    name: String,
    score: u32,
}

fn main() {
    let mut players = vec![
        Player { name: String::from("Zed"), score: 3 },
        Player { name: String::from("Amy"), score: 5 },
    ];
    players.sort_by_key(|p| &p.name);
    println!("{} {}", players[0].name, players[0].score);
}
error: lifetime may not live long enough

Use players.sort_by(|a, b| a.name.cmp(&b.name)) instead (no copies), or sort_by_key(|p| p.name.clone()) (allocates a String per comparison).

Your task

Write fn leaderboard(players: &mut Vec<Player>, min_score: u32). It removes every player whose score is below min_score, then sorts the rest by score, highest first; players with the same score are sorted by name, A to Z. Use retain and a sort method with closures, no loops.

Challenge 2

Write fn tidy_tags(tags: &mut Vec<String>) that cleans up a list of tags: remove empty tags, sort the rest by length (shortest first) with equal lengths in alphabetical order, then remove duplicates. For example ["rust", "", "go", "c", "rust", "zig"] becomes ["c", "go", "zig", "rust"]. No loops.

Challenge 3

Write a program that reads shopping items from input, one per line, as name price (for example lamp 19.99), until the input ends. Keep only the items that cost at most 20.00, sort them by price (cheapest first), and print each one as name: price with two decimals. If nothing is left, print nothing affordable.

Prices are f64, so sort with sort_by and total_cmp, and filter with retain.

Previous: Returning closures