未ソート整数のリストがあり、重複している要素を見つけたいです。
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 } )
これを解決する簡単な方法はありますか?
これを試して:
_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 }
_
データを繰り返し渡す必要がないため、指定したアプローチよりもはるかに高速です。
パーティーに少し遅れましたが、別のアプローチがあります:
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)
要約: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
非常に大きなList
sの場合、このより効率的なバージョン(追加の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ライブラリ、 ScalaOlio のfilterDupes
関数の適応です。 org.scalaolio.collection.immutable.List_._
にあります。
私のお気に入りは
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)
}