2010-05-07 15 views
5

Tengo una lista de objetos, por ejemplo, Lista. La clase Entity tiene un método igual, en pocos atributos (regla comercial) para diferenciar un objeto Entity del otro.La mejor estructura de datos para la lista de objetos que se consulta con frecuencia

La tarea que suelen llevar a cabo en esta lista es eliminar todas las copias o menos así:

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); 
    } 
} 

Ahora, el problema que he estado observando es que esta parte del código, se está desacelerando desciende considerablemente tan pronto como la lista tenga objetos de más de 10000. Entiendo que el arraylista está haciendo una búsqueda de ao (N).

¿Existe una alternativa más rápida, el uso de HashMap no es una opción, porque la singularidad de la entidad se basa en 4 de sus atributos en conjunto, sería tedioso introducir la clave en el mapa? ordenará set help en consultas más rápidas?

Gracias

+0

He actualizado mi respuesta, espero que sea de ayuda para usted. –

+0

Otra nota menor: si su 'lstEntities' es normalmente muy grande, debería considerar hacer' new ArrayList (int) 'con una conjetura razonablemente grande sobre qué tan grande será la lista. Esto evitará que su 'ArrayList' tenga que reasignar la memoria todo el tiempo. Creo que 'new ArrayList()' tiene un valor predeterminado de 32 elementos, por lo que se MUCHOS ajustes y copias si su lista 'noDuplicates' aumenta. –

Respuesta

2

Ahora, el problema que he estado observando es que esta parte del código, se ralentiza considerablemente tan pronto como la lista tiene objetos de más de 10000. Entiendo que el arraylist está haciendo una búsqueda de o (N).

El algoritmo informados es en realidad peor que O (N)

  • iteración a través de la lista de entrada lstEntities - O (N)
  • dentro de este bucle, que está llamando ArrayList.indexOf(T) que tiene para escanear la lista - O (N) otra vez

Su algoritmo es realmente O (N^2) ya que potencialmente puede escanear la lista dos veces dentro de un bucle.

Suena como si lo que se quiere hacer es en realidad dos operaciones:

  1. Desde la entrada List, eliminar los duplicados
  2. Cuando encuentre duplicados, "fusionar" las entidades.

Puede hacerlo escaneando la lista una sola vez, en lugar de hacerlo en bucles anidados. Recomendaría dividir su Entity para mover los campos que "identifican" una Entidad a otro tipo, como ID, o al menos agregar un método getID() que puede devolver estos campos agrupados en un solo tipo. De esta forma, puede construir fácilmente un Mapa entre los dos tipos para poder fusionar entidades con identidades "duplicadas". Esto podría ser algo como esto:

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); 
    } 
} 

Iterar a través de la lista es O (n), mientras que HashMap.get(K) es una operación de tiempo constante.

+1

¿No es esta la opción que el cartel descartó con su afirmación "usar HashMap no es una opción, porque la singularidad de la entidad se basa en 4 de sus atributos en conjunto, sería tedioso poner la clave en el mapa" ? Creo que esa afirmación es ridícula, pero como está en la pregunta, debería refutarse explícitamente si vas a ir en contra de ella. –

+0

De acuerdo con @Daniel. También tenga en cuenta que 'HashMap.get()' es solo 'O (1)' si tiene una buena función hash. Con potencialmente miles de objetos Entity que podrían ser difíciles ya que @panzerschreck tendrá que escribir su propio método hashCode. –

+1

@Daniel, buen punto, me lo perdí. Ok aquí está mi refutación: 1) es trivial escribir un tipo 'EntityID' que contiene esos cuatro atributos e implementa equals() y hashcode() (usa commons-lang para simplificar) 2) es trivial agregar un getID() método para 'Entity' que construye una nueva instancia' EntityID' para los cuatro atributos que forman la "identidad" 3) la cantidad de trabajo en # 1 y # 2 (una clase, tres métodos) vale la cantidad de cálculo Ahorrará al convertir un algoritmo O (N^2) en O (N). –

2

Una idea es utilizar un Set en lugar de un List, no hay duplicados en un Set. Para eliminar los duplicados de una lista, usted podría agregar el List a un nuevo Set

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

