ExamplesBy LevelBy TopicLearning Paths
358 Intermediate

358: IndexMap Ordered (Insertion-Order Map)

Functional Programming

Tutorial

The Problem

HashMap iterates in arbitrary order; BTreeMap iterates in key-sorted order. But sometimes you need to iterate in insertion order — preserving the sequence in which entries were added. This is how Python dicts work since 3.7, how JSON objects are commonly expected to behave, and how HTTP headers must be processed. The indexmap crate provides this natively; this example demonstrates the pattern using a HashMap + Vec<K> combination to illustrate the mechanism, which you can replace with the real IndexMap crate in production.

🎯 Learning Outcomes

  • • Build an insertion-order-preserving map using HashMap<K, V> + Vec<K> for order tracking
  • • Insert maintaining both the hash map (O(1) lookup) and the order vector (O(1) push)
  • • Iterate in insertion order by walking the Vec<K> and looking up values
  • • Understand why skipping the order vector loses insertion order guarantees
  • • Recognize that the indexmap crate is the production-quality solution
  • • Know the cost: remove is O(n) due to Vec compaction (use swap_remove for O(1))
  • Code Example

    #![allow(clippy::all)]
    //! # IndexMap Ordered
    //! HashMap that preserves insertion order (simulated without external crate).
    
    use std::collections::HashMap;
    
    pub struct OrderedMap<K, V> {
        map: HashMap<K, V>,
        order: Vec<K>,
    }
    
    impl<K: Clone + Eq + std::hash::Hash, V> OrderedMap<K, V> {
        pub fn new() -> Self {
            Self {
                map: HashMap::new(),
                order: Vec::new(),
            }
        }
    
        pub fn insert(&mut self, key: K, value: V) {
            if self.map.insert(key.clone(), value).is_none() {
                self.order.push(key);
            }
        }
    
        pub fn get(&self, key: &K) -> Option<&V> {
            self.map.get(key)
        }
    
        pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
            self.order
                .iter()
                .filter_map(|k| self.map.get(k).map(|v| (k, v)))
        }
    
        pub fn len(&self) -> usize {
            self.map.len()
        }
        pub fn is_empty(&self) -> bool {
            self.map.is_empty()
        }
    }
    
    impl<K: Clone + Eq + std::hash::Hash, V> Default for OrderedMap<K, V> {
        fn default() -> Self {
            Self::new()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn preserves_order() {
            let mut m = OrderedMap::new();
            m.insert("b", 2);
            m.insert("a", 1);
            m.insert("c", 3);
            let keys: Vec<_> = m.iter().map(|(k, _)| *k).collect();
            assert_eq!(keys, vec!["b", "a", "c"]);
        }
        #[test]
        fn get_works() {
            let mut m = OrderedMap::new();
            m.insert("k", 42);
            assert_eq!(m.get(&"k"), Some(&42));
        }
    }

    Key Differences

    AspectRust HashMap + VecOCaml assoc list
    LookupO(1)O(n)
    InsertionO(1) amortizedO(1) prepend
    IterationInsertion orderInsertion order (reversed)
    RemoveO(n) for ordered removeO(n) filter
    Production solutionindexmap crateorderedhashtbl package

    OCaml Approach

    OCaml's association lists ((key * value) list) naturally preserve insertion order:

    (* Association list: insertion-ordered, O(n) lookup *)
    let insert lst k v = (k, v) :: lst  (* prepends — reverse insertion order *)
    let get lst k = List.assoc_opt k lst
    let iter lst f = List.iter (fun (k, v) -> f k v) (List.rev lst)
    
    (* For production: use the ordered-hashtbl or sequence package *)
    

    Association lists are the OCaml idiom for small ordered maps. For larger maps, the orderedhashtbl package or CCHashtbl.Poly with order tracking mirrors the HashMap+Vec approach.

    Full Source

    #![allow(clippy::all)]
    //! # IndexMap Ordered
    //! HashMap that preserves insertion order (simulated without external crate).
    
    use std::collections::HashMap;
    
    pub struct OrderedMap<K, V> {
        map: HashMap<K, V>,
        order: Vec<K>,
    }
    
    impl<K: Clone + Eq + std::hash::Hash, V> OrderedMap<K, V> {
        pub fn new() -> Self {
            Self {
                map: HashMap::new(),
                order: Vec::new(),
            }
        }
    
        pub fn insert(&mut self, key: K, value: V) {
            if self.map.insert(key.clone(), value).is_none() {
                self.order.push(key);
            }
        }
    
        pub fn get(&self, key: &K) -> Option<&V> {
            self.map.get(key)
        }
    
        pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
            self.order
                .iter()
                .filter_map(|k| self.map.get(k).map(|v| (k, v)))
        }
    
        pub fn len(&self) -> usize {
            self.map.len()
        }
        pub fn is_empty(&self) -> bool {
            self.map.is_empty()
        }
    }
    
    impl<K: Clone + Eq + std::hash::Hash, V> Default for OrderedMap<K, V> {
        fn default() -> Self {
            Self::new()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn preserves_order() {
            let mut m = OrderedMap::new();
            m.insert("b", 2);
            m.insert("a", 1);
            m.insert("c", 3);
            let keys: Vec<_> = m.iter().map(|(k, _)| *k).collect();
            assert_eq!(keys, vec!["b", "a", "c"]);
        }
        #[test]
        fn get_works() {
            let mut m = OrderedMap::new();
            m.insert("k", 42);
            assert_eq!(m.get(&"k"), Some(&42));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn preserves_order() {
            let mut m = OrderedMap::new();
            m.insert("b", 2);
            m.insert("a", 1);
            m.insert("c", 3);
            let keys: Vec<_> = m.iter().map(|(k, _)| *k).collect();
            assert_eq!(keys, vec!["b", "a", "c"]);
        }
        #[test]
        fn get_works() {
            let mut m = OrderedMap::new();
            m.insert("k", 42);
            assert_eq!(m.get(&"k"), Some(&42));
        }
    }

    Deep Comparison

    OCaml vs Rust: Indexmap Ordered

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • **remove with order**: Implement remove(&mut self, key: &K) -> Option<V> that removes the key from both the HashMap and the Vec<K>; use Vec::retain for correctness or swap_remove for O(1) (unordered removal).
  • JSON object ordering: Use OrderedMap<String, String> to represent a JSON object and serialize it with keys in insertion order; compare output with HashMap-based serialization.
  • Indexmap crate: Replace OrderedMap with the indexmap::IndexMap crate; verify that get_index(0) returns the first inserted key-value pair, and that iteration order matches insertion order.
  • Open Source Repos