9

Why does the immutable version of the ListMap store in ascending order, while mutable version stores in descending order?

Here is a test that you can use if you got scalatest-1.6.1.jar and junit-4.9.jar

  @Test def StackoverflowQuestion()
  {
    val map = Map("A" -> 5, "B" -> 12, "C" -> 2, "D" -> 9, "E" -> 18)
    val sortedIMMUTABLEMap = collection.immutable.ListMap[String, Int](map.toList.sortBy[Int](_._2): _*)
    println("head : " + sortedIMMUTABLEMap.head._2)
    println("last : " + sortedIMMUTABLEMap.last._2)
    sortedIMMUTABLEMap.foreach(X => println(X))
    assert(sortedIMMUTABLEMap.head._2 < sortedIMMUTABLEMap.last._2)

    val sortedMUTABLEMap = collection.mutable.ListMap[String, Int](map.toList.sortBy[Int](_._2): _*)
    println("head : " + sortedMUTABLEMap.head._2)
    println("last : " + sortedMUTABLEMap.last._2)
    sortedMUTABLEMap.foreach(X => println(X))
    assert(sortedMUTABLEMap.head._2 > sortedMUTABLEMap.last._2)
  }

Heres the output of the PASSING test :

head : 2
last : 18
(C,2)
(A,5)
(D,9)
(B,12)
(E,18)
head : 18
last : 2
(E,18)
(B,12)
(D,9)
(A,5)
(C,2)
Duncan McGregor
  • 17,665
  • 12
  • 64
  • 118
Zasz
  • 12,330
  • 9
  • 43
  • 63
  • 2
    One major advantage of a good collections API is that it protects you from having to "exhaustively learn all the eccentricities like this". The order of iteration isn't part of the contract of either `ListMap`, so you never have to think about it. – Travis Brown Sep 24 '11 at 18:04
  • A less specific interface description leaves more room for future changes/improvements. If you want reliable behaviour in terms of element order, use a `SortedMap`. – Raphael Sep 24 '11 at 22:51
  • How is Duncan able to edit this question? I understood you need 2000 rep to edit! Lost in offering bounties? – Zasz Sep 30 '11 at 17:27
  • this is why i use LinkedHashmap – Andrew Norman Mar 17 '16 at 23:25

3 Answers3

12

The symptoms can be simplified to:

scala> collection.mutable.ListMap(1 -> "one", 2 -> "two").foreach(println)
(2,two)
(1,one)

scala> collection.immutable.ListMap(1 -> "one", 2 -> "two").foreach(println)
(1,one)
(2,two)

The "sorting" in your code is not the core of the issue, your call to ListMap is using the ListMap.apply call from the companion object that constructs a list map backed by a mutable or immutable list. The rule is that the insertion order will be preserved.

The difference seems to be that mutable list is backed by an immutable list and insert happens at the front. So that's why when iterating you get LIFO behavior. I'm still looking at the immutable one but I bet the inserts are effectively at the back. Edit, I'm changing my mind: insert are probably at the front, but it seems the immutable.ListMap.iterator method decides to reverse the result with a toList.reverseIterator on the returned iterator. I think it worth bringing it in the mailing list.

Could the documentation be better? Certainly. Is there pain? Not really, I don't let it happen. If the documentation is incomplete, it's wise to test the behavior or go look up at the source before picking a structure versus another one.

Actually, there can be pain if the Scala team decides to change behavior at a later time and feel they can because the behavior is effectively undocumented and there is no contract.


To address your use case explained in the the comment, say you've collected the string frequency count in a map (mutable or immutable):

val map = Map("A" -> 5, "B" -> 12, "C" -> 2, "D" -> 9, "E" -> 18, "B" -> 5)

Since you only need to sort once at the end, you can convert the tuples from the map to a seq and then sort:

map.toSeq.sortBy(_._2)
// Seq[(java.lang.String, Int)] = ArrayBuffer((C,2), (A,5), (B,5), (D,9), (E,18))
huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Good point, it will be a bit of a problem if they change things now (or later). I found it very hard to debug because my sort test and source were having the exact same code, but different versions of ListMap. Would be much better if the collections had better names representing their mutability – Zasz Sep 24 '11 at 16:52
  • I will mark this answer as correct if you can suggest which data structure i should use, I need a sorted table of (string, int) pairs sorted on the int value, and ITERABLE after sorting. – Zasz Sep 24 '11 at 16:56
  • @Zasz, do you allow duplicates or multiple strings for the same int in your table? Do you need the table to stay sorted as it is mutated? Or it's fine if you sort it before displaying/iterating? I wouldn't accept my answer until I hear back from the mailing list... – huynhjl Sep 24 '11 at 17:24
  • Thanks for your effort on my behalf. Ints will repeat (they represent the number of times the string has occured), while strings will never have duplicates. I will not add items after I sort - I add all items and sort in the end once. I created a scala language dev thread [here](http://groups.google.com/group/scala-language/browse_thread/thread/f1a86af2730592cc) – Zasz Sep 24 '11 at 17:42
4

As I see it neither ListMap claims to be a sorted map, just a map implemented with a List. In fact I don't see anything in their contract that says anything about preserving the insertion order.

Programming in Scala explains that ListMap may be of use if the early elements are more likely to be accessed, but that otherwise it has little advantage over Map.

Duncan McGregor
  • 17,665
  • 12
  • 64
  • 118
  • 1
    Exactly what I meant about not enough info on the data structures. How I ended up choosing ListMap was after seeing a thread in stackoverflow, which used it for sorting. And based on what is written for the ListMap documentation, it is very difficult to know where to use ListMap – Zasz Sep 24 '11 at 18:22
1

Don't build any expectations on order, it is not declared and it will vary between Scala versions.

For example:

import scala.collection.mutable.{ListMap => MutableListMap}

MutableListMap("A" -> 5, "B" -> 12, "C" -> 2, "D" -> 9, "E" -> 18).foreach(println)

On 2.9.1 gives: (E,18) (D,9) (C,2) (B,12) (A,5)

but on 2.11.6 gives: (E,18) (C,2) (A,5) (B,12) (D,9)

hmu10
  • 11
  • 3