2015-05-05 17 views
12

Załóżmy, że mam Collection i Predicate pasujący do elementów, które chciałbym usunąć z Collection. Ale ja nie chcę ich odrzucać, chcę przenieść dopasowane elementy do nowej kolekcji. Chciałbym zrobić coś takiego this w Javie 7:Usuwanie i zbieranie elementów za pomocą strumieni Java

List<E> removed = new LinkedList<>(); 
for (Iterator<E> i = data.iterator(); i.hasNext();) { 
    E e = i.next(); 
    if (predicate.test(e)) { 
     removed.add(e); 
     i.remove(); 
    } 
} 

Jestem ciekaw czy istnieje strumienie/Java 8 sposobów, aby to zrobić. Collections.removeIf() niestety po prostu zwraca boolean (nawet zliczania liczby elementów usuniętych Zbyt źle?). I wyobrazić sobie coś takiego (choć oczywiście .removeAndYield(Predicate) nie istnieje):

List<E> removed = data.removeAndYield(predicate).collect(Collectors.toList()); 

Uwaga: to pytanie inspirowane przez a similar question; to pytanie dotyczy bardziej ogólnego przypadku pobrania strumienia z elementów usuniętych z kolekcji. Jak wskazano w połączonym pytaniu, rozwiązanie imperatywne może być bardziej czytelne, ale jestem ciekawy, czy jest to nawet możliwe w przypadku strumieni.

Edycja: Oczywiście możemy podzielić zadanie na dwa osobne etapy i przy założeniu odpowiednich struktur danych będzie to efektywne. Pytanie może dotyczyć dowolnych kolekcji (które mogą nie być wydajne .contains() itp.).

+0

Trudność polega na tym, że nie można usunąć elementów z 'Seta' podczas iteracji bez uzyskania' ConcurrentModificationException', więc każda metoda Java 8 wymagałaby co najmniej dwóch iteracji, jak w odpowiedzi Mishy. AFAIK jedynym sposobem na zrobienie tego przy pomocy pojedynczej iteracji 'set' jest użycie jawnego' Iteratora'. –

+0

Możesz za pomocą 'Iteratora' nad' Collection', co sugeruje, że jest to możliwe z koncepcją 'Stream'. Oczywiście nie oznacza to, że jest dostępny po wyjęciu z pudełka, ale może nie jest to skomplikowane. A może jest jakiś dobry powód, dla którego nie jest dostępny w JDK. – dimo414

+0

Przykład "Java 7" w moim pytaniu demonstruje usuwanie elementów w połowie iteracji. Jeśli możemy zrobić to w jednym przebiegu, to czy można to zrobić funkcjonalnie? – dimo414

Odpowiedz

11

bym keep it simple:

Set<E> removed = set.stream() 
    .filter(predicate) 
    .collect(Collectors.toSet()); 

set.removeAll(removed); 
+0

Podoba mi się to (+1), chociaż ostatnie 2 z 3 opcji są lepsze od pierwszych, ponieważ tylko te przechodzą przez 'usunięty' zamiast całego' zestawu'. –

+1

Zgadzam się w przypadku, gdy oryginalna struktura jest zbiorem. Jeśli jednak struktura źródłowa nie jest zbiorem, 'remove.forEach' usunie tylko pierwsze wystąpienie pasującego elementu z kolekcji, podczas gdy' removeIf' i 'removeAll' usunie je wszystkie. Ogólnie rzecz biorąc, myślę, że 'removeAll' jest najlepszym wyborem. – Misha

+0

@pbabcdefp Zmieniłem odpowiedź, aby usunąć niepotrzebne opcje. – Misha

4

Jeśli chcesz sposób funkcjonalny, aby to zrobić, można napisać swoją własną metodę.

static <E> Set<E> removeIf(Collection<? extends E> collection, Predicate<? super E> predicate) { 
    Set<E> removed = new HashSet<>(); 
    for (Iterator<? extends E> i = collection.iterator(); i.hasNext();) { 
     E e = i.next(); 
     if (predicate.test(e)) { 
      removed.add(e); 
      i.remove(); 
     } 
    } 
    return removed; 
} 

ten może być używany, aby usunąć wszystkie numery nieparzyste od List.

Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6)); 
Set<Integer> removed = removeIf(set, i -> i % 2 != 0); 
System.out.println(set); 
System.out.println(removed); 
16

Jeśli nie masz nic przeciwko, pozwól, że nieco ugięję twoje wymagania. :-)

Jedną z cech pożądanego rezultatu jest to, że pasujące elementy powinny znaleźć się w jednej kolekcji, a nie pasujące elementy powinny znaleźć się w innej kolekcji. W świecie zmutowanych pre-Java-8, najłatwiejszym sposobem na zrobienie kolekcji nieodpowiednich elementów jest usunięcie pasujących elementów z oryginalnej kolekcji.

Ale czy usunięcie - modyfikacja oryginalnej listy - nieodłączna część wymogu?

Jeśli tak nie jest, to wynik może być osiągnięty za pomocą prostej operacji partycjonowania:

Map<Boolean, List<E>> map = data.stream().collect(partitioningBy(predicate)); 

Wynik mapie jest zasadniczo dwa listy, które zawierają dobieraniu (klucz = true) i niedopasowanych (key = false) elementy.

Zaletą jest to, że technika ta może być wykonana w jednym przebiegu i równolegle w razie potrzeby. Oczywiście tworzy to duplikat listy elementów niezgodnych w porównaniu do usunięcia meczów z oryginału, ale jest to cena za niezmienność. Kompromisy mogą być tego warte.

+2

Dobra sugestia. Zmutowane kolekcje są często bardziej kłopotliwe, niż są warte, więc dobrze jest zobaczyć prosty sposób na zrobienie tego z niezmiennymi kolekcjami. – dimo414

+1

@ dimo414 Dzięki, i o tak, dziękuję za naprawienie grupowania na partycjonowanie. Ups! –

0

Wystarczy napisać sobie funkcję wielokrotnego użytku tak:

/** 
* Removes all matching element from iterator 
* 
* @param it 
* @param predicate 
*/ 
public static <E> void removeMatching(final Iterator<E> it, final Predicate<E> predicate) { 
    while (it.hasNext()) { 
     final E e = it.next(); 
     if (predicate.test(e)) { 
      it.remove(); 
     } 
    } 
} 

Ja również nie znaleziono wstępnie istniejące rozwiązania dla strumieni. Użycie iterator.remove() wymaga mniej pamięci niż zestaw tymczasowy.

Powiązane problemy