【问题标题】:SortedSet map does not always preserve element ordering in result?SortedSet 映射并不总是在结果中保留元素排序?
【发布时间】:2013-10-22 21:05:52
【问题描述】:

给定以下 Scala 2.9.2 代码:

更新为无效示例

import collection.immutable.SortedSet

case class Bar(s: String)

trait Foo {
  val stuff: SortedSet[String]
  def makeBars(bs: Map[String, String])
    = stuff.map(k => Bar(bs.getOrElse(k, "-"))).toList
}

case class Bazz(rawStuff: List[String]) extends Foo {
  val stuff = SortedSet(rawStuff: _*)
}


// test it out....
val b = Bazz(List("A","B","C"))
b.makeBars(Map("A"->"1","B"->"2","C"->"3"))
// List[Bar] = List(Bar(1), Bar(2), Bar(3))
// Looks good?

// Make a really big list not in order. This is why we pass it to a SortedSet...    
val data =  Stream.continually(util.Random.shuffle(List("A","B","C","D","E","F"))).take(100).toList
val b2 = Bazz(data.flatten)

// And how about a sparse map...?
val bs = util.Random.shuffle(Map("A" -> "1", "B" -> "2", "E" -> "5").toList).toMap
b2.makeBars(bs)
// res24: List[Bar] = List(Bar(1), Bar(2), Bar(-), Bar(5))

我发现,在某些情况下,扩展 Foo 的类的 makeBars 方法确实返回排序列表。事实上,列表排序确实反映了SortedSet的排序

我在上面的代码中遗漏了什么,Scala 并不总是将 SortedSet 映射到 List,其中元素按 SortedSet 排序?

【问题讨论】:

  • 你需要展示一个实际不起作用的例子。
  • 同意。这需要一些时间,因为我必须清理一些专有代码。
  • 已更新。我遗漏了他的 map 函数实际上是将 stuff SortedSet 的元素包装在案例类“Bar”中
  • 再次更新,这次展示了makeBars 函数中的更多缺陷。

标签: scala


【解决方案1】:

你对隐式解析感到惊讶。

map 方法需要一个与目标集合类型(在简单情况下,与源集合类型相同)和映射器函数的返回类型兼容的 CanBuildFrom 实例。

SortedSet 的特定情况下,其隐含的CanBuildFrom 要求Ordering[A](其中A 是映射器函数的返回类型)可用。当您的 map 函数返回编译器已经知道如何找到 Ordering 的内容时,您就很好了:

scala> val ss = collection.immutable.SortedSet(10,9,8,7,6,5,4,3,2,1)
ss: scala.collection.immutable.SortedSet[Int] = TreeSet(1, 2, 3, 4, 5, 
                                                        6, 7, 8, 9, 10)

scala> val result1 = ss.map(_ * 2)
result1: scala.collection.immutable.SortedSet[Int] = TreeSet(2, 4, 6, 8, 10, 
                                                            12, 14, 16, 18, 20) 
                 // still sorted because Ordering[Int] is readily available

scala> val result2 = ss.map(_ + " is a number")
result2: scala.collection.immutable.SortedSet[String] = TreeSet(1 is a number, 
                                                                10 is a number, 
                                                                2 is a number, 
                                                                3 is a number, 
                                                                4 is a number, 
                                                                5 is a number, 
                                                                6 is a number, 
                                                                7 is a number, 
                                                                8 is a number, 
                                                                9 is a number) 
// The default Ordering[String] is an "asciibetical" sort, 
// so 10 comes between 1 and 2. :)

但是,当您的映射器函数返回一个未知排序的类型时,SortedSet 上的隐式不匹配(具体来说,无法找到其隐式参数的值),因此编译器看起来“向上”获取兼容的CanBuildFrom,并从Set 中找到通用的。

scala> case class Foo(i: Int)
defined class Foo

scala> val result3 = ss.map(Foo(_))
result3: scala.collection.immutable.Set[Foo] = Set(Foo(10), Foo(4), Foo(6), Foo(7), Foo(1), Foo(3), Foo(5), Foo(8), Foo(9), Foo(2))

// The default Set is a hash set, therefore ordering is not preserved

当然,您可以通过简单地提供一个 Ordering[Foo] 的实例来解决这个问题:

scala> implicit val fooIsOrdered: Ordering[Foo] = Ordering.by(_.i)
fooIsOrdered: Ordering[Foo] = scala.math.Ordering$$anon$9@7512dbf2

scala> val result4 = ss.map(Foo(_))
result4: scala.collection.immutable.SortedSet[Foo] = TreeSet(Foo(1), Foo(2), 
                                                       Foo(3), Foo(4), Foo(5), 
                                                       Foo(6), Foo(7), Foo(8), 
                                                       Foo(9), Foo(10))
  // And we're back!

最后,请注意,玩具示例通常不会出现问题,因为 Scala 集合库具有针对小型 (n

【讨论】:

  • 如果 OP 为他的 makeBars 方法提供了类型注释,他就会发现他的错误(没有为 Bar 定义 Ordering)。
  • @Alex Cruise 哇,太棒了!还有一个原因是我不总是喜欢 Scala 集合的继承结构。
【解决方案2】:

您可能正在假设 SortedSet 在 Java 中的作用。您需要指定元素的排列顺序。请参阅http://www.scala-lang.org/docu/files/collections-api/collections_8.html

【讨论】:

  • 实际上,我对map 做了一个错误的假设,因为我已经习惯了Clojure,其中map 函数总是返回seq - 没有CanBuildFrom
猜你喜欢
  • 1970-01-01
  • 2018-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-03
  • 1970-01-01
  • 2011-04-12
相关资源
最近更新 更多