考虑一下:
case class X(...)
val xs: Seq[X] = ... // some method result
val ys: Seq[X] = ... // some other method result
虽然如下:
xs.distinct.sameElements(xs) // true
ys.distinct.sameElements(ys) // true
我正面临:
xs forall(ys contains _) // true
xs.toSet subsetOf ys.toSet // false
为什么呢?我的意思是,很明显,在Set
中Seq
选择随机元素会出现重复,但由于“(...).distinct.sameElements(...)
”而没有重复项。
我当然需要更深入地了解平等检查......
修改
经过长时间的搜索后,我发现了问题并将其浓缩为以下内容:
我的元素不一样,但我必须仔细看看为什么distinct.sameElements
不抱怨。但同时出现了一个新问题:
考虑一下:
val rnd = scala.util.Random
def int2Label(i: Int) = "[%4s]".format(Seq.fill(rnd.nextInt(4))(i).mkString)
val s = Seq(1,2,3,4)
// as expected :
val m1: Map[Int,String] = s.map(i => (i,int2Label(i))).toMap
println(m1) // Map(5 -> [ 555], 1 -> [ ], 2 -> [ 22], 3 -> [ ])
println(m1) // Map(5 -> [ 555], 1 -> [ ], 2 -> [ 22], 3 -> [ ])
// but accessing m2 several times yields different results. Why?
val m2: Map[Int,String] = s.map(i => (i,i)).toMap.mapValues { int2Label(_) }
println(m2) // Map(5 -> [ 5], 1 -> [ 11], 2 -> [ 22], 3 -> [ 333])
println(m2) // Map(5 -> [ 55], 1 -> [ 11], 2 -> [ ], 3 -> [ ])
所以我的第一个序列中的元素不一样,因为它们依赖于m2
- 构造,因此每次访问它们时它们都是不同的。
我的新问题是,为什么m2
的行为与m1
形成对比,尽管两者都是不可变的地图。这对我来说并不直观。
答案 0 :(得分:3)
该领域存在问题的最常见原因 - 测试集等式等 - 是
hashCode
不同意equals
hashCode
与当前equals
不一致)原因是重要的是distinct
和toSet
使用哈希码来构建集合,而contains
只是使用exists
在集合上运行:
xs forall(ys contains _) == xs forall (x => ys exists (y => x==y) )
由于许多集合在大于某个最小尺寸(通常为4)之前不会开始使用哈希码,因此更加复杂,因此您不会总是注意到这一点。但是,让我们向自己证明:
class Liar(s: String) {
override def equals(o: Any) = o match {
case l: Liar => s == l.s
case _ => _
}
// No hashCode override!
}
val strings = List("Many","song","lyrics","go","na","na","na","na")
val lies = strings.map(s => new Liar(s))
val truly_distinct = lies.take(5)
lies.length // 8
lies.distinct.length // 8!
lies.toSet.size // 8!
lies forall( truly_distinct contains _ ) // True, because it's true
lies.toSet subsetOf truly_distinct.toSet // False--not even the same size!
好的,现在我们知道,对于大多数这些操作,匹配hashCode
和equals
是一件好事。
警告:在Java中,即使使用原语,也会经常发生不匹配:
new java.lang.Float(1.0) == new java.lang.Integer(1) // True
(new java.lang.Float(1.0)).hashCode == (new java.lang.Integer(1)).hashCode // Uh-oh
但Scala现在至少可以捕捉到这一点(希望每一次都有):
(new java.lang.Float(1.0)).## == (new java.lang.Integer(1)).## // Whew
案例类也可以正确地执行此操作,因此我们有三种可能性
equals
但未覆盖hashCode
以匹配第一个很容易。
第二个似乎是你的问题,它源于mapValues
实际创建原始集合的视图而不是新集合的事实。 (filterKeys
也是这样做的。)就个人而言,我认为这是一个值得怀疑的设计选择,因为通常当你有一个视图而你想要创建一个具体的实例时,你.force
它。但是默认地图没有.force
,因为他们没有意识到它们可能是视图。所以你必须求助于
myMap.map{ case (k,v) => (k, /* something that produces a new v */) }
myMap.mapValues(v => /* something that produces a new v */).view.force
Map() ++ myMap.mapValues(v => /* something that produces a new v */)
如果您正在执行文件IO来映射您的值(例如,如果您的值是文件名并且您正在映射到其内容)并且您不想一遍又一遍地读取文件,那么这非常重要
但是你的情况 - 你在分配随机值 - 是另一个重要的选择一个副本,而不是一遍又一遍地重新创建值。