Pero, de nuevo, tal vez hay alguna razón para utilizar un List en el primer lugar? De lo contrario, podría usar un Set y no tener que preocuparse por ningún duplicado.

EDITAR

No hay ninguna referencia índice de los elementos en un Set (en comparación con un List, donde se puede hacer get(int index)). Los elementos en un Set están flotando sin un punto de referencia específico.

Si necesita encontrar una específica, necesita recorrer todas ellas. Si eso no está bien y/o no puede estar sin la referencia indexada, que permite get(int index) y remove(int index), supongo que Set no es una opción para usted.

+0

Usar un conjunto no ayudará durante la inserción, si trato de agregar un duplicado, no me lo permitirá, entonces necesito consultar ese objeto usando contains() y get() probablemente. Es eso lo que querías decir ? En caso afirmativo, ¿qué tan rápido es get() en el set? – panzerschreck

+0

No hay get() en un conjunto. Hay add (Object o) y remove (Object o). Si intenta agregar un duplicado al Conjunto, agregue (Objeto o) devolverá falso. –

+0

Entonces realmente no funcionará para el código publicado, ¿o sí? Él tiene que hacer esa operación 'merge', y esto no lo permitirá. –

3

En lugar de una estructura de lista, puede usar un conjunto (más apropiado si le preocupa la singularidad de la entidad), como ha sugerido Lars. Además, si el rendimiento es un problema, consideraría usar un TreeSet e implementar un Comparator para comparar instancias de Entity basadas en sus atributos. La estructura de árbol permitirá operaciones de inserción, eliminación y recuperación rápidas (complejidad logarítmica).

+1

Si crees que una estructura de mapa con hash no es factible, esta es probablemente la mejor respuesta. Su llamada actual a 'noDuplicates.indexOf (entity)' tendrá el peor rendimiento posible de 'O (N)' mientras que una llamada a 'TreeSet.contains()' puede garantizarle el rendimiento 'O (log (N))'. Con un pequeño esfuerzo en 'Comparator', puede hacer que use su método existente 'Entity.equals' también. (@rati: esto es más o menos lo que dijiste ... simplemente agregando más detalles) –

1

Todo depende de lo que esté haciendo la operación merge. ¿Cambia merge cualquiera de los atributos que se comparan al hacer equals? Si no, entonces usted se sorprenderá de lo rápido que será si hace esto:

En primer lugar, definir un hashCode para su clase Entity que sea compatible con su definición de equals.Una forma común de hacer esto es:

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; 
} 

A continuación, utilice un HashMap de manera que se pueden encontrar estas cosas:

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. 

debo señalar que me siento un poco sucia escribir el código anterior, debido realmente no debería hacer que las claves Map no sean inmutables, pero esto funcionará y será mucho, mucho más rápido que lo que está haciendo ahora.

+0

esto causará problemas si los campos usados ​​en 'Entity.hashCode()' son afectados por la operación 'merge' –

+0

Puedes considerar usar un HashSet en lugar de un HashMap. Automáticamente filtrará los duplicados por usted, por lo que podría omitir la verificación '" if (map.containsKey (entity)) "'. Código más limpio y la misma complejidad algorítmica. –

+1

@Brent Nash: pero eso no le permitirá llamar '' combinar' en la entidad que está almacenada en la estructura. Él tiene que hacer eso (aparentemente). –

0

A menos que tenga un motivo para necesitar el pedido de una lista, probablemente sea mejor que tenga un conjunto, específicamente un HashSet.

Veo su preocupación sobre el uso de una colección hash porque "la singularidad de la entidad se basa en 4 de sus atributos en conjunto", pero eso se supera fácilmente. Solo tiene que definir un método hashcode() que sea compatible con su método equals() existente, y luego puede insertar sus entidades en un conjunto, y como efecto secundario mágico, nunca tendrá que eliminar duplicados nuevamente.

0

Dos pasos simples para un O (N * log (N)) algoritmo:

  1. Ordenar la lista mediante un comparador sobre la base de los cuatro campos importantes
  2. iterar sobre la lista de la comparación de cada elemento a la siguiente en la lista, si son iguales, combínalos y elimina uno.
Cuestiones relacionadas