2010-05-07 6 views
5

J'ai une liste d'objets dire, Liste. La classe Entity a une méthode equals, sur quelques attributs (règle métier) pour différencier un objet Entity de l'autre.Meilleur pour la liste structure de données d'objets fréquemment sollicités

La tâche que nous portons habituellement sur cette liste est de supprimer tous les doublons quelque chose comme ceci:

List<Entity> noDuplicates = new ArrayList<Entity>(); 
for(Entity entity: lstEntities) 
{ 
    int indexOf = noDuplicates.indexOf(entity); 
    if(indexOf >= 0) 
    { 
      noDuplicates.get(indexOf).merge(entity); 
    } 
    else 
    { 
      noDuplicates.add(entity); 
    } 
} 

Maintenant, le problème que j'ai observé est que cette partie du code, ralentit vers le bas considérablement dès que la liste a des objets plus de 10000.Je comprends arraylist fait une recherche (N).

Y at-il une alternative plus rapide, utiliser HashMap n'est pas une option, parce que l'unicité de l'entité est construite sur 4 de ses attributs ensemble, il serait fastidieux de mettre la clé elle-même dans la carte? va trier définir l'aide dans l'interrogation plus rapide?

Merci

+0

Mis à jour ma réponse, j'espère que cela vous sera utile. –

+0

Autre remarque mineure: Si votre 'lstEntities' est normalement très grand, vous devriez envisager de faire' new ArrayList (int) 'avec une estimation raisonnable de la taille de la liste. Cela empêchera votre 'ArrayList' d'avoir à réaffecter la mémoire tout le temps. Je crois que 'new ArrayList()' par défaut n'est que de 32 éléments de taille, donc il y aura beaucoup de redimensionnement et de copie si votre liste 'noDuplicates' devient grosse. –

Répondre

2

Maintenant, le problème que j'ai observé est que cette partie du code, ralentit considérablement dès que la liste a des objets plus 10000.I comprendre arraylist fait une recherche o (N).

L'algorithme que vous avez publié est en fait pire que O (N)

  • Itère la liste d'entrée lstEntities - O (N)
  • au sein de cette boucle, vous appelez ArrayList.indexOf(T) qui doit scanner la liste - O (N) à nouveau

vous algorithme est en fait O (N^2) puisque vous êtes potentiellement numérisez la liste deux fois dans une boucle.

On dirait que vous ce que vous voulez faire est en fait deux opérations:

  1. De l'entrée List, supprimer les doublons
  2. Lorsque vous trouvez les doublons, les entités « fusion ».

Vous pouvez le faire en analysant la liste une seule fois, plutôt que dans des boucles imbriquées. Je recommande de diviser votre Entity pour déplacer les champs qui "identifient" une entité dans un autre type, comme ID, ou tout au moins d'ajouter une méthode getID() qui peut retourner ces champs groupés dans un seul type. De cette façon, vous pouvez facilement créer une carte entre les deux types pour pouvoir fusionner des entités avec des identités «en double». Cela pourrait ressembler à ceci:

Map<ID, Entity> map = new HashMap<ID, Entity>(inputList.size()); 
for (Entity e : inputList) { 
    Entity existing = map.get(e.getID()); 
    if (existing == null) { 
     //not in map, add it 
     map.put(e.getID(), e); 
    } 
    else { 
     existing.merge(e); 
    } 
} 

la liste Itération est O (n) tandis que HashMap.get(K) est une opération constante de temps.

+1

N'est-ce pas essentiellement l'option que l'affiche exclut avec sa déclaration "utiliser HashMap n'est pas une option, parce que l'unicité de l'entité est construite sur 4 de ses attributs ensemble, il serait fastidieux de mettre la clé dans la carte" ? Je pense que cette déclaration est ridicule, mais puisque c'est dans la question, elle devrait être réfutée explicitement si vous allez à l'encontre. –

+0

D'accord avec @Daniel. Gardez aussi à l'esprit que 'HashMap.get()' est seulement 'O (1)' si vous avez une bonne fonction de hachage. Avec potentiellement 1000s d'objets Entity qui pourraient être difficiles puisque @panzerschreck devra écrire sa propre méthode hashCode. –

+1

@Daniel, bon point, j'ai raté ça. Ok voici ma réfutation: 1) il est trivial d'écrire un type 'EntityID' qui contient ces quatre attributs et implémente correctement equals() et hashcode() (utiliser commons-lang pour plus de simplicité) 2) il est trivial d'ajouter un getID() méthode 'Entity' qui construit une nouvelle instance' EntityID' pour les quatre attributs qui forment l '"identité" 3) la quantité de travail dans # 1 et # 2 (une classe, trois méthodes) vaut la quantité de calcul vous économiserez en transformant un algorithme O (N^2) en O (N). –

2

Une idée est d'utiliser un Set au lieu d'un List, il n'y a pas de doublons dans un Set. Pour supprimer les doublons dans une liste, vous pouvez ajouter simplement le List à une nouvelle Set

List<Entity> list = //your list. 
Set<Entity> set = new HashSet<Entitiy>(); 
set.addAll(list); 

Mais là encore, peut-être il y a une raison pour utiliser un List en premier lieu? Sinon, vous pouvez utiliser un Set à la place, et ne pas avoir à vous soucier des doublons.

