web-dev-qa-db-ja.com

リストで重複を見つける方法は?

未ソート整数のリストがあり、重複している要素を見つけたいです。

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)

Dup.distinctを使用してセットの異なる要素を見つけることができるので、次のように答えを書きました。

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)
val distinct = dup.distinct
val elementsWithCounts = distinct.map( (a:Int) => (a, dup.count( (b:Int) => a == b )) )
val duplicatesRemoved = elementsWithCounts.filter( (pair: Pair[Int,Int]) => { pair._2 <= 1 } )
val withDuplicates = elementsWithCounts.filter( (pair: Pair[Int,Int]) => { pair._2 > 1 } )

これを解決する簡単な方法はありますか?

28
Phil

これを試して:

_val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)
dup.groupBy(identity).collect { case (x, List(_,_,_*)) => x }
_

groupByは、個々の整数をその出現のリストに関連付けます。 collectは基本的にmapで、一致しない要素は無視されます。 caseに続く一致パターンは、パターンList(_,_,_*)に適合するリストに関連付けられた整数xに一致します。リストは、少なくとも2つの要素があり、それぞれアンダースコアで表されます実際にそれらの値を保存する必要はありません(そして、これらの2つの要素の後にゼロ個以上の要素が続くことがあります:__*_)。

次のこともできます。

_dup.groupBy(identity).collect { case (x,ys) if ys.lengthCompare(1) > 0 => x }
_

データを繰り返し渡す必要がないため、指定したアプローチよりもはるかに高速です。

51
dhg

パーティーに少し遅れましたが、別のアプローチがあります:

dup.diff(dup.distinct).distinct

diffは、引数内の項目より上のすべての追加項目を提供します(dup.distinct)、これは重複です。

別のアプローチは、foldLeftを使用し、それを困難な方法で行うことです。

2つの空のセットから始めます。 1つは、少なくとも1回は見た要素です。もう1つは、少なくとも2回見た要素(重複)です。

リストを走査します。現在の要素が既に表示されている場合(seen(cur))は重複しているため、duplicatesに追加されます。それ以外の場合は、seenに追加します。結果は、重複を含む2番目のセットになります。

これを汎用メソッドとして書くこともできます。

def dups[T](list: List[T]) = list.foldLeft((Set.empty[T], Set.empty[T])){ case ((seen, duplicates), cur) => 
  if(seen(cur)) (seen, duplicates + cur) else (seen + cur, duplicates)      
}._2

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)

dups(dup) //Set(1,5,101)
2
Kigyo

要約:List.distinctと複数回出現する各要素で構成されるListの両方を返す非常に効率的な関数を記述しました。要素の複製が出現したインデックス。

詳細:私がしたように、複製自体についてもう少し情報が必要な場合は、Listを反復処理するより一般的な関数を記述しました(順序は重要でした)一度だけ、元のList重複排除(最初の以降のすべての重複が削除されます。つまり、distinctの呼び出しと同じ)および各重複を示す2番目のListと、Intインデックスで構成されるTuple2を返します元のList内で発生しました。

Scalaコレクションの一般的なパフォーマンス特性 ;に基づいて、関数を2回実装しました。 filterDupesL(Lは線形の場合)およびfilterDupesEc(Ecは実質的に定数の場合)。

「線形」関数は次のとおりです。

def filterDupesL[A](items: List[A]): (List[A], List[(A, Int)]) = {
  def recursive(
      remaining: List[A]
    , index: Int =
        0
    , accumulator: (List[A], List[(A, Int)]) =
        (Nil, Nil)): (List[A], List[(A, Int)]
  ) =
    if (remaining.isEmpty)
      accumulator
    else
      recursive(
          remaining.tail
        , index + 1
        , if (accumulator._1.contains(remaining.head)) //contains is linear
          (accumulator._1, (remaining.head, index) :: accumulator._2)
        else
          (remaining.head :: accumulator._1, accumulator._2)
      )
  val (distinct, dupes) = recursive(items)
  (distinct.reverse, dupes.reverse)
}

以下は、もう少し直感的になるかもしれない例です。この文字列値のリストがある場合:

val withDupes =
  List("a.b", "a.c", "b.a", "b.b", "a.c", "c.a", "a.c", "d.b", "a.b")

...そして、次を実行します:

val (deduped, dupeAndIndexs) =
  filterDupesL(withDupes)

...結果は次のとおりです。

deduped: List[String] = List(a.b, a.c, b.a, b.b, c.a, d.b)
dupeAndIndexs: List[(String, Int)] = List((a.c,4), (a.c,6), (a.b,8))

そして、単に複製が必要な場合は、単にmap全体でdupeAndIndexesを呼び出し、distinctを呼び出します。

val dupesOnly =
  dupeAndIndexs.map(_._1).distinct

...またはすべてを1回の呼び出しで:

val dupesOnly =
  filterDupesL(withDupes)._2.map(_._1).distinct

...またはSetが優先される場合は、distinctをスキップしてtoSetを呼び出します...

val dupesOnly2 =
  dupeAndIndexs.map(_._1).toSet

...またはすべてを1回の呼び出しで:

val dupesOnly2 =
  filterDupesL(withDupes)._2.map(_._1).toSet

非常に大きなListsの場合、このより効率的なバージョン(追加のSetを使用して、実質的に一定時間でcontainsチェックを変更すること)を検討してください。

「効果的に定数」関数は次のとおりです。

def filterDupesEc[A](items: List[A]): (List[A], List[(A, Int)]) = {
  def recursive(
      remaining: List[A]
    , index: Int =
        0
    , seenAs: Set[A] =
        Set()
    , accumulator: (List[A], List[(A, Int)]) =
        (Nil, Nil)): (List[A], List[(A, Int)]
  ) =
    if (remaining.isEmpty)
      accumulator
    else {
      val (isInSeenAs, seenAsNext) = {
        val isInSeenA =
          seenAs.contains(remaining.head) //contains is effectively constant
        (
            isInSeenA
          , if (!isInSeenA)
              seenAs + remaining.head
            else
              seenAs
        )
      }
      recursive(
          remaining.tail
        , index + 1
        , seenAsNext
        , if (isInSeenAs)
          (accumulator._1, (remaining.head, index) :: accumulator._2)
        else
          (remaining.head :: accumulator._1, accumulator._2)
      )
    }
  val (distinct, dupes) = recursive(items)
  (distinct.reverse, dupes.reverse)
}

上記の関数は両方とも、私のオープンソースScalaライブラリ、 ScalaOliofilterDupes関数の適応です。 org.scalaolio.collection.immutable.List_._にあります。

2

私のお気に入りは

def hasDuplicates(in: List[Int]): Boolean = {
  val sorted = in.sortWith((i, j) => i < j)
  val zipped = sorted.tail.Zip(sorted)
  zipped.exists(p => p._1 == p._2)
}
0
user8221989