Créer une liste distincte de la liste existante en Java 7 et 8?

Si j'ai:

List<Integer> listInts = { 1, 1, 3, 77, 2, 19, 77, 123, 14, 123... }

en Java, Quelle est la manière efficace de créer un <!-- 2-ne contenant que le distinct valeurs de listInts?

Ma pensée est de créer un Set<Integer> setInts contenant toutes les valeurs de listInts alors appelez List<Integer> listDistinctInts = new ArrayList<>(setInts);

mais cela semble potentiellement inefficace - Existe-t-il une meilleure solution en utilisant Java 7?

Je n'utilise pas Java 8, mais je crois que je pourrais faire quelque chose comme ce(?):

List<Integer> listDistinctInts = listInts.stream().distinct().collect(Collectors.toList());

cela Serait-il plus performant que l'approche ci-dessus et/ou est-il un moyen plus efficace de le faire en Java 8?

enfin, (et je suis conscient que poser plusieurs questions pourrait être mal vu mais c'est directement lié) si seulement je me souciais de la count des éléments distincts dans listInts existe - t-il un moyen plus efficace d'obtenir cette valeur (en Java 7 et 8) - sans d'abord créer une liste ou un ensemble de toutes les éléments?

je suis le plus intéressé par les méthodes Java natives pour accomplir ceci et éviter de réinventer n'importe quelles roues mais je considérerais le code ou les bibliothèques roulées à la main si elles offrent une meilleure clarté ou performance. J'ai lu cette question connexe Java - Liste Distincte des Objets mais il n'est pas tout à fait clair sur les différences de performance entre les approches Java 7 et 8 ou s'il pourrait y avoir de meilleures techniques?

18
demandé sur Community 2014-12-14 02:34:20

7 réponses

j'ai maintenant micro-comparé la plupart des options proposées à partir des excellentes réponses fournies. Comme la plupart des questions non triviales liées à la performance, la réponse à laquelle il convient de répondre est "ça dépend".

Tous mes tests ont été effectués avec JMH Java Microbenchmarking Harnais.

la plupart de ces tests ont été effectués en utilisant JDK 1.8, bien que j'ai effectué certains des tests avec JDK 1.7 trop juste pour s'assurer que son la performance n'était pas trop différente (elle était presque identique). J'ai testé les techniques suivantes tirées des réponses fournies jusqu'à présent:


1. Java 8 Stream - la solution en utilisant stream() j'ai eu proprosed comme une possibilité si l'aide de Java8:

public List<Integer> testJava8Stream(List<Integer> listInts) {
    return listInts.stream().distinct().collect(Collectors.toList());
}

prosmoderne Java 8 approche, pas de 3ème partie des dépendances

cons Nécessite Java 8


2. Ajouter À La Liste - La solution proposée par Victor2748 où une nouvelle liste est construite et ajoutée, si et seulement si la liste ne contient pas déjà la valeur. Notez que j'ai aussi préallouer la liste de destination à la taille de l'original (le max possible) pour éviter toute réaffectation:

public List<Integer> testAddingToList(List<Integer> listInts) {
    List<Integer> listDistinctInts = new ArrayList<>(listInts.size());
    for(Integer i : listInts)
    {
        if( !listDistinctInts.contains(i) ) { listDistinctInts.add(i); }
    }
    return listDistinctInts;
}

pros fonctionne dans N'importe quelle version de Java, pas besoin de créer un ensemble et puis la copie, pas de 3ème partie deps

consdoit vérifier à plusieurs reprises la liste des valeurs existantes pendant que nous la construisons


3. GS Collections Fast(Eclipse collections) - La solution proposée par Craig P. Motlincollection GS bibliothèque et leur type de liste personnalisé FastList:

public List<Integer> testGsCollectionsFast(FastList listFast)
{
    return listFast.distinct();
}

pros apparemment très rapide, code expressif simple, fonctionne en Java 7 et 8

cons nécessite une bibliothèque tierce partie et un FastList plutôt que List<Integer>


4. Collection GS adaptée - la solution FastList ne comparait pas tout à fait comme-pour-comme parce qu'elle avait besoin d'un FastList passé à la méthode plutôt que d'un bon ol' ArrayList<Integer> donc J'ai aussi testé la méthode de l'adaptateur Craig proposé:

public List<Integer> testGsCollectionsAdapted(List<Integer> listInts)
{
    return listAdapter.adapt(listInts).distinct();
}

prosNe nécessite pas un FastList, travaille dans Java 7 et 8

cons doit adapter la liste donc peut ne pas fonctionner aussi bien, a besoin de bibliothèque tierce partie


5. Goyave ImmutableSet - La méthode proposée par Louis Wasserman dans les commentaires, et par փ Shengyuan lu dans leur réponse en utilisant Goyave:

public List<Integer> testGuavaImmutable(List<Integer> listInts)
{
    return ImmutableSet.copyOf(listInts).asList();
}

pros apparemment très rapide, fonctionne en Java 7 ou 8

consrenvoie un Immutable List, ne peut pas gérer la valeur null dans la Liste d'entrée, et exige de la 3e partie de la bibliothèque


7. HashSet - mon idée originale (aussi recommandé par EverV0id, ulix et Radiodef)

public List<Integer> testHashSet(List<Integer> listInts)
{
    return new ArrayList<Integer>(new HashSet<Integer>(listInts));
}

prosFonctionne en Java 7 et 8, pas de 3ème partie des dépendances

cons ne conserve pas l'ordre original de la liste, doit construire set puis Copier vers list.


6. LinkedHashSet Depuis le HashSet la solution n'est pas de préserver l'ordre des nombres Entiers dans la liste d'origine j'ai aussi testé une version qui utilise LinkedHashSet à préserver ordre:

public List<Integer> testLinkedHashSet(List<Integer> listInts)
{
    return new ArrayList<Integer>(new LinkedHashSet<Integer>(listInts));
}

pros conserve la commande originale, fonctionne en Java 7 et 8, aucune dépendance de tierce partie

consRare d'être aussi rapide que régulier HashSet approche


Résultats

Voici mes résultats pour différentes tailles de listInts (résultats ordonnés du plus lent au plus rapide):

1. prendre distincte de la liste de tableaux de 100 000 aléatoires entiers entre 0 à 50,000 (ie. grande liste, quelques doublons)

Benchmark                Mode       Samples     Mean   Mean error    Units

AddingToList            thrpt        10        0.505        0.012    ops/s
Java8Stream             thrpt        10      234.932       31.959    ops/s
LinkedHashSet           thrpt        10      262.185       16.679    ops/s
HashSet                 thrpt        10      264.295       24.154    ops/s
GsCollectionsAdapted    thrpt        10      357.998       18.468    ops/s
GsCollectionsFast       thrpt        10      363.443       40.089    ops/s
GuavaImmutable          thrpt        10      469.423       26.056    ops/s

2. en prenant distinct de ArrayList de 1000 ints aléatoires entre 0-50 (c.-à-d. moyen de la liste, de nombreux doublons)

Benchmark                Mode       Samples     Mean   Mean error    Units

AddingToList            thrpt        10    32794.698     1154.113    ops/s
HashSet                 thrpt        10    61622.073     2752.557    ops/s
LinkedHashSet           thrpt        10    67155.865     1690.119    ops/s
Java8Stream             thrpt        10    87440.902    13517.925    ops/s
GsCollectionsFast       thrpt        10   103490.738    35302.201    ops/s
GsCollectionsAdapted    thrpt        10   143135.973     4733.601    ops/s
GuavaImmutable          thrpt        10   186301.330    13421.850    ops/s

3. prendre distinct de ArrayList de 100 ints aléatoires entre 0-100 (c.-à-d. petite liste, quelques doublons)

Benchmark                Mode       Samples     Mean   Mean error    Units

AddingToList            thrpt        10   278435.085    14229.285    ops/s
Java8Stream             thrpt        10   397664.052    24282.858    ops/s
LinkedHashSet           thrpt        10   462701.618    20098.435    ops/s
GsCollectionsAdapted    thrpt        10   477097.125    15212.580    ops/s
GsCollectionsFast       thrpt        10   511248.923    48155.211    ops/s
HashSet                 thrpt        10   512003.713    25886.696    ops/s
GuavaImmutable          thrpt        10  1082006.560    18716.012    ops/s

4. prise distincte de L'ArrayList de 10 ints aléatoires entre 0-50 (c.-à-d. minuscule liste, quelques doublons)

Benchmark                Mode       Samples     Mean   Mean error    Units

Java8Stream             thrpt        10  2739774.758   306124.297    ops/s
LinkedHashSet           thrpt        10  3607479.332   150331.918    ops/s
HashSet                 thrpt        10  4238393.657   185624.358    ops/s
GsCollectionsAdapted    thrpt        10  5919254.755   495444.800    ops/s
GsCollectionsFast       thrpt        10  7916079.963  1708778.450    ops/s
AddingToList            thrpt        10  7931479.667   966331.036    ops/s
GuavaImmutable          thrpt        10  9021621.880   845936.861    ops/s

Conclusions

  • si vous prenez seulement les éléments distincts d'une liste une fois, et la liste n'est pas très longue de ces méthodes devrait être suffisant.

  • les approches générales les plus efficaces proviennent des bibliothèques de tiers: collections GS et goyaves effectuées admirablement.

  • vous devrez peut-être tenir compte de la taille de votre liste et du nombre probable de doublons lors de la sélection de la méthode la plus performante.

  • l'approche naïve d'ajouter à une nouvelle liste seulement si la valeur n'y est pas déjà fonctionne très bien pour les listes minuscules, mais dès que vous avez plus d'une poignée de valeurs dans la liste d'entrée elle exécute le pire des méthodes essayées.

  • La Goyave ImmutableSet.copyOf(listInts).asList() la méthode fonctionne le plus rapidement dans la plupart des situations. Mais prendre note des restrictions: la liste retournée est Immutable et la liste des entrées ne peut pas contenir nulls.

  • HashSet method exécute le meilleur des approches non tierces et généralement mieux que les flux Java 8, mais réordonne les entiers (qui peuvent ou non être un problème selon votre cas d'utilisation).

  • LinkedHashSet l'approche maintient l'ordre mais sans surprise était généralement pire que la méthode du HashSet.

  • les Deux HashSet et LinkedHashSet les méthodes fonctionneront plus mal lorsque vous utilisez des listes de types de données qui ont des calculs complexes de HashCode, alors faites votre propre profilage si vous essayez de choisir distinct Foos de List<Foo>.

  • Si vous avez déjà collections GS en tant que dépendance, il fonctionne très bien et est plus flexible que L'ImmutableList Goyave approche. Si vous ne l'avez pas comme une dépendance, il vaut la peine d'envisager de l'ajouter si la performance de sélectionner des éléments distincts est critique pour la performance de votre application.

  • de manière décevante, Java 8 streams semble donner des résultats assez médiocres. Il y a peut-être une meilleure façon de coder le distinct() appeler que la façon dont j'ai utilisé, de sorte que les commentaires ou d'autres réponses sont bien sûr les bienvenus.

NB. Je ne suis pas expert chez MicroBenchmarking, donc si quelqu'un trouve des défauts dans mes résultats ou la méthodologie s'il Vous Plaît me prévenir et je vais essayer de corriger la réponse.

27
répondu Matt Coubrough 2017-05-23 11:53:53

Si vous utilisez Collections Eclipse (anciennement collections GS), vous pouvez utiliser la méthode distinct().

ListIterable<Integer> listInts = FastList.newListWith(1, 1, 3, 77, 2, 19, 77, 123, 14, 123);
Assert.assertEquals(
        FastList.newListWith(1, 3, 77, 2, 19, 123, 14),
        listInts.distinct());

l'avantage d'utiliser distinct() au lieu de convertir en un ensemble puis de revenir à une liste est que distinct() préserve l'ordre de la liste originale, en conservant la première occurrence de chaque élément. Il est mis en œuvre en utilisant à la fois un ensemble et une liste.

MutableSet<T> seenSoFar = UnifiedSet.newSet();
int size = list.size();
for (int i = 0; i < size; i++)
{
    T item = list.get(i);
    if (seenSoFar.add(item))
    {
        targetCollection.add(item);
    }
}
return targetCollection;

si vous ne pouvez pas convertir votre liste originale en GS Type de Collections, vous pouvez utiliser ListAdapter pour obtenir la même API.

MutableList<Integer> distinct = ListAdapter.adapt(integers).distinct();

Il n'y a aucun moyen d'éviter la création de l'Ensemble. Néanmoins, UnifiedSet est plus efficace que HashSet, il y aura donc un avantage de vitesse.

Si tout ce que vous voulez, c'est le d'éléments distincts, il est plus efficace de simplement créer un ensemble sans créer la liste.

Verify.assertSize(7, UnifiedSet.newSet(listInts));

Eclipse Collections 8.0 nécessite Java 8. Collections Eclipse 7.X fonctionne bien avec Java 8, mais ne nécessite que Java 5.

Remarque: je suis un committer pour Eclipse collections.

3
répondu Craig P. Motlin 2016-09-15 08:12:11

Vous devriez essayer new LinkedList(new HashSet(listInts)).

1
répondu Everv0id 2014-12-13 23:43:38

Goyave peut être votre choix:

ImmutableSet<Integer> set = ImmutableSet.copyOf(listInts);

L'API est extrêmement optimisée.

C'est plus rapide que listInts.stream().distinct() et new LinkedHashSet<>(listInts).

1
répondu 卢声远 Shengyuan Lu 2014-12-15 04:52:23

en ajoutant une valeur à un listInts cocher:

int valueToAdd;
//...
if (!listInts.contains(valueToAdd)) {listInts.add(valueToAdd)}

si vous avez une liste existante, utiliser un pour chaque déclaration pour copier toutes les valeurs de cette liste, à nouveau, que vous voulez être "distinctes":

List<Integer> listWithRepeatedValues;
List<Integer> distinctList;
//...
for (Integer i : listWithRepeatedValues) {
    if (!listInts.contains(valueToAdd)) {distinctList.add(i);}
}
0
répondu Victor2748 2014-12-13 23:59:49

Ne vous inquiétez pas. L'utilisation d'un HashSet est un moyen simple et efficace pour éliminer les doublons:

    Set<Integer> uniqueList = new HashSet<>();
    uniqueList.addAll(listInts);   // Add all elements eliminating duplicates

    for (int n : uniqueList)       // Check the results (in no particular order)
        System.out.println(n);

    System.out.println("Number distinct values: " + uniqueList.size());

dans un scénario plus spécifique, juste au cas où l'intervalle des valeurs possibles est connu, n'est pas très grand, alors que listInts est très grand.

La façon la plus efficace de compter le nombre d'entrées uniques dans la liste que je peux penser est:

    boolean[] counterTable = new boolean[124];
    int counter = 0;

    for (int n : listInts)
        if (!counterTable[n]) {
            counter++;
            counterTable[n] = true;
        }

    System.out.println("Number of distinct values: " + counter);
0
répondu ulix 2014-12-15 02:47:00

Cela devrait fonctionner:

yourlist.flux.)(map (votre wrapper qui remplace la méthode equals et hashchode::new).distinct.)(carte(wrapper définis ci-dessus::méthode qui retourne le résultat final).recueillir des(Collectionneurs.toList ());

0
répondu Righto 2017-07-31 10:35:28