Scala: Usuń duplikaty na liście obiektów
Mam listę obiektów List[Object]
, które są instancjami z tej samej klasy. Ta klasa ma pole, które musi być unikalne Object.property
. Jaki jest najczystszy sposób na iterację listy obiektów i usunięcie wszystkich obiektów (oprócz pierwszego) z tą samą właściwością?
8 answers
list.groupBy(_.property).map(_._2.head)
Wyjaśnienie: metoda groupBy akceptuje funkcję, która przekształca element w klucz do grupowania. _.property
jest skrótem od elem: Object => elem.property
(kompilator generuje unikalną nazwę, coś w rodzaju x$1
). Więc teraz mamy mapę Map[Property, List[Object]]
. A Map[K,V]
rozszerza Traversable[(K,V)]
. Więc może być przemierzana jak lista, ale elementy są krotką. Jest to podobne do Javy Map#entrySet()
. Metoda map tworzy nową kolekcję poprzez iterację każdego elementu i zastosowanie do niego funkcji. W tym przypadku funkcją jest _._2.head
, która jest skrótem od elem: (Property, List[Object]) => elem._2.head
. _2
jest tylko metodą krotki, która zwraca drugi element. Drugim elementem jest List [Object] i head
zwraca pierwszy element
Aby wynik był typem, który chcesz:
import collection.breakOut
val l2: List[Object] = list.groupBy(_.property).map(_._2.head)(breakOut)
Aby krótko wyjaśnić, map
w rzeczywistości oczekuje dwóch argumentów, funkcji i obiektu, który jest używany do konstruowania wyniku. W pierwszym fragmencie kodu nie widzisz drugiej wartości, ponieważ jest ona oznaczona jako domyślna i tak dostarczana przez kompilator z listy predefiniowane wartości w zakresie. Wynik jest zwykle uzyskiwany z zmapowanego kontenera. To zwykle dobrze. mapa na liście zwróci listę, mapa na tablicy zwróci tablicę itd. W tym przypadku jednak chcemy wyrazić kontener, który chcemy jako wynik. W tym miejscu stosuje się metodę breakOut. Tworzy konstruktor (rzecz, która buduje wyniki), patrząc tylko na pożądany typ wyniku. Jest to metoda generyczna, a kompilator wywnioskował jej typy generyczne, ponieważ jawnie wpisaliśmy l2 do być List[Object]
lub, aby zachować porządek (zakładając, że {[18] } jest typu 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
jest metodą, która akceptuje początkowy wynik oraz funkcją, która akceptuje element i zwraca zaktualizowany wynik. Metoda iteruje każdy element, aktualizując wynik zgodnie z zastosowaniem funkcji do każdego elementu i zwracając wynik końcowy. Przechodzimy od prawej do lewej (zamiast od lewej do prawej z foldLeft
), ponieważ poprzedzamy objects
- to jest O(1), ale dodawanie to O (N). Zobacz też dobra stylizacja tutaj, używamy dopasowania wzoru, aby wyodrębnić elementy.
W tym przypadku wynikiem początkowym jest para (krotka) pustej listy i zbioru. Lista jest wynikiem, który nas interesuje, a zestaw służy do śledzenia, jakie właściwości już napotkaliśmy. W każdej iteracji sprawdzamy, czy zbiór props
zawiera już właściwość (w Scali obj(x)
jest tłumaczony na obj.apply(x)
). W Set
, metoda apply
jest def apply(a: A): Boolean
. Oznacza to, że akceptuje element i zwraca true / false, jeśli istnieje lub nie). Jeśli właściwość istnieje (już napotkana), wynik jest zwracany jako-is. W przeciwnym razie wynik zostanie zaktualizowany tak, aby zawierał obiekt (o :: objects
) i właściwość zostanie zapisana (props + o.property
)
Update: @ andreypopp chciał metody generycznej:
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)
Do użycia:
scala> list.distinctBy(_.property)
res7: List[Obj] = List(Obj(1), Obj(2), Obj(3))
Zauważ również, że jest to dość wydajne, ponieważ używamy konstruktora. Jeśli masz naprawdę duże listy, możesz użyć zmiennego HashSet zamiast zwykłego zestawu i porównać wydajność.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-29 08:32:45
Oto trochę podstępne, ale szybkie rozwiązanie, które zachowuje porządek:
list.filterNot{ var set = Set[Property]()
obj => val b = set(obj.property); set += obj.property; b}
Chociaż używa wewnętrznie var, myślę, że jest łatwiejszy do zrozumienia i odczytania niż rozwiązanie foldLeft.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-21 19:11:20
Jeszcze jedno rozwiązanie
@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)
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-29 08:36:08
Z zachowaniem kolejności:
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-24 18:20:01
Znalazłem sposób, aby to działało z groupBy, z jednym pośrednim krokiem:
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)
}
Użyj go tak:
scala> distinctBy(List(redVolvo, bluePrius, redLeon))(_.color)
res0: List[Car] = List(redVolvo, bluePrius)
Podobne do pierwszego rozwiązania IttayD, ale filtruje oryginalną kolekcję na podstawie zbioru unikalnych wartości. Jeśli moje oczekiwania są prawidłowe, to wykonuje trzy przejścia: jeden dla groupBy
, jeden dla map
i jeden dla filter
. Utrzymuje kolejność oryginalnego zbioru, ale niekoniecznie przyjmuje pierwszą wartość dla każdej nieruchomości. Na przykład, może zamiast tego powróciły List(bluePrius, redLeon)
.
Oczywiście rozwiązanie IttayD jest jeszcze szybsze, ponieważ robi tylko jedno przejście.
Moje rozwiązanie ma również tę wadę, że jeśli kolekcja ma Car
s, które są rzeczywiście takie same, oba będą na liście wyjściowej. Można to naprawić, usuwając filter
i zwracając uniqueValues
bezpośrednio z typem From[T]
. Wydaje się jednak, że CanBuildFrom[Map[P, From[T]], T, From[T]]
nie istnieje... sugestie są mile widziane!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-29 08:41:23
Wiele dobrych odpowiedzi powyżej. Jednak distinctBy
jest już w Scali, ale w niezbyt oczywistym miejscu. Być może możesz użyć go jak
def distinctBy[B](xs: List[A])(f: A => B): List[A] =
scala.reflect.internal.util.Collections.distinctBy(xs)(f)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-20 13:10:02
Począwszy od Scala 2.13
, większość kolekcji jest teraz zaopatrzona w distinctBy
metoda, która zwraca wszystkie elementy sekwencji ignorując duplikaty po zastosowaniu danej funkcji przekształcającej:
list.distinctBy(_.property)
Na przykład:
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))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-02 00:18:09
Nie wiem jakiej wersji Scali używasz, ale 2.8.2 zdecydowanie ma
list.distinct
Edit (fixing the down votes)
list.distinctBy
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-13 17:25:15