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?

Siva
la source
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);
Bao Dinh
la source
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() );
RobAu
la source
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]
Dave
la source
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.

Thomas Mathew
la source
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).

Tagir Valeev
la source
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());
Oussama Zoghlami
la source
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.

Prashant
la source
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());
numéro6
la source
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() );
}
Kaplan
la source
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.

Patrick
la source
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 (...) :)

Zhurov Konstantin
la source
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());
    }
  }
}
Victor
la source
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;
}

}
Ilia Galperin
la source
0

Et la vérification des index?

        numbers.stream()
            .filter(integer -> numbers.indexOf(integer) != numbers.lastIndexOf(integer))
            .collect(Collectors.toSet())
            .forEach(System.out::println);
bagom
la source
1
Devrait fonctionner correctement, mais aussi des performances O (n ^ 2) comme d'autres solutions ici.
Florian Albrecht