Ausgabe
Ich habe eine Liste von Objekten List[Object]
, die alle von derselben Klasse instanziiert sind. Diese Klasse hat ein Feld, das eindeutig sein muss Object.property
. Was ist der sauberste Weg, um die Liste der Objekte zu durchlaufen und alle Objekte (außer dem ersten) mit derselben Eigenschaft zu entfernen?
Lösung
list.groupBy(_.property).map(_._2.head)
Erläuterung: Die groupBy-Methode akzeptiert eine Funktion, die ein Element in einen Schlüssel zum Gruppieren umwandelt. _.property
ist nur eine Abkürzung für elem: Object => elem.property
(der Compiler generiert einen eindeutigen Namen, so etwas wie x$1
). Jetzt haben wir also eine Karte Map[Property, List[Object]]
. A Map[K,V]
verlängert Traversable[(K,V)]
. Es kann also wie eine Liste durchlaufen werden, aber Elemente sind ein Tupel. Dies ist ähnlich wie bei Java Map#entrySet()
. Die Map-Methode erstellt eine neue Sammlung, indem sie jedes Element iteriert und eine Funktion darauf anwendet. In diesem Fall ist die Funktion _._2.head
die Abkürzung für elem: (Property, List[Object]) => elem._2.head
. _2
ist nur eine Methode von Tuple, die das zweite Element zurückgibt. Das zweite Element ist List[Object] und head
gibt das erste Element zurück
So erhalten Sie als Ergebnis den gewünschten Typ:
import collection.breakOut
val l2: List[Object] = list.groupBy(_.property).map(_._2.head)(breakOut)
Um es kurz zu erklären, map
eigentlich werden zwei Argumente erwartet, eine Funktion und ein Objekt, das verwendet wird, um das Ergebnis zu konstruieren. Im ersten Codeausschnitt sehen Sie den zweiten Wert nicht, da er als implizit markiert ist und daher vom Compiler aus einer Liste vordefinierter Werte im Gültigkeitsbereich bereitgestellt wird. Das Ergebnis wird in der Regel aus dem gemappten Container bezogen. Dies ist normalerweise eine gute Sache. map on List gibt List zurück, map on Array liefert Array usw. In diesem Fall möchten wir jedoch den gewünschten Container als Ergebnis ausdrücken. Hier kommt die BreakOut-Methode zum Einsatz. Es erstellt einen Builder (das Ding, das Ergebnisse erstellt), indem es nur den gewünschten Ergebnistyp betrachtet. Es ist eine generische Methode, und der Compiler leitet ihre generischen Typen ab, weil wir l2 explizit als List[Object]
oder eingegeben haben, um die Reihenfolge beizubehalten (vorausgesetzt, Object#property
es ist vom TypProperty
):
list.foldRight((List[Object](), Set[Property]())) {
case (o, cum@(objects, props)) =>
if (props(o.property)) cum else (o :: objects, props + o.property))
}._1
foldRight
ist eine Methode, die ein Anfangsergebnis akzeptiert, und eine Funktion, die ein Element akzeptiert und ein aktualisiertes Ergebnis zurückgibt. Die Methode iteriert jedes Element, aktualisiert das Ergebnis entsprechend der Anwendung der Funktion auf jedes Element und gibt das Endergebnis zurück. Wir gehen von rechts nach links (anstatt von links nach rechts mit foldLeft
), weil wir objects
– das ist O(1) voranstellen, aber das Anhängen ist O(N). Beachten Sie auch hier das gute Styling, wir verwenden einen Musterabgleich, um die Elemente zu extrahieren.
In diesem Fall ist das anfängliche Ergebnis ein Paar (Tupel) aus einer leeren Liste und einer Menge. Die Liste ist das Ergebnis, an dem wir interessiert sind, und die Menge wird verwendet, um zu verfolgen, auf welche Eigenschaften wir bereits gestoßen sind. Bei jeder Iteration prüfen wir, ob die Menge props
die Eigenschaft bereits enthält (wird in Scala obj(x)
übersetzt in obj.apply(x)
. In , ist Set
die Methode . Das heißt, akzeptiert ein Element und gibt true / false zurück, wenn es existiert oder nicht). Wenn die Eigenschaft existiert (bereits gefunden wurde), wird das Ergebnis unverändert zurückgegeben. Andernfalls wird das Ergebnis so aktualisiert, dass es das Objekt enthält ( ) und die Eigenschaft aufgezeichnet wird ( )apply
def apply(a: A): Boolean
o :: objects
props + o.property
Update: @andreypopp wollte eine generische Methode:
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)
benutzen:
scala> list.distinctBy(_.property)
res7: List[Obj] = List(Obj(1), Obj(2), Obj(3))
Beachten Sie auch, dass dies ziemlich effizient ist, da wir einen Builder verwenden. Wenn Sie wirklich große Listen haben, möchten Sie möglicherweise ein veränderliches HashSet anstelle eines regulären Sets verwenden und die Leistung bewerten.
Beantwortet von – IttayD
Antwort geprüft von – Gilberto Lyons (FixError Admin)