13

I'd like to iterate through the keys of a HashMap in order. Is there an elegant way to do this? The best I can think of is this:

use std::collections::HashMap;

fn main() {
    let mut m = HashMap::<String, String>::new();

    m.insert("a".to_string(), "1".to_string());
    m.insert("b".to_string(), "2".to_string());
    m.insert("c".to_string(), "3".to_string());
    m.insert("d".to_string(), "4".to_string());

    let mut its = m.iter().collect::<Vec<_>>();
    its.sort();

    for (k, v) in &its {
        println!("{}: {}", k, v);
    }
}

I'd like to be able to do something like this:

for (k, v) in m.iter_sorted() {
}
for (k, v) in m.iter_sorted_by(...) {
}

Obviously I can write a trait to do that, but my question is does something like this already exist?

Edit: Also, since people are pointing out that BTreeMap is already sorted I should probably note that while this is true, it isn't actually as fast as a HashMap followed by sort() (as long as you only sort it once of course). Here are some benchmark results for random u32->u32 maps:

hashmap vs btreemap

Additionally, a BTreeMap only allows a single sort order.

2
  • 4
    If you really want a HashMap, this is conceptually the best you can do. If you are fine using a BTreeMap, iteration will automatically be in order. Commented Nov 26, 2019 at 13:16
  • Yes I'm aware of BTreeMap, and I know this is algorithmically optimal. I'm just asking about coding ergonomics - is there a shorter more elegant way to write it basically. Commented Nov 26, 2019 at 13:26

2 Answers 2

22

HashMap doesn't guarantee a particular order of iteration. Simplest way to achieve consistent order is to use BTreeMap which is based on B-tree, where data is sorted.

You should understand that any implementation will do this in O(n) memory, particularly storing references to all items and at least O(n * log(n)) time to sort data out.

If you understand cost of doing this you can use IterTools::sorted from itertools crate.

use itertools::Itertools; // 0.8.2
use std::collections::HashMap;

fn main() {
    let mut m = HashMap::<String, String>::new();

    m.insert("a".to_string(), "1".to_string());
    m.insert("b".to_string(), "2".to_string());
    m.insert("c".to_string(), "3".to_string());
    m.insert("d".to_string(), "4".to_string());

    println!("{:#?}", m.iter().sorted())
}

Playground link

Sign up to request clarification or add additional context in comments.

6 Comments

Yeah I'm aware of BTreeMap - Itertools was the answer I was looking for, thanks!
Also using O(N) memory isn't likely to ever be a concern because O(N) memory is already used to store the actual data. I would also expect the memory overhead of BTreeMap to that of storing N iterators. As for the time, sorting more than once is definitely slower but if you only need the data sorted once I wouldn't be surprised if it was very similar or even faster (depending on how it is implemented), since BTreeMap is effectively an insertion sort anyway.
@Timmmm "Insertion sort" usually means the O(n ²) algorithm to sort an array in place. Inserting n elements in a BTreeMap is fumdamentally different, and has a time complexity of O(n log n). If you need the data in order even once, I'd go with a BTreeMap – it's definitely simpler, and likely faster as well.
@Timmmm I'm also not convinced it's faster, but in the absence of a benchmark I'd go with the simpler solution. :)
Just did a simple benchmark, with 10000 random u32 keys and values. HashMap (with .reserve()) followed by sort takes 1.16 ms, BTreeMap takes 1.47 ms, so I was right! Another reason to not use BTreeMap is that it only allows a single sort order.
|
3

Based on what @Inline wrote, a more generic solution using HashMap, allowing for sorting by value and changing values. (Note that the content of the HashMap was adjusted in order to make the distinction of sorting by key and value visible.)

use itertools::Itertools;  // itertools = "0.10"
use std::collections::HashMap;

fn main() {
    let mut m = HashMap::<String, String>::new();

    m.insert("a".to_string(), "4".to_string());
    m.insert("b".to_string(), "3".to_string());
    m.insert("c".to_string(), "2".to_string());
    m.insert("d".to_string(), "1".to_string());

    // iterate (sorted by keys)
    for (k, v) in m.iter().sorted_by_key(|x| x.0) {
        println!("k={}, v={}", k, v);
    }
    println!();

    // iterate (sorted by values)
    for (k, v) in m.iter().sorted_by_key(|x| x.1) {
        println!("k={}, v={}", k, v);
    }
    println!();

    // iterate (sorted by keys), write to values
    for (k, v) in m.iter_mut().sorted_by_key(|x| x.0) {
        *v += "v"; // append 'v' to value
        println!("k={}, v={}", k, v);
    }
}

Playground link

1 Comment

Does anyone know how to sort by values and writing to values?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.