web-dev-qa-db-fra.com

Quel est le moyen le plus rapide de comparer deux ensembles en Java?

J'essaie d'optimiser un morceau de code qui compare des éléments de liste.

Par exemple.

public void compare(Set<Record> firstSet, Set<Record> secondSet){
    for(Record firstRecord : firstSet){
        for(Record secondRecord : secondSet){
            // comparing logic
        }
    }
}

S'il vous plaît prendre en compte que le nombre d'enregistrements dans les ensembles sera élevé.

Merci

Shekhar

87
Shekhar
firstSet.equals(secondSet)

Cela dépend vraiment de ce que vous voulez faire dans la logique de comparaison ... que se passe-t-il si vous trouvez un élément dans un ensemble pas dans l'autre? Votre méthode a un type de retour void donc je suppose que vous ferez le travail nécessaire avec cette méthode.

Un contrôle plus fin si vous en avez besoin:

if (!firstSet.containsAll(secondSet)) {
  // do something if needs be
}
if (!secondSet.containsAll(firstSet)) {
  // do something if needs be
}

Si vous avez besoin d’obtenir les éléments d’un ensemble et non de l’autre.
EDIT: set.removeAll(otherSet) renvoie un booléen, pas un ensemble. Pour utiliser removeAll (), vous devez copier le jeu puis l’utiliser.

Set one = new HashSet<>(firstSet);
Set two = new HashSet<>(secondSet);
one.removeAll(secondSet);
two.removeAll(firstSet);

Si le contenu de one et two est vide, vous savez que les deux ensembles sont égaux. Sinon, vous avez les éléments qui ont rendu les ensembles inégaux.

Vous avez mentionné que le nombre d'enregistrements pourrait être élevé. Si l'implémentation sous-jacente est un HashSet, alors l'extraction de chaque enregistrement est effectuée dans le temps O(1), de sorte que vous ne pouvez pas vraiment obtenir mieux que cela. TreeSet est O(log n).

141
Noel M

Si vous voulez simplement savoir si les ensembles sont égaux, la méthode equals sur AbstractSet est implémentée comme suit:

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection c = (Collection) o;
        if (c.size() != size())
            return false;
        return containsAll(c);
    }

Notez comment cela optimise les cas courants où:

  • les deux objets sont les mêmes
  • l'autre objet n'est pas du tout un ensemble, et
  • la taille des deux ensembles est différente.

Après cela, containsAll(...) renverra false dès qu'il trouvera un élément dans l'autre ensemble qui ne figure pas également dans cet ensemble. Mais si tous les éléments sont présents dans les deux ensembles, il faudra les tester tous.

La pire performance se produit donc lorsque les deux ensembles sont égaux mais pas les mêmes objets. Ce coût est généralement O(N) ou O(NlogN), selon l'implémentation de this.containsAll(c).

Et vous obtenez une performance proche de la pire des cas si les ensembles sont volumineux et ne diffèrent que par un pourcentage infime des éléments.


MISE À JOUR

Si vous êtes prêt à investir du temps dans la mise en œuvre d'un ensemble personnalisé, il existe une approche qui peut améliorer le cas "presque identique".

L'idée est que vous devez pré-calculer et mettre en cache un hachage pour l'ensemble afin de pouvoir obtenir la valeur de hashcode actuelle de l'ensemble dans O(1). Ensuite, vous pouvez comparer le hashcode des deux ensembles en tant qu’accélération.

Comment pouvez-vous implémenter un hashcode comme ça? Eh bien, si le hashcode défini était:

  • zéro pour un ensemble vide, et
  • le XOR de tous les codes de hachage d'élément pour un ensemble non vide,

vous pouvez alors mettre à jour, à moindre coût, le hashcode mis en cache de l'ensemble à chaque fois que vous ajoutez ou supprimez un élément. Dans les deux cas, vous avez simplement XOR le hashcode de l'élément avec le hashcode actuellement défini.

Bien entendu, cela suppose que les codes de hachage d'élément sont stables alors que les éléments sont membres d'ensembles. Il suppose également que la fonction hashcode des classes d'éléments donne un bon écart. En effet, lorsque les deux codes de hachage sont identiques, vous devez toujours revenir à la comparaison O(N) de tous les éléments.


Vous pourriez pousser cette idée un peu plus loin… du moins en théorie.

