59

LinkedHashMap is used to preserve insertion order in the map, but this only works for mutable maps. Which is the immutable Map implementation that preserves insertion order?

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
Matroska
  • 6,885
  • 14
  • 63
  • 99
  • 1
    This is not an exact duplicate, the question is for Immutable map, the alleged duplicate is about both mutable and immutable. the other question doesn't *directly* answer the immutable part (perhaps does indirectly) – Eran Medan Jul 21 '15 at 02:16

2 Answers2

65

ListMap implements an immutable map using a list-based data structure, and thus preserves insertion order.

scala> import collection.immutable.ListMap
import collection.immutable.ListMap

scala> ListMap(1 -> 2) + (3 -> 4)
res31: scala.collection.immutable.ListMap[Int,Int] = Map(1 -> 2, 3 -> 4)

scala> res31 + (6 -> 9)
res32: scala.collection.immutable.ListMap[Int,Int] = Map(1 -> 2, 3 -> 4, 6 -> 9)

The following extension method - Seq#toListMap can be quite useful when working with ListMaps.

scala> import scalaz._, Scalaz._, Liskov._
import scalaz._
import Scalaz._
import Liskov._

scala> :paste
// Entering paste mode (ctrl-D to finish)

implicit def seqW[A](xs: Seq[A]) = new SeqW(xs)
class SeqW[A](xs: Seq[A]) {
  def toListMap[B, C](implicit ev: A <~< (B, C)): ListMap[B, C] = {
    ListMap(co[Seq, A, (B, C)](ev)(xs) : _*)  
  }
}


// Exiting paste mode, now interpreting.

seqW: [A](xs: Seq[A])SeqW[A]
defined class SeqW

scala> Seq((2, 4), (11, 89)).toListMap
res33: scala.collection.immutable.ListMap[Int,Int] = Map(2 -> 4, 11 -> 89)
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • 3
    There's a gotcha with ListMap - calling update() with existing key *changes* the order of items. Example: `ListMap("a" → 1, "b" → 2).updated("a", 2).toList` yields `List((b,2), (a,2))`. Very unfortunate for my use case :( – Tomáš Dvořák Feb 08 '17 at 09:51
25

While ListMap will preserve insertion order, it is not very efficient - e.g. lookup time is linear. I suggest you create a new collection class which wraps both the immutable.HashMap and the immutable.TreeMap. The immutable map should be parametrized as immutable.HashMap[Key, (Value, Long)], where the Long in the tuple gives you the pointer to the corresponding entry in the TreeMap[Long, Key]. You then keep an entry counter on the side. This tree map will sort the entries according to the insertion order.

You implement insertion and lookup in the straightforward way - increment the counter, insert into the hash map and insert to the the counter-key pair into the treemap. You use the hash map for the lookup.

You implement iteration by using the tree map.

To implement remove, you have to remove the key-value pair from the hash map and use the index from the tuple to remove the corresponding entry from the tree map.

axel22
  • 32,045
  • 9
  • 125
  • 137
  • 3
    +1. Any chance of having such a collection in stdlib in near future? – missingfaktor Feb 16 '12 at 15:59
  • This hasn't been planned, but if the discussion on the Scala internals mailing list revealed a lot of people want this, then why not. – axel22 Feb 16 '12 at 16:07
  • 1
    Would you care to elaborate why? – axel22 Nov 06 '12 at 19:23
  • 3
    Because the index you store in your tuple can change over time when you remove elements from the vector. It's the perfect example of why different collections with different trade-offs exist. Compared to a classic Linked-List map, your implementation lost the ability to remove keys in constant time (log n at best). – AlexG Mar 21 '13 at 09:34
  • Using TreeMap will not preserve insertion order. TreeMap sorts keys using comparator. – Matzz Dec 16 '14 at 12:10
  • 1
    Read the part of the answer about the `TreeMap` using `Long` keys that reflect when an element was inserted. If your comparator is on the insertion order, the tree will preserve the insertion order. – axel22 Dec 16 '14 at 22:14