Scala - 找到两个Seq不同的第一个位置

Scala - finding first position in which two Seq differ

Scala 自带漂亮的 corresponds 方法:

val a = scala.io.Source.fromFile("fileA").getLines().toSeq()
val b = scala.io.Source.fromFile("fileB").getLines().toSeq()

val areEqual = a.corresponds(b){_.equals(_)}

if(areEqual) ...

而且我非常喜欢它的简洁性。

是否已经定义了类似的方法,它也会向我报告两个序列不同的第一个位置?

即有没有更惯用的方式来写这样的东西:

val result = ((seqA zip seqB).zipWithIndex).find{case ((a,b),i) => !a.equals(b)} match{
    case Some(((a,b),i)) => s"seqA and seqB differ in pos $i: $a <> $b"
    case _ => "no difference"
}

因为如您所见,阅读起来简直让人头疼。如果我想使用三元组而不是元组的元组,情况会变得更糟:

val result = (((seqA zip seqB).zipWithIndex) map {case (t,i) => (t._1,t._2,i)}).find{case (a,b,i) => !a.equals(b)} match{
    case Some((a,b,i)) => s"seqA and seqB differ in pos $i: $a <> $b"
    case _ => "no difference"
}

我知道 diff 方法。不幸的是,那个忽略了元素的顺序。

这个好一点:

(as zip bs).zipWithIndex.collectFirst { case ((a,b),i) if a!=b => i }

参见:

def firstDiff[A,B](as: Seq[A], bs: Seq[B]) = (as zip bs).zipWithIndex.collectFirst { case ((a,b),i) if a!=b => i }

firstDiff(Seq(1,2,3,4), Seq(1,2,9,4))
// res1: Option[Int] = Some(2)

如果你想要 ab 在输出中:

(as zip bs).zipWithIndex.collectFirst { case ((a,b),i) if a!=b => (i,a,b) }

此外:如果您希望它像您的 corresponds 示例一样,您可以将其作为扩展方法来实现:

implicit class Enriched_counts_TraversableOnce[A](val as: TraversableOnce[A]) extends AnyVal {
  def firstDiff[B](bs: TraversableOnce[B]): Option[Int] = {
    (as.toIterator zip bs.toIterator)
      .zipWithIndex
      .collectFirst { case ((a,b),i) if a!=b => i }
  }
}

Seq(1,2,3,4).firstDiff(Seq(1,2,9,4))
// res2: Option[Int] = Some(2)

甚至:

implicit class Enriched_counts_TraversableOnce[A](val as: TraversableOnce[A]) extends AnyVal {
  def firstDiff2[B](bs: TraversableOnce[B])(p: (A,B) => Boolean): Option[Int] = {
    (as.toIterator zip bs.toIterator)
      .zipWithIndex
      .collectFirst { case ((a,b),i) if !p(a,b) => i }
  }
}

Seq(1,2,3,4).firstDiff2(Seq(1,2,9,4)){ _ == _ }
// res3: Option[Int] = Some(2)

您可以按如下方式使用indexWhere(参见ScalaDoc):

(as zip bs).indexWhere{case (x,y) => x != y}

示例:

scala> val as = List(1,2,3,4)
scala> val bs = List(1,2,4,4)

scala> (as zip bs).indexWhere{case (x,y) => x != y}

res0: Int = 2

但是,请注意,如果一个 Seq 比另一个长(zip 截断较长的 Seq ),所有基于 zip 的解决方案都可能报告没有差异 - 这可能是也可能不是你想要的需要...

更新:对于等长的Seqs,不同的做法如下:

as.indices.find(i => as(i) != bs(i))

这很好,因为它是 returns 和 Option[Int],所以如果 Seq 之间没有区别,它是 returns None 而不是神奇的 -1。

如果 asbs 短,它的行为与其他解决方案相同,但如果 as 更长,则失败(当然,您可以采用最小长度)。

但是,因为它通过索引寻址两个 Seq,所以它只会在 IndexedSeq 秒内表现良好。

更新2:我们可以使用lift来处理不同的Seq长度,这样我们在按索引检索元素时得到一个Option:

bs.indices.find(i => as.lift(i) != bs.lift(i))

所以如果 as = [1,2]bs = [1,2,3],它们不同的第一个索引是 2(因为 as 中缺少这个元素)。然而,在这种情况下,我们需要在最长的 Seq 而不是最短的 Seq 上调用 indices - 或者使用 max 显式检查哪个最长,例如

(0 until (as.length max bs.length)).find(i => as.lift(i) != bs.lift(i))