Supposons que votre classe d'élément set dispose d'une méthode pour renvoyer une somme de contrôle cryptographique pour l'élément. Maintenant, implémentez les sommes de contrôle de l'ensemble en XORing les sommes de contrôle retournées pour les éléments.

Qu'est-ce que cela nous achète?

Eh bien, si nous supposons qu'il ne se passe rien, la probabilité que deux éléments de jeu inégaux aient la même somme de contrôle à N bits est de 2-N. Et la probabilité 2 ensembles inégaux ont les mêmes sommes de contrôle à N bits est également 2-N. Donc, mon idée est que vous pouvez implémenter equals en tant que:

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection c = (Collection) o;
        if (c.size() != size())
            return false;
        return checksums.equals(c.checksums);
    }

Selon les hypothèses ci-dessus, cela ne vous donnera la mauvaise réponse qu'une fois sur deux.-N temps. Si vous donnez à N une taille suffisante (par exemple 512 bits), la probabilité d’une réponse fausse devient négligeable (par exemple environ 10-150).

L'inconvénient est que le calcul de la somme de contrôle cryptographique pour les éléments est très coûteux, en particulier à mesure que le nombre de bits augmente. Vous avez donc vraiment besoin d’un mécanisme efficace pour mémoriser les sommes de contrôle. Et cela pourrait être problématique.

58
Stephen C

Il existe une méthode dans Guava Sets qui peut aider ici:

public static <E>  boolean equals(Set<? extends E> set1, Set<? extends E> set2){
return Sets.symmetricDifference(set1,set2).isEmpty();
}
15
husayt

Il existe une solution O(N) pour des cas très spécifiques où:

  • les ensembles sont tous deux triés
  • tous deux triés dans le même ordre

Le code suivant suppose que les deux ensembles sont basés sur des enregistrements comparables. Une méthode similaire pourrait être basée sur un comparateur.

    public class SortedSetComparitor <Foo extends Comparable<Foo>> 
            implements Comparator<SortedSet<Foo>> {

        @Override
        public int compare( SortedSet<Foo> arg0, SortedSet<Foo> arg1 ) {
            Iterator<Foo> otherRecords = arg1.iterator();
            for (Foo thisRecord : arg0) {
                // Shorter sets sort first.
                if (!otherRecords.hasNext()) return 1;
                int comparison = thisRecord.compareTo(otherRecords.next());
                if (comparison != 0) return comparison;
            }
            // Shorter sets sort first
            if (otherRecords.hasNext()) return -1;
            else return 0;
        }
    }
4
Philip Couling

Vous avez la solution suivante de https://www.mkyong.com/Java/java-how-to-compare-two-sets/

public static boolean equals(Set<?> set1, Set<?> set2){

    if(set1 == null || set2 ==null){
        return false;
    }

    if(set1.size() != set2.size()){
        return false;
    }

    return set1.containsAll(set2);
}

Ou si vous préférez utiliser une seule déclaration return:

public static boolean equals(Set<?> set1, Set<?> set2){

  return set1 != null 
    && set2 != null 
    && set1.size() == set2.size() 
    && set1.containsAll(set2);
}
4
ilopezluna

Si vous utilisez la bibliothèque Guava, il est possible de faire:

        SetView<Record> added = Sets.difference(secondSet, firstSet);
        SetView<Record> removed = Sets.difference(firstSet, secondSet);

Et puis tirer une conclusion à partir de ceux-ci.

3
riwnodennyk

Je mettrais le secondSet dans un HashMap avant la comparaison. De cette façon, vous réduirez le temps de recherche de la deuxième liste à n (1). Comme ça:

HashMap<Integer,Record> hm = new HashMap<Integer,Record>(secondSet.size());
int i = 0;
for(Record secondRecord : secondSet){
    hm.put(i,secondRecord);
    i++;
}
for(Record firstRecord : firstSet){
    for(int i=0; i<secondSet.size(); i++){
    //use hm for comparison
    }
}
2
Sahin Habesoglu
public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;

        Set<String> a = this;
        Set<String> b = o;
        Set<String> thedifference_a_b = new HashSet<String>(a);


        thedifference_a_b.removeAll(b);
        if(thedifference_a_b.isEmpty() == false) return false;

        Set<String> thedifference_b_a = new HashSet<String>(b);
        thedifference_b_a.removeAll(a);

        if(thedifference_b_a.isEmpty() == false) return false;

        return true;
    }
1
Zahran