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 3

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 по индексу, он будет хорошо работать только для IndexedSeqs.

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)