EDIT

Il n'y a pas de référence d'index des éléments dans un Set (par rapport à un List, où vous pouvez faire get(int index)). Les éléments d'un Set flottent sans point de référence spécifique.

Si vous avez besoin de trouver un particulier, vous devez itérer tous. Si ce n'est pas correct et/ou vous ne pouvez pas être sans référence indexé - qui permet de get(int index) et remove(int index) - Je suppose que Set est pas une option pour vous.

+0

En utilisant un ensemble, ne sera pas utile lors de l'insertion à droite, si j'essaie d'ajouter un doublon, il ne me le permettra pas, alors j'ai besoin d'interroger cet objet en utilisant contains() et get() probablement. C'est ce que vous vouliez dire? si oui, quelle est la rapidité de get() sur le plateau? – panzerschreck

+0

Il n'y a pas get() sur un ensemble. Il y a ajouter (Object o) et remove (Object o). Si vous essayez d'ajouter un doublon à l'ensemble, ajouter (Object o) retournera false. –

+0

Alors ça ne marchera pas vraiment pour le code affiché, n'est-ce pas? Il doit faire cette opération 'merge', et cela ne le laissera pas. –

3

Au lieu d'une structure de liste, vous pouvez utiliser un ensemble (plus approprié si vous êtes préoccupé par le caractère unique entité), comme Lars a suggéré. De plus, si la performance est un problème, je regarderais en utilisant un TreeSet et implémenterais un Comparator pour comparer des instances d'entité basées sur leurs attributs. La structure arborescente permettra des opérations d'insertion, de suppression et de récupération rapides (complexité logarithmique).

+1

Si vous pensez qu'une structure de carte avec un hachage n'est pas faisable, alors c'est probablement la meilleure réponse. Votre appel actuel à 'noDuplicates.indexOf (entity)' aura les performances les plus défavorables de 'O (N)' alors qu'un appel 'TreeSet.contains()' peut vous garantir 'O (log (N))' performance. Avec un peu d'effort sur 'Comparator', vous pouvez aussi utiliser votre méthode' Entity.equals' existante. (@rati: c'est à peu près ce que vous avez dit ... juste ajouter plus de détails) –

1

Tout dépend de ce que le fonctionnement merge fait. Est-ce que merge change l'un des attributs qui sont comparés lorsque vous faites equals? Sinon, vous serez étonné de voir combien plus vite il sera si vous faites ceci:

D'abord, définir un hashCode pour votre classe Entity qui est compatible avec votre définition de equals.Une façon courante de le faire est:

public int hashCode() { 
    // assuming the four attributes that determine equality are called 
    // attrFoo, attrBar, attrBaz, and attrQux 
    int hash = 1; 
    hash += attrFoo == null ? 0 : attrFoo.hashCode(); 
    hash *= 37; 
    hash += attrBar == null ? 0 : attrBar.hashCode(); 
    hash *= 37; 
    hash += attrBaz == null ? 0 : attrBaz.hashCode(); 
    hash *= 37; 
    hash += attrQux == null ? 0 : attrQux.hashCode(); 

    return hash; 
} 

Ensuite, utilisez un HashMap afin que vous puissiez trouver ces choses:

Map<Entity, Entity> map = new HashMap<Entity, Entity>(); 
for(Entity entity: lstEntities) { 
    if (map.containsKey(entity)) { 
    map.get(entity).merge(entity); 
    } else { 
    map.put(entity, entity); 
    } 
} 
return map.values(); // or keys(). Whichever. 

Je dois souligner que je me sens un peu sale écrire le code ci-dessus, parce que vous ne devriez vraiment pas faire des clés qui ne sont pas immuables, mais cela fonctionnera beaucoup plus vite que ce que vous faites maintenant.

+0

cela causera en effet des problèmes si les champs utilisés dans 'Entity.hashCode()' sont affectés par l'opération 'merge' –

+0

Vous pourriez envisager d'utiliser un HashSet au lieu d'un HashMap. Il va automatiquement filtrer les doublons pour vous, donc vous pouvez passer la vérification '" if (map.containsKey (entity)) "'. Code plus propre et même complexité algorithmique. –

+1

@Brent Nash: mais cela ne le laissera jamais appeler 'fusionner 'sur l'entité qui est stockée dans la structure. Il doit le faire (apparemment). –

0

À moins que vous n'ayez une raison d'avoir besoin de la commande d'une liste, vous seriez probablement mieux avec un ensemble - spécifiquement, un HashSet.

Je vois votre souci d'utiliser une collection hachée parce que "l'unicité de l'entité est construite sur 4 de ses attributs ensemble", mais cela est facilement surmonté. Il vous suffit de définir une méthode hashcode() compatible avec votre méthode equals() existante, puis vous pouvez insérer vos entités dans un Set et, comme effet secondaire magique, ne plus jamais avoir à supprimer les doublons.

0

Deux étapes simples pour un O (N * Log (N)) algorithme:

  1. Trier la liste en utilisant un comparateur basé sur les quatre domaines importants
  2. itérer sur la liste la comparaison de chaque élément à la Dans la liste suivante, s'ils sont égaux, fusionnez-les et supprimez-en un.
Questions connexes