Scala-нахождение первой позиции, в которой различаются два Seq
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"
}
I я знаю о методе diff
. К сожалению, этот человек пренебрегает порядком элементов.
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
zip
, могут не сообщать о различиях, если один Seq длиннее другого (zip
усекает более длинный Seq ) - это может быть или не быть тем, что вам нужно...Update : для секций одинаковой длины другой подход выглядит следующим образом:
as.indices.find(i => as(i) != bs(i))
Это хорошо, поскольку он возвращает
Option[Int]
, поэтому он возвращаетNone
, а не волшебный -1, если нет разницы между Seqs.Он ведет себя так же, как и другое решение, Если
as
корочеbs
, но терпит неудачу, еслиas
длиннее (можно, конечно, взять минимальную длину).Однако, поскольку он обращается к обоим Seq по индексу, он будет хорошо работать только для
IndexedSeq
s.Update 2 : мы можем работать с различными длинами Seq с помощью
lift
, так что мы получаем опцию при извлечении элементов по индексу:bs.indices.find(i => as.lift(i) != bs.lift(i))
Итак, если
as = [1,2]
иbs = [1,2,3]
, первый индекс, по которому они отличаются, равен 2 (поскольку этот элемент отсутствует вas
). Однако в этом случае нам нужно вызватьindices
на самом длинном Seq, а не на самом коротком-или явно проверить, который является самым длинным, используяmax
, например(0 until (as.length max bs.length)).find(i => as.lift(i) != bs.lift(i))
Это немного лучше:
(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)