2008-08-22 19 views
39

Tengo dos colecciones del mismo objeto, Collection<Foo> oldSet y Collection<Foo> newSet. La lógica requerida es la siguiente:¿Cuál es la mejor manera de comparar dos colecciones en Java y actuar sobre ellas?

  • si foo está en (*) oldSet pero no newSet, llame doRemove(foo)
  • más si foo no está en oldSet pero en newSet, llame doAdd(foo)
  • más si foo es en ambas colecciones pero modificadas, llame a doUpdate(oldFoo, newFoo)
  • else si !foo.activated && foo.startDate >= now, llame al doStart(foo)
  • persona si foo.activated && foo.endDate <= now, llame doEnd(foo)

(*) "en" significa que los partidos identificador único, no necesariamente el contenido.

El (legacy) código actual hace muchas comparaciones de averiguar removeSet, addSet, updateSet, startSet y endSet, y luego bucle para actuar sobre cada elemento.

El código es bastante desordenado (en parte porque ya he omitido algo de lógica de spaghetti) y estoy tratando de refactorizarlo. Algunos más información de fondo:

  • Por lo que yo sé, el oldSet y newSet en realidad están respaldados por ArrayList
  • Cada juego contiene menos de 100 artículos, más probable es máximo hacia fuera en 20
  • Este código se llama frecuencia (medido en millones/día), aunque rara vez los conjuntos difieren

Mis preguntas:

  • Si convierto oldSet y newSet en HashMap<Foo> (el orden no es motivo de preocupación), con los ID como claves, ¿facilitaría la lectura y la comparación del código? ¿Cuánto de tiempo & el rendimiento de la memoria es pérdida en la conversión?
  • ¿Sería más eficiente y conciso iterar los dos conjuntos y realizar la operación apropiada?

Respuesta

-1

Para un conjunto que los pequeños por lo general no vale la pena convertir de una matriz a un HashMap/set. De hecho, es mejor que los guardes en una matriz y luego los clasifiques por clave e iteremos sobre ambas listas simultáneamente para hacer la comparación.

9

He creado una aproximación de lo que creo que está buscando simplemente usando el Framework Collections en Java. Francamente, creo que probablemente sea excesivo como lo señala @Mike Deck. Para un conjunto tan pequeño de elementos para comparar y procesar, creo que los arreglos serían una mejor opción desde un punto de vista de procedimiento, pero aquí está mi solución pseudo-codificada (porque soy flojo).Tengo una suposición de que la clase Foo es comparable basa en su identificación única y no todos los datos en su contenido:

Collection<Foo> oldSet = ...; 
Collection<Foo> newSet = ...; 

private Collection difference(Collection a, Collection b) { 
    Collection result = a.clone(); 
    result.removeAll(b) 
    return result; 
} 

private Collection intersection(Collection a, Collection b) { 
    Collection result = a.clone(); 
    result.retainAll(b) 
    return result; 
} 

public doWork() { 
    // if foo is in(*) oldSet but not newSet, call doRemove(foo) 
    Collection removed = difference(oldSet, newSet); 
    if (!removed.isEmpty()) { 
     loop removed { 
      Foo foo = removedIter.next(); 
      doRemove(foo); 
     } 
    } 
    //else if foo is not in oldSet but in newSet, call doAdd(foo) 
    Collection added = difference(newSet, oldSet); 
    if (!added.isEmpty()) { 
     loop added { 
      Foo foo = addedIter.next(); 
      doAdd(foo); 
     } 
    } 

    // else if foo is in both collections but modified, call doUpdate(oldFoo, newFoo) 
    Collection matched = intersection(oldSet, newSet); 
    Comparator comp = new Comparator() { 
     int compare(Object o1, Object o2) { 
      Foo f1, f2; 
      if (o1 instanceof Foo) f1 = (Foo)o1; 
      if (o2 instanceof Foo) f2 = (Foo)o2; 
      return f1.activated == f2.activated ? f1.startdate.compareTo(f2.startdate) == 0 ? ... : f1.startdate.compareTo(f2.startdate) : f1.activated ? 1 : 0; 
     } 

     boolean equals(Object o) { 
      // equal to this Comparator..not used 
     } 
    } 
    loop matched { 
     Foo foo = matchedIter.next(); 
     Foo oldFoo = oldSet.get(foo); 
     Foo newFoo = newSet.get(foo); 
     if (comp.compareTo(oldFoo, newFoo) != 0) { 
      doUpdate(oldFoo, newFoo); 
     } else { 
      //else if !foo.activated && foo.startDate >= now, call doStart(foo) 
      if (!foo.activated && foo.startDate >= now) doStart(foo); 

      // else if foo.activated && foo.endDate <= now, call doEnd(foo) 
      if (foo.activated && foo.endDate <= now) doEnd(foo); 
     } 
    } 
} 

