[FIXED] Scala: Entfernen Sie Duplikate in der Objektliste

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. _.propertyist 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.headdie Abkürzung für elem: (Property, List[Object]) => elem._2.head. _2ist nur eine Methode von Tuple, die das zweite Element zurückgibt. Das zweite Element ist List[Object] und headgibt 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, mapeigentlich 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#propertyes 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

foldRightist 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 propsdie Eigenschaft bereits enthält (wird in Scala obj(x)übersetzt in obj.apply(x). In , ist Setdie 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 ( )applydef apply(a: A): Booleano :: objectsprops + 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)

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like