オブジェクトのリストを持っていますList[Object]
これらはすべて同じクラスからインスタンス化されます。このクラスには一意のフィールドが必要ですObject.property
。オブジェクトのリストを反復処理し、同じプロパティを持つすべてのオブジェクト(ただし最初のオブジェクト)を削除する最もクリーンな方法は何ですか?
_list.groupBy(_.property).map(_._2.head)
_
説明:groupByメソッドは、グループ化のために要素をキーに変換する関数を受け入れます。 __.property
_は、単に_elem: Object => elem.property
_の省略形です(コンパイラーは_x$1
_のような一意の名前を生成します)。これで、マップ_Map[Property, List[Object]]
_ができました。 _Map[K,V]
_はTraversable[(K,V)]
を拡張します。そのため、リストのように走査できますが、要素はタプルです。これは、JavaのMap#entrySet()
に似ています。 mapメソッドは、各要素を繰り返して関数を適用することにより、新しいコレクションを作成します。この場合、関数はelem: (Property, List[Object]) => elem._2.head
の省略形である__._2.head
_です。 __2
_は、2番目の要素を返すTupleの単なるメソッドです。 2番目の要素はList [Object]で、head
は最初の要素を返します
結果を希望する型にするには:
_import collection.breakOut
val l2: List[Object] = list.groupBy(_.property).map(_._2.head)(breakOut)
_
簡単に説明すると、map
は実際には2つの引数、関数と結果を構築するために使用されるオブジェクトを想定しています。最初のコードスニペットでは、2番目の値は暗黙的としてマークされているため、スコープ内の事前定義値のリストからコンパイラーによって提供されるため、2番目の値は表示されません。通常、結果はマップされたコンテナから取得されます。これは通常良いことです。リスト上のマップはリストを返し、配列上のマップは配列などを返します。しかし、この場合、結果として必要なコンテナを表現したいと思います。ここでbreakOutメソッドが使用されます。目的の結果タイプのみを表示することにより、ビルダー(結果を構築するもの)を構築します。これはジェネリックメソッドであり、コンパイラーは、l2を明示的に_List[Object]
_と入力するか、順序を保持するためにジェネリック型を推測します(_Object#property
_はProperty
型であると仮定します)。
_list.foldRight((List[Object](), Set[Property]())) {
case (o, cum@(objects, props)) =>
if (props(o.property)) cum else (o :: objects, props + o.property))
}._1
_
foldRight
は、初期結果を受け入れるメソッドと、要素を受け入れて更新された結果を返す関数です。このメソッドは各要素を繰り返し、関数を各要素に適用して結果を更新し、最終結果を返します。 foldLeft
の前に追加するため、右から左に(objects
で左から右にではなく)移動します。これはO(1)ですが、追加はO(N)です。また、ここで適切なスタイリングを確認してください。パターンマッチを使用して要素を抽出しています。
この場合、初期結果は空のリストとセットのペア(タプル)です。リストは、私たちが興味を持っている結果であり、このセットは、すでに遭遇したプロパティを追跡するために使用されます。各反復で、セットprops
にすでにプロパティが含まれているかどうかを確認します(Scalaでは、obj(x)
はobj.apply(x)
に変換されます。Set
では、メソッドapply
はdef apply(a: A): Boolean
です。つまり、要素を受け入れ、存在するかどうかに応じてtrue/falseを返します。プロパティが存在する場合(既に検出されている場合)、結果はそのまま返されます。それ以外の場合、結果が更新されてオブジェクト(_o :: objects
_)が含まれ、プロパティが記録されます(_props + o.property
_)
更新:@andreypoppは一般的なメソッドが必要でした:
_import scala.collection.IterableLike
import scala.collection.generic.CanBuildFrom
class RichCollection[A, Repr](xs: IterableLike[A, Repr]){
def distinctBy[B, That](f: A => B)(implicit cbf: CanBuildFrom[Repr, A, That]) = {
val builder = cbf(xs.repr)
val i = xs.iterator
var set = Set[B]()
while (i.hasNext) {
val o = i.next
val b = f(o)
if (!set(b)) {
set += b
builder += o
}
}
builder.result
}
}
implicit def toRich[A, Repr](xs: IterableLike[A, Repr]) = new RichCollection(xs)
_
使用する:
_scala> list.distinctBy(_.property)
res7: List[Obj] = List(Obj(1), Obj(2), Obj(3))
_
また、ビルダーを使用しているため、これは非常に効率的です。リストが非常に大きい場合は、通常のセットの代わりに可変HashSetを使用して、パフォーマンスをベンチマークすることができます。
順序を保持する少し卑劣だが高速なソリューションを次に示します。
list.filterNot{ var set = Set[Property]()
obj => val b = set(obj.property); set += obj.property; b}
内部でvarを使用していますが、foldLeft-solutionよりも理解しやすく、読みやすいと思います。
開始Scala 2.13
、ほとんどのコレクションに distinctBy
メソッドが追加されました。このメソッドは、特定の変換関数を適用した後、重複を無視してシーケンスのすべての要素を返します。
list.distinctBy(_.property)
例えば:
List(("a", 2), ("b", 2), ("a", 5)).distinctBy(_._1) // List((a,2), (b,2))
List(("a", 2.7), ("b", 2.1), ("a", 5.4)).distinctBy(_._2.floor) // List((a,2.7), (a,5.4))
もう一つのソリューション
@tailrec
def collectUnique(l: List[Object], s: Set[Property], u: List[Object]): List[Object] = l match {
case Nil => u.reverse
case (h :: t) =>
if (s(h.property)) collectUnique(t, s, u) else collectUnique(t, s + h.prop, h :: u)
}
順序を保持する場合:
def distinctBy[L, E](list: List[L])(f: L => E): List[L] =
list.foldLeft((Vector.empty[L], Set.empty[E])) {
case ((acc, set), item) =>
val key = f(item)
if (set.contains(key)) (acc, set)
else (acc :+ item, set + key)
}._1.toList
distinctBy(list)(_.property)
私は、1つの中間ステップで、groupByで動作させる方法を見つけました:
_def distinctBy[T, P, From[X] <: TraversableLike[X, From[X]]](collection: From[T])(property: T => P): From[T] = {
val uniqueValues: Set[T] = collection.groupBy(property).map(_._2.head)(breakOut)
collection.filter(uniqueValues)
}
_
次のように使用します。
_scala> distinctBy(List(redVolvo, bluePrius, redLeon))(_.color)
res0: List[Car] = List(redVolvo, bluePrius)
_
IttayDの最初のソリューションに似ていますが、一意の値のセットに基づいて元のコレクションをフィルタリングします。私の期待が正しい場合、これは3つのトラバーサルを実行します。1つはgroupBy
、1つはmap
、1つはfilter
です。元のコレクションの順序を維持しますが、必ずしも各プロパティの最初の値を取得するとは限りません。たとえば、代わりにList(bluePrius, redLeon)
を返した可能性があります。
もちろん、IttayDのソリューションは、たった1つのトラバーサルを行うため、さらに高速です。
私のソリューションには、コレクションに実際に同じCar
sがある場合、両方が出力リストに含まれるという欠点もあります。これは、filter
を削除し、タイプ_From[T]
_でuniqueValues
を直接返すことで修正できます。ただし、_CanBuildFrom[Map[P, From[T]], T, From[T]]
_は存在しないようです...提案を歓迎します!
上記の多くの良い答え。ただし、distinctBy
は既にScalaにありますが、それほど明白ではありません。おそらくあなたはそれを次のように使うことができます
def distinctBy[A, B](xs: List[A])(f: A => B): List[A] =
scala.reflect.internal.util.Collections.distinctBy(xs)(f)