Java 8, Streams pour trouver les éléments en double


87

J'essaie de lister les éléments en double dans la liste d'entiers, par exemple,

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

using Streams of jdk 8. Quelqu'un at-il essayé. Pour supprimer les doublons, nous pouvons utiliser l'API distinct (). Mais qu'en est-il de trouver les éléments dupliqués? Quelqu'un peut-il m'aider?



Si vous ne souhaitez pas collecter le flux, cela se résume essentiellement à "comment puis-je regarder plus d'un élément à la fois dans un flux"?
Thorbjørn Ravn Andersen

Set <Integer> items = new HashSet (); nombres.stream (). filter (n -> i! tems.add (n)). collect (Collectors.toSet ());
Saroj Kumar Sahoo

Réponses:


127

Vous pouvez utiliser Collections.frequency:

numbers.stream().filter(i -> Collections.frequency(numbers, i) >1)
                .collect(Collectors.toSet()).forEach(System.out::println);

11
Les mêmes performances O (n ^ 2) que dans la réponse @OussamaZoghlami , bien que probablement plus simple. Néanmoins, voici un vote positif. Bienvenue dans StackOverflow!
Tagir Valeev

6
Comme mentionné, il s'agit d'une solution ^ 2 dans laquelle une solution linéaire triviale existe. Je n'accepterais pas cela en CR.
jwilner

3
C'est peut-être plus lent que l'option @Dave, mais c'est plus joli donc je vais prendre le coup de performance.
jDub9

@jwilner est votre point concernant la solution n ^ 2 faisant référence à l'utilisation de Collections.frequency dans un filtre?
mancocapac

5
@mancocapac oui, c'est quadratique car l'appel de fréquence doit visiter chaque élément en nombre, et il est appelé sur chaque élément. Ainsi, pour chaque élément, nous visitons chaque élément - n ^ 2 et inutilement inefficace.
jwilner

71

Exemple de base. La première moitié construit la carte de fréquence, la seconde moitié la réduit à une liste filtrée. Probablement pas aussi efficace que la réponse de Dave, mais plus polyvalente (comme si vous voulez en détecter exactement deux, etc.)

     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() );

12
Cette réponse est la bonne imo car elle est linéaire et ne viole pas la règle du "prédicat sans état".
jwilner

53

Vous avez besoin d'un ensemble ( allItemsci-dessous) pour contenir tout le contenu du tableau, mais c'est 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]

18
filter()nécessite un prédicat sans état. Votre «solution» est étonnamment similaire à l'exemple d'un prédicat avec état donné dans le javadoc: docs.oracle.com/javase/8/docs/api/java/util/stream/…
Matt McHenry

1
@MattMcHenry: cela signifie-t-il que cette solution a le potentiel de produire un comportement inattendu, ou s'agit-il simplement d'une mauvaise pratique?
IcedDante

7
@IcedDante Dans un cas localisé comme là où vous savez avec certitude que le flux est sequential(), c'est probablement sûr. Dans le cas plus général où le flux peut être parallel(), il est à peu près garanti qu'il se cassera de manière étrange.
Matt McHenry

5
En plus de produire un comportement inattendu dans certaines situations, cela mélange des paradigmes comme Bloch soutient que vous ne devriez pas dans la troisième édition de Effective Java. Si vous vous surprenez à écrire ceci, utilisez simplement une boucle for.
jwilner

6
Trouvé ceci dans la nature utilisé par la contrainte Hibernate Validator UniqueElements .
Dave

14

Une manière O (n) serait comme ci-dessous:

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());

La complexité de l'espace doublerait dans cette approche, mais cet espace n'est pas un gaspillage; en fait, nous avons maintenant le dupliqué seul uniquement en tant qu'ensemble ainsi qu'un autre ensemble avec tous les doublons supprimés également.


13

Ma bibliothèque StreamEx qui améliore les flux Java 8 fournit une opération spéciale distinct(atLeast)qui ne peut conserver que les éléments apparaissant au moins le nombre de fois spécifié. Ainsi, votre problème peut être résolu comme ceci:

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

En interne, c'est similaire à la solution @Dave, il compte les objets, pour prendre en charge d'autres quantités voulues et il est compatible avec le parallèle (il utilise ConcurrentHashMappour le flux parallélisé, mais HashMappour le séquentiel). Pour de grandes quantités de données, vous pouvez accélérer l'utilisation .parallel().distinct(2).


26
La question concerne les flux Java, pas les bibliothèques tierces.
ᄂ ᄀ

9

Vous pouvez obtenir le dupliqué comme ceci:

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());

11
N'est-ce pas une opération O (n ^ 2)?
Trejkaz

4
Essayez d'utilisernumbers = Arrays.asList(400, 400, 500, 500);
Tagir Valeev

1
Est-ce similaire à la création d'une boucle à 2 profondeurs? for (..) {for (..)} Juste curieux de savoir comment cela fonctionne en interne
redigaffi

Bien que ce soit une bonne approche, avoir à l' streamintérieur streamest coûteux.
Vishwa Ratna

4

Je pense que les solutions de base à la question devraient être les suivantes:

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());

Eh bien, il n'est pas recommandé d'effectuer une opération de filtrage, mais pour une meilleure compréhension, je l'ai utilisé, de plus, il devrait y avoir une filtration personnalisée dans les versions futures.


3

Un multiset est une structure qui maintient le nombre d'occurrences pour chaque élément. Utilisation de l'implémentation de Guava:

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

2

la création d'une carte ou d'un flux supplémentaire prend du temps et de l'espace…

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]


… Et dont la question est prétendument un [duplicata]

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() );
}

1

Si vous avez seulement besoin de détecter la présence de doublons (au lieu de les lister, ce que voulait l'OP), convertissez-les simplement en liste et ensemble, puis comparez les tailles:

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

J'aime cette approche car elle a moins de place pour les erreurs.


0

Je pense avoir une bonne solution pour résoudre un problème comme celui-ci - Liste => Liste avec regroupement par Something.a & Something.b. Il existe une définition étendue:

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);
    }

}

classe A, list1 ce ne sont que des données entrantes - la magie est dans Objects.hash (...) :)


1
Attention: If Objects.hashproduit la même valeur pour (v.a_1, v.b_1, v.c_1, v.d_1)et (v.a_2, v.b_2, v.c_2, v.d_2), alors ils vont être considérés comme égaux et supprimés en tant que doublons, sans réellement vérifier que les a, b, c et d sont les mêmes. Cela peut être un risque acceptable, ou vous pouvez utiliser une fonction autre que celle Objects.hashqui garantit un résultat unique sur votre domaine.
Marty Neal

0

Devez-vous utiliser les idiomes java 8 (steams)? Peut-être qu'une solution simple serait de déplacer la complexité vers une structure de données de carte qui contient les nombres comme clé (sans se répéter) et les heures auxquelles il se produit en tant que valeur. Vous pouvez les itérer sur cette carte et ne faire quelque chose qu'avec les nombres qui se produisent> 1.

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());
    }
  }
}

0

Essayez cette solution:

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;
}

}

0

Et la vérification des index?

        numbers.stream()
            .filter(integer -> numbers.indexOf(integer) != numbers.lastIndexOf(integer))
            .collect(Collectors.toSet())
            .forEach(System.out::println);

1
Devrait fonctionner correctement, mais aussi des performances O (n ^ 2) comme d'autres solutions ici.
Florian Albrecht
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.