Java 8, Streams, um die doppelten Elemente zu finden

87

Ich versuche, doppelte Elemente in der Ganzzahlliste aufzulisten, z.

List<Integer> numbers = Arrays.asList(new Integer[]{1,2,1,3,4,4});    

Verwenden von Streams von JDK 8. Hat jemand ausprobiert. Um die Duplikate zu entfernen, können wir die eindeutige () API verwenden. Aber was ist mit dem Finden der duplizierten Elemente? Kann mir jemand helfen?

Siva
quelle
2
Mögliches Duplikat von Collect Stream mit Gruppierungs-, Zähl- und
Filteroperationen
Wenn Sie den Stream nicht sammeln möchten, läuft dies im Wesentlichen darauf hinaus, "wie kann ich mehr als ein Element gleichzeitig in einem Stream anzeigen"?
Thorbjørn Ravn Andersen
Setze <Integer> items = new HashSet (); numbers.stream (). filter (n -> i! tems.add (n)). collect (Collectors.toSet ());
Saroj Kumar Sahoo

Antworten:

127

Sie können verwenden Collections.frequency:

numbers.stream().filter(i -> Collections.frequency(numbers, i) >1)
                .collect(Collectors.toSet()).forEach(System.out::println);
Bao Dinh
quelle
11
Dieselbe O (n ^ 2) -Leistung wie in der Antwort von @OussamaZoghlami , obwohl wahrscheinlich einfacher. Trotzdem hier eine positive Bewertung. Willkommen bei StackOverflow!
Tagir Valeev
6
Wie erwähnt ist dies eine ^ 2-Lösung, bei der eine triviale lineare Lösung existiert. Ich würde das in CR nicht akzeptieren.
Jwilner
3
Es ist möglicherweise langsamer als die @ Dave-Option, aber es ist hübscher, also nehme ich den Performance-Hit.
jDub9
@jwilner ist Ihr Punkt bezüglich der n ^ 2-Lösung in Bezug auf die Verwendung von Collections.frequency in einem Filter?
Mancocapac
5
@mancocapac Ja, es ist quadratisch, weil der Frequenzanruf jedes Element in Zahlen besuchen muss und für jedes Element aufgerufen wird. Somit besuchen wir für jedes Element jedes Element - n ^ 2 und unnötig ineffizient.
Jwilner
71

Grundlegendes Beispiel. In der ersten Hälfte wird die Frequenzkarte erstellt, in der zweiten Hälfte wird sie auf eine gefilterte Liste reduziert. Wahrscheinlich nicht so effizient wie Daves Antwort, aber vielseitiger (wenn Sie genau zwei erkennen möchten usw.)

     List<Integer> duplicates = IntStream.of( 1, 2, 3, 2, 1, 2, 3, 4, 2, 2, 2 )
       .boxed()
       .collect( Collectors.groupingBy( Function.identity(), Collectors.counting() ) )
       .entrySet()
       .stream()
       .filter( p -> p.getValue() > 1 )
       .map( Map.Entry::getKey )
       .collect( Collectors.toList() );
RobAu
quelle
12
Diese Antwort ist imo die richtige, da sie linear ist und nicht gegen die Regel "zustandsloses Prädikat" verstößt.
Jwilner
53

Sie benötigen einen Satz ( allItemsunten), um den gesamten Array-Inhalt aufzunehmen, aber dies ist O (n):

Integer[] numbers = new Integer[] { 1, 2, 1, 3, 4, 4 };
Set<Integer> allItems = new HashSet<>();
Set<Integer> duplicates = Arrays.stream(numbers)
        .filter(n -> !allItems.add(n)) //Set.add() returns false if the item was already in the set.
        .collect(Collectors.toSet());
System.out.println(duplicates); // [1, 4]
Dave
quelle
18
filter()erfordert ein zustandsloses Prädikat. Ihre "Lösung" ähnelt auffallend dem Beispiel eines Stateful-Prädikats im Javadoc: docs.oracle.com/javase/8/docs/api/java/util/stream/…
Matt McHenry
1
@MattMcHenry: Bedeutet das, dass diese Lösung das Potenzial hat, unerwartetes Verhalten hervorzurufen, oder ist es nur eine schlechte Praxis?
IcedDante
7
@IcedDante In einem lokalisierten Fall wie dort, in dem Sie sicher wissen, dass der Stream ist sequential(), ist er wahrscheinlich sicher. In dem allgemeineren Fall, in dem sich der Stream befindet parallel(), ist es ziemlich garantiert, dass er auf seltsame Weise bricht.
Matt McHenry
5
Dies führt nicht nur zu unerwartetem Verhalten in einigen Situationen, sondern auch zu Paradigmen, wie Bloch argumentiert, dass Sie dies in der dritten Ausgabe von Effective Java nicht tun sollten. Wenn Sie dies schreiben, verwenden Sie einfach eine for-Schleife.
Jwilner
6
Fand dies in freier Wildbahn, die von der Einschränkung " Hibernate Validator UniqueElements" verwendet wird .
Dave
14

