为什么可变和不可变的ListMaps在Scala中有不同的顺序?

时间:2011-09-24 15:21:44

标签: scala scala-collections

为什么ListMap的不可变版本按升序存储,而可变版本按降序存储?

如果你有scalatest-1.6.1.jar和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)
  }

继承PASSING测试的输出:

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)

3 个答案:

答案 0 :(得分:12)

症状可以简化为:

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)

代码中的“排序”不是问题的核心,您对ListMap的调用是使用来自配对对象的ListMap.apply调用,该对象构造由可变或不可变的备份的列表映射名单。规则是将保留插入顺序。

差异似乎是可变列表由 immutable 列表支持,插入发生在前面。这就是为什么在迭代时你会得到LIFO行为。我仍然在看不可变的那个,但我敢打赌插入有效地在后面。编辑,我正在改变我的想法:插入可能在前面,但似乎immutable.ListMap.iterator方法决定在返回的迭代器上用toList.reverseIterator反转结果。我认为值得把它带到邮件列表中。

文档可以更好吗?当然。有痛苦吗?不是真的,我不会让它发生。如果文档不完整,那么在选择结构与另一个结构之前测试行为或查看源代码是明智的。

实际上,如果Scala团队决定稍后改变行为并认为他们可以,因为行为实际上没有记录并且没有合同,那么可能会有痛苦。


要解决评论中解释的用例,请说你已经在地图中收集了字符串频率计数(可变或不可变):

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

由于您只需要在结尾处排序一次,您可以将地图中的元组转换为seq,然后排序:

map.toSeq.sortBy(_._2)
// Seq[(java.lang.String, Int)] = ArrayBuffer((C,2), (A,5), (B,5), (D,9), (E,18))

答案 1 :(得分:4)

我认为ListMap声称它不是一个有序地图,只是一个用List实现的地图。事实上,我在合同中没有看到任何关于保留插入顺序的内容。

Scala中的编程解释说,如果更有可能访问早期元素,ListMap可能会有用,但除此之外它没有Map优势。

答案 2 :(得分:1)

不要对订单构建任何期望,它不会被声明,并且在Scala版本之间会有所不同。

例如:

Sub LoopThroughDirectory()

Dim MyFile As String
Dim erow
Dim Filepath As String
Dim targetWbk As Workbook
Dim sourceWbk As Workbook

Filepath = "C:\test"
MyFile = Dir(Filepath)

Workbooks.Open (Filepath & "\workbook.xlsm")
Set sourceWbk = ActiveWorkbook


Do While Len(MyFile) > 0
If Not MyFile = "workbook.xlsm" And MyFile = "*.xls*" Then

Workbooks.Open (Filepath & MyFile)
Set sourceWbk = ActiveWorkbook

sourceWbk.Sheets("Sheet2").Range("A1:N24").Copy

If targetWbk.Sheets("Sheet1").Range("A1") = vbNullString Then
targetWbk.Sheets("Sheet1").Range("A1:N24").PasteSpecial xlPasteFormulas, xlPasteValues

Else
 targetWbk.Sheets("sheet1").Cells(A1, Columns.Count).End(xlToLeft).Offset(0, 1).PasteSpecial xlPasteFormulas, xlPasteValues
End If

MyFile = Dir

End If
Loop

End Sub

在2.9.1上给出: (E,18) (d,9) (C,2) (B,12) (A,5)

但在2.11.6给出: (E,18) (C,2) (A,5) (B,12) (d,9)