subsetOf与forall包含

时间:2011-03-25 14:10:10

标签: scala

考虑一下:

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

为什么呢?我的意思是,很明显,在SetSeq选择随机元素会出现重复,但由于“(...).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形成对比,尽管两者都是不可变的地图。这对我来说并不直观。

1 个答案:

答案 0 :(得分:3)

该领域存在问题的最常见原因 - 测试集等式等 - 是

  1. hashCode不同意equals
  2. 您的值不稳定(因此之前hashCode与当前equals不一致)
  3. 原因是重要的是distincttoSet使用哈希码来构建集合,而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!
    

    好的,现在我们知道,对于大多数这些操作,匹配hashCodeequals是一件好事。

    警告:在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
    

    案例类也可以正确地执行此操作,因此我们有三种可能性

    1. 您覆盖了equals但未覆盖hashCode以匹配
    2. 您的价值观不稳定
    3. 有一个错误,Java包装的原始hashCode不匹配又回来咬你了
    4. 第一个很容易。

      第二个似乎是你的问题,它源于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来映射您的值(例如,如果您的值是文件名并且您正在映射到其内容)并且您不想一遍又一遍地读取文件,那么这非常重要

      但是你的情况 - 你在分配随机值 - 是另一个重要的选择一个副本,而不是一遍又一遍地重新创建值。