Quelle est la façon la plus efficace de trouver la taille de l'intersection de deux ensembles non clairsemés en Java? C'est une opération que je vais appeler sur de grands ensembles un très grand nombre de fois, donc l'optimisation est importante. Je ne peux pas modifier les jeux originaux.
J'ai regardé Apache Commons CollectionUtils.intersection qui semble être assez lent. Mon approche actuelle consiste à prendre le plus petit des deux ensembles, à le cloner, puis à appeler .retainAll sur le plus grand des deux ensembles.
public static int getIntersection(Set<Long> set1, Set<Long> set2) {
boolean set1IsLarger = set1.size() > set2.size();
Set<Long> cloneSet = new HashSet<Long>(set1IsLarger ? set2 : set1);
cloneSet.retainAll(set1IsLarger ? set1 : set2);
return cloneSet.size();
}
Utilisez simplement la méthode Google GuavaSets#intersection(Set, Set)
.
Vous pouvez éviter tout travail manuel en utilisant la méthode Set retenueAll ().
Depuis les documents:
s1.retainAll (s2) - transforme s1 en l'intersection de s1 et s2. (L'intersection de deux ensembles est l'ensemble contenant uniquement les éléments communs aux deux ensembles.)
Les membres des ensembles peuvent-ils être facilement mappés dans une gamme relativement petite d'entiers? Si tel est le cas, envisagez d'utiliser des BitSets. L'intersection est alors juste au niveau du bit et - 32 membres potentiels à la fois.
Si les deux ensembles peuvent être triés, comme TreeSet
exécuter les deux itérateurs pourrait être un moyen plus rapide de compter le nombre d'objets partagés.
Si vous effectuez cette opération souvent, cela peut apporter beaucoup si vous pouvez envelopper les ensembles afin que vous puissiez mettre en cache le résultat de l'opération d'intersection en gardant un indicateur dirty
pour suivre la validité du résultat mis en cache, calculant à nouveau si nécessaire .
Utilisation de Java 8 stream:
set1.stream().filter(s -> set2.contains(s)).collect(Collectors.toList());
Si vous calculez l'intersection juste pour le compte du nombre d'éléments dans l'ensemble, je suggère que vous ayez juste besoin de compter l'intersection directement au lieu de construire l'ensemble puis d'appeler size()
.
Ma fonction de comptage:
/**
* Computes the size of intersection of two sets
* @param small first set. preferably smaller than the second argument
* @param large second set;
* @param <T> the type
* @return size of intersection of sets
*/
public <T> int countIntersection(Set<T> small, Set<T> large){
//assuming first argument to be smaller than the later;
//however double checking to be sure
if (small.size() > large.size()) {
//swap the references;
Set<T> tmp = small;
small = large;
large = tmp;
}
int result = 0;
for (T item : small) {
if (large.contains(item)){
//item found in both the sets
result++;
}
}
return result;
}
C’est une bonne approche. Vous devriez obtenir des performances O(n) de votre solution actuelle.
Pour info, si une collection d'ensembles sont tous triés en utilisant la même relation de comparaison, alors vous pouvez itérer leur intersection dans le temps N * M, où N est la taille de l'ensemble le plus petit et M est le nombre d'ensembles.
Mise en œuvre laissée au lecteur comme exercice. Voici un exemple .
Comptage d'intersections à travers les flux/réduire (il suppose que vous déterminez quel ensemble est plus grand avant de l'appeler):
public int countIntersect(Set<Integer> largerSet, Set<Integer> smallerSet){
return smallerSet.stream().reduce(0, (a,b) -> largerSet.contains(b)?a+1:a);
}
Cependant, ailleurs, j'ai lu qu'aucun code Java Java ne peut être plus rapide que les méthodes Set pour les opérations set car elles sont implémentées en tant que code natif au lieu de Java code. Par conséquent, je sauvegarde la suggestion d'essayer BitSet pour obtenir des résultats plus rapides.