En cuanto a sus preguntas: Si convierto oldset y nuevoSet en HashMap (orden es no es motivo de preocupación aquí), con las identificaciones como claves, ¿haría el código más fácil de leer y más fácil de comparar? ¿Cuánto de tiempo & el rendimiento de la memoria es pérdida en la conversión? Creo que probablemente harías el código más legible utilizando un mapa PERO ... probablemente usarías más memoria y tiempo durante la conversión.

¿Sería más eficiente y conciso iterar los dos conjuntos y realizar la operación apropiada? Sí, esto sería lo mejor de ambos mundos especialmente si sigues los consejos de @Mike Sharek de rodar tu propia lista con los métodos especializados o si sigues algo como el patrón de diseño de visitante para recorrer tu colección y procesar cada elemento.

2

me mudaría a las listas y resolver de esta manera:

  1. Ordenar por ambas listas ascendente utilizando Identificación del encargo Comparator si los objetos en las listas no son Comparable
  2. iterar sobre los elementos de ambas listas como en la fase de fusión en merge sort algorithm, pero en lugar de fusionar listas, comprueba su lógica.

El código sería más o menos así:

/* Main method */ 
private void execute(Collection<Foo> oldSet, Collection<Foo> newSet) { 
    List<Foo> oldList = asSortedList(oldSet); 
    List<Foo> newList = asSortedList(newSet); 

    int oldIndex = 0; 
    int newIndex = 0; 
    // Iterate over both collections but not always in the same pace 
    while(oldIndex < oldList.size() 
     && newIndex < newIndex.size()) { 
    Foo oldObject = oldList.get(oldIndex); 
    Foo newObject = newList.get(newIndex); 

    // Your logic here 
    if(oldObject.getId() < newObject.getId()) { 
     doRemove(oldObject); 
     oldIndex++; 
    } else if(oldObject.getId() > newObject.getId()) { 
     doAdd(newObject); 
     newIndex++; 
    } else if(oldObject.getId() == newObject.getId() 
      && isModified(oldObject, newObject)) { 
     doUpdate(oldObject, newObject); 
     oldIndex++; 
     newIndex++; 
    } else { 
     ... 
    } 
    }// while 

    // Check if there are any objects left in *oldList* or *newList* 

    for(; oldIndex < oldList.size(); oldIndex++) { 
    doRemove(oldList.get(oldIndex)); 
    }// for(oldIndex) 

    for(; newIndex < newList.size(); newIndex++) { 
    doAdd(newList.get(newIndex)); 
    }// for(newIndex) 
}// execute(oldSet, newSet) 

/** Create sorted list from collection 
    If you actually perform any actions on input collections than you should 
    always return new instance of list to keep algorithm simple. 
*/ 
private List<Foo> asSortedList(Collection<Foo> data) { 
    List<Foo> resultList; 
    if(data instanceof List) { 
    resultList = (List<Foo>)data; 
    } else { 
    resultList = new ArrayList<Foo>(data); 
    } 
    Collections.sort(resultList) 
    return resultList; 
} 
34

biblioteca commons.collections de Apache tiene una clase CollectionUtils que proporciona métodos fáciles de usar para la manipulación de la colección/de cheques, como intersección, diferencia y unión.

Los documentos de la API org.apache.commons.collections.CollectionUtils son here.

+0

+1 por sugerir un sólido URL biblioteca –

+1

ya no está disponible. :( –

+0

http://commons.apache.org/proper/commons-collections/javadocs/api-4.0/index.html –

2

Creo que la manera más fácil de hacerlo es mediante el uso de las colecciones de apache api - CollectionUtils.subtract (list1, list2) siempre que las listas sean del mismo tipo.

-2

Para consultar una lista o un conjunto, podemos usar Arrays.equals(object[], object[]). Verificará solo los valores. Para obtener el Object[] podemos usar el método Collection.toArray().

20

Usted puede utilizar Java 8 corrientes, por ejemplo

set1.stream().filter(s -> set2.contains(s)).collect(Collectors.toSet()); 

o Sets clase de Guava:

Set<String> intersection = Sets.intersection(set1, set2); 
Set<String> difference = Sets.difference(set1, set2); 
Set<String> symmetricDifference = Sets.symmetricDifference(set1, set2); 
Set<String> union = Sets.union(set1, set2); 
+0

guayaba es la mejor opción, gracias –

+1

Mientras llama a estas colecciones "conjuntos", el tipo real es ' Colección ', por lo que a diferencia de los" Sets "actuales, los duplicados no están fuera de toda duda. –

0
public static boolean doCollectionsContainSameElements(
     Collection<Integer> c1, Collection<Integer> c2){ 

    if (c1 == null || c2 == null) { 
     return false; 
    } 
    else if (c1.size() != c2.size()) { 
     return false; 
    } else {  
     return c1.containsAll(c2) && c2.containsAll(c1); 
    }  
} 
Cuestiones relacionadas