Ein O (n) Weg wäre wie folgt:

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 4, 4);
Set<Integer> duplicatedNumbersRemovedSet = new HashSet<>();
Set<Integer> duplicatedNumbersSet = numbers.stream().filter(n -> !duplicatedNumbersRemovedSet.add(n)).collect(Collectors.toSet());

Die Raumkomplexität würde sich bei diesem Ansatz verdoppeln, aber dieser Raum ist keine Verschwendung; Tatsächlich haben wir das Duplikat jetzt nur noch als Set sowie als weiteres Set, wobei auch alle Duplikate entfernt wurden.

Thomas Mathew
quelle
13

Meine StreamEx- Bibliothek, die die Java 8-Streams erweitert, bietet eine spezielle Operation, bei distinct(atLeast)der nur Elemente beibehalten werden können, die mindestens so oft angezeigt werden . So kann Ihr Problem folgendermaßen gelöst werden:

List<Integer> repeatingNumbers = StreamEx.of(numbers).distinct(2).toList();

Intern ähnelt es der @ Dave-Lösung, zählt Objekte, unterstützt andere gewünschte Mengen und ist parallel-freundlich (wird ConcurrentHashMapfür parallelisierte Streams verwendet, jedoch HashMapfür sequentielle Streams ). Für große Datenmengen können Sie eine Beschleunigung verwenden .parallel().distinct(2).

Tagir Valeev
quelle
26
Die Frage bezieht sich auf Java Streams, nicht auf Bibliotheken von Drittanbietern.
11 ᄀ
9

Sie können das Duplikat wie folgt erhalten:

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 4, 4);
Set<Integer> duplicated = numbers
  .stream()
  .filter(n -> numbers
        .stream()
        .filter(x -> x == n)
        .count() > 1)
   .collect(Collectors.toSet());
Oussama Zoghlami
quelle
11
Ist das nicht eine O (n ^ 2) -Operation?
Trejkaz
4
Versuchen Sie zu verwendennumbers = Arrays.asList(400, 400, 500, 500);
Tagir Valeev
1
Ist dies ähnlich wie beim Erstellen einer 2-Tiefen-Schleife? für (..) {für (..)} Nur neugierig, wie intern es funktioniert
Redigaffi
Obwohl es ein netter Ansatz ist, ist es teuer , streamdrinnen zu sein stream.
Vishwa Ratna
4

Ich denke, grundlegende Lösungen für die Frage sollten wie folgt sein:

Supplier supplier=HashSet::new; 
HashSet has=ls.stream().collect(Collectors.toCollection(supplier));

List lst = (List) ls.stream().filter(e->Collections.frequency(ls,e)>1).distinct().collect(Collectors.toList());

Nun, es wird nicht empfohlen, eine Filteroperation durchzuführen, aber zum besseren Verständnis habe ich sie verwendet. Außerdem sollte es in zukünftigen Versionen eine benutzerdefinierte Filterung geben.

Prashant
quelle
3

Ein Multiset ist eine Struktur, die die Anzahl der Vorkommen für jedes Element beibehält. Verwenden der Guava-Implementierung:

Set<Integer> duplicated =
        ImmutableMultiset.copyOf(numbers).entrySet().stream()
                .filter(entry -> entry.getCount() > 1)
                .map(Multiset.Entry::getElement)
                .collect(Collectors.toSet());
numéro6
quelle
2

Das Erstellen einer zusätzlichen Karte oder eines zusätzlichen Streams ist zeit- und platzaufwendig.

Set<Integer> duplicates = numbers.stream().collect( Collectors.collectingAndThen(
  Collectors.groupingBy( Function.identity(), Collectors.counting() ),
  map -> {
    map.values().removeIf( cnt -> cnt < 2 );
    return( map.keySet() );
  } ) );  // [1, 4]


… Und für die Frage, von der behauptet wird, sie sei ein [Duplikat]

public static int[] getDuplicatesStreamsToArray( int[] input ) {
  return( IntStream.of( input ).boxed().collect( Collectors.collectingAndThen(
      Collectors.groupingBy( Function.identity(), Collectors.counting() ),
      map -> {
        map.values().removeIf( cnt -> cnt < 2 );
        return( map.keySet() );
      } ) ).stream().mapToInt( i -> i ).toArray() );
}
Kaplan
quelle
1

Wenn Sie nur das Vorhandensein von Duplikaten erkennen müssen (anstatt sie aufzulisten, was das OP wollte), konvertieren Sie sie einfach in eine Liste und einen Satz und vergleichen Sie dann die Größen:

    List<Integer> list = ...;
    Set<Integer> set = new HashSet<>(list);
    if (list.size() != set.size()) {
      // duplicates detected
    }

Ich mag diesen Ansatz, weil er weniger Orte für Fehler hat.

Patrick
quelle
0

Ich denke, ich habe eine gute Lösung, um ein Problem wie dieses zu beheben - Liste => Liste mit Gruppierung nach Something.a & Something.b. Es gibt eine erweiterte Definition:

public class Test {

    public static void test() {

        class A {
            private int a;
            private int b;
            private float c;
            private float d;

            public A(int a, int b, float c, float d) {
                this.a = a;
                this.b = b;
                this.c = c;
                this.d = d;
            }
        }


        List<A> list1 = new ArrayList<A>();

        list1.addAll(Arrays.asList(new A(1, 2, 3, 4),
                new A(2, 3, 4, 5),
                new A(1, 2, 3, 4),
                new A(2, 3, 4, 5),
                new A(1, 2, 3, 4)));

        Map<Integer, A> map = list1.stream()
                .collect(HashMap::new, (m, v) -> m.put(
                        Objects.hash(v.a, v.b, v.c, v.d), v),
                        HashMap::putAll);

        list1.clear();
        list1.addAll(map.values());

        System.out.println(list1);
    }

}

Klasse A, Liste1, es sind nur eingehende Daten - Magie ist in den Objects.hash (...) :)

Zhurov Konstantin
quelle
1
Warnung: Wenn Objects.hashderselbe Wert für (v.a_1, v.b_1, v.c_1, v.d_1)und erzeugt wird (v.a_2, v.b_2, v.c_2, v.d_2), werden sie als gleich betrachtet und als Duplikate entfernt, ohne tatsächlich zu überprüfen, ob a, b, c und d gleich sind. Dies kann ein akzeptables Risiko sein, oder Sie möchten möglicherweise eine andere Funktion verwenden, Objects.hashdie garantiert ein eindeutiges Ergebnis für Ihre Domain liefert.
Marty Neal
0

Müssen Sie die Java 8-Redewendungen (Dämpfe) verwenden? Perphaps Eine einfache Lösung wäre, die Komplexität auf eine kartenähnliche Datenstruktur zu verlagern, die Zahlen als Schlüssel enthält (ohne sich zu wiederholen) und die Zeiten, zu denen sie als Wert auftreten. Sie könnten diese Karte iterieren und nur mit den Zahlen> 1 etwas tun.

import java.lang.Math;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

public class RemoveDuplicates
{
  public static void main(String[] args)
  {
   List<Integer> numbers = Arrays.asList(new Integer[]{1,2,1,3,4,4});
   Map<Integer,Integer> countByNumber = new HashMap<Integer,Integer>();
   for(Integer n:numbers)
   {
     Integer count = countByNumber.get(n);
     if (count != null) {
       countByNumber.put(n,count + 1);
     } else {
       countByNumber.put(n,1);
     }
   }
   System.out.println(countByNumber);
   Iterator it = countByNumber.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry pair = (Map.Entry)it.next();
        System.out.println(pair.getKey() + " = " + pair.getValue());
    }
  }
}
Sieger
quelle
0

Versuchen Sie diese Lösung:

public class Anagramm {

public static boolean isAnagramLetters(String word, String anagramm) {
    if (anagramm.isEmpty()) {
        return false;
    }

    Map<Character, Integer> mapExistString = CharCountMap(word);
    Map<Character, Integer> mapCheckString = CharCountMap(anagramm);
    return enoughLetters(mapExistString, mapCheckString);
}

private static Map<Character, Integer> CharCountMap(String chars) {
    HashMap<Character, Integer> charCountMap = new HashMap<Character, Integer>();
    for (char c : chars.toCharArray()) {
        if (charCountMap.containsKey(c)) {
            charCountMap.put(c, charCountMap.get(c) + 1);
        } else {
            charCountMap.put(c, 1);
        }
    }
    return charCountMap;
}

static boolean enoughLetters(Map<Character, Integer> mapExistString, Map<Character,Integer> mapCheckString) {
    for( Entry<Character, Integer> e : mapCheckString.entrySet() ) {
        Character letter = e.getKey();
        Integer available = mapExistString.get(letter);
        if (available == null || e.getValue() > available) return false;
    }
    return true;
}

}
Ilia Galperin
quelle
0

Was ist mit der Überprüfung von Indizes?

        numbers.stream()
            .filter(integer -> numbers.indexOf(integer) != numbers.lastIndexOf(integer))
            .collect(Collectors.toSet())
            .forEach(System.out::println);
Bagom
quelle
1
Sollte gut funktionieren, aber auch O (n ^ 2) Leistung wie einige andere Lösungen hier.
Florian Albrecht