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)
如果你想要 a
和 b
在输出中:
(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。
如果 as
比 bs
短,它的行为与其他解决方案相同,但如果 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))
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)
如果你想要 a
和 b
在输出中:
(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。
如果 as
比 bs
短,它的行为与其他解决方案相同,但如果 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))