2015-05-28 1 views
3

Scala поставляется с красивым corresponds способом:Scala - найти первое положение, в котором два Seq отличаются

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) ... 

И я совсем как краткость этого.

Существует ли уже подобный метод, который также сообщит мне о первой позиции, в которой эти две последовательности отличаются?

I.e. есть более идиоматический способ, чтобы написать что-то вроде этого:

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. К сожалению, это не учитывает порядок элементов.

+0

FYI: Вы должны написать 'a.equals (б)' как 'а = b' – dhg

+0

Помимо:! Я не думаю, что вам нужно' случай _ => false', потому что 'zip' усечет чем длиннее два входа (если они отличаются по длине), значит, ваш первый случай всегда будет соответствовать? – DNA

ответ

7

Вы можете использовать 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: Для Seqs одинаковой длины, другой подход заключается в следующем:

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

Это хорошо, как он возвращает Option[Int], поэтому она возвращает None, а не магический -1, если нет никакой разницы между Seqs.

Он ведет себя так же, как и у другого решения, если as короче bs, но сбой, если as длиннее (вы, конечно, можете взять минимальную длину).

Однако, поскольку он адресован как Seqs по индексу, он будет хорошо работать только для IndexedSeq.

Update 2: Мы можем иметь дело с различным последом длиной с помощью 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)) 
+1

Есть несколько решений по спискам несоответствующих размеров здесь: http://stackoverflow.com/questions/8621902/in-scala-is-it-possible-to-zip-two-lists-of-differing-sizes – Daenyth

3

Это немного лучше:

(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) 
Смежные вопросы