¿Cómo puedo calcular la diferencia entre dos ArrayLists?

81

Tengo dos ArrayLists.

ArrayList A contiene:

['2009-05-18','2009-05-19','2009-05-21']

ArrayList B contiene:

['2009-05-18','2009-05-18','2009-05-19','2009-05-19','2009-05-20','2009-05-21','2009-05-21','2009-05-22']

Tengo que comparar ArrayList A y ArrayList B. El resultado ArrayList debe contener la Lista que no existe en ArrayList A.

El resultado de ArrayList debe ser:

['2009-05-20','2009-05-22']

como comparar

naveen
fuente

Respuestas:

193

En Java, puede utilizar el método de la Collectioninterfaz removeAll.

// Create a couple ArrayList objects and populate them
// with some delicious fruits.
Collection firstList = new ArrayList() {{
    add("apple");
    add("orange");
}};

Collection secondList = new ArrayList() {{
    add("apple");
    add("orange");
    add("banana");
    add("strawberry");
}};

// Show the "before" lists
System.out.println("First List: " + firstList);
System.out.println("Second List: " + secondList);

// Remove all elements in firstList from secondList
secondList.removeAll(firstList);

// Show the "after" list
System.out.println("Result: " + secondList);

El código anterior producirá el siguiente resultado:

First List: [apple, orange]
Second List: [apple, orange, banana, strawberry]
Result: [banana, strawberry]
William Brendel
fuente
7
Si su lista es de una clase personalizada, entonces tendrá que anular el método equals de su clase, ¿verdad?
RTF
5
@RTF Sí, debe proporcionar una implementación equalsque permita comparar sus objetos. Lea sobre la implementación hashCodetambién. Por ejemplo, nota cómo String::equalses entre mayúsculas y minúsculas , por lo que "Apple" y "Apple" no se considerará la misma.
Basil Bourque
1
En realidad, la respuesta depende de lo que quieras hacer. RemoveAll no conservará los duplicados. Si agrega otra cadena de "manzana" a su segunda lista, también se eliminará, lo que puede no ser siempre lo que desea.
Jules Testard
2
Esto es tan ineficiente. Es triste que esta sea la respuesta seleccionada y mejor calificada. removeAllinvoca firstList.containstodos los elementos de secondList. Usar un HashSetevitaría eso y hay algunas buenas respuestas más bajas.
Vlasec
12

En Java 8 con streams, en realidad es bastante simple. EDITAR: Puede ser eficiente sin flujos, ver más abajo.

List<String> listA = Arrays.asList("2009-05-18","2009-05-19","2009-05-21");
List<String> listB = Arrays.asList("2009-05-18","2009-05-18","2009-05-19","2009-05-19",
                                   "2009-05-20","2009-05-21","2009-05-21","2009-05-22");

List<String> result = listB.stream()
                           .filter(not(new HashSet<>(listA)::contains))
                           .collect(Collectors.toList());

Tenga en cuenta que el conjunto de hash solo se crea una vez: la referencia del método está vinculada a su método contiene. Hacer lo mismo con lambda requeriría tener el conjunto en una variable. Hacer una variable no es una mala idea, especialmente si le resulta desagradable o más difícil de entender.

No puede negar fácilmente el predicado sin algo como este método de utilidad (o conversión explícita), ya que no puede llamar directamente a la referencia del método negado (primero se necesita la inferencia de tipo).

private static <T> Predicate<T> not(Predicate<T> predicate) {
    return predicate.negate();
}

Si las transmisiones tuvieran un filterOutmétodo o algo, se vería mejor.


Además, @Holger me dio una idea. ArrayListtiene su removeAllmétodo optimizado para múltiples eliminaciones, solo reorganiza sus elementos una vez. Sin embargo, utiliza el containsmétodo proporcionado por la colección dada, por lo que necesitamos optimizar esa parte si listAes cualquier cosa menos pequeña.

Con listAy listBdeclarado previamente, esta solución no necesita Java 8 y es muy eficiente.

List<String> result = new ArrayList(listB);
result.removeAll(new HashSet<>(listA));
Vlasec
fuente
1
@Bax ¿Por qué editar? El original era más limpio y funcionalmente idéntico.
shmosel
1
@Bax No, no es así.
shmosel
1
Con Guava, puedes hacerlo Predicates.in(new HashSet<>(listA)).negate().
shmosel
1
Acabo de ejecutar algunas pruebas y esta solución es ~ 10-20% más rápida que listB.removeAll (new HashSet <> (listA)). y Guava Sets.difference (...) si 2 veces más lento que los arroyos.
Telebog
1
@Vlasec ArrayList.removetiene complejidad lineal, pero ArrayList.removeAllno se basa en removeuna operación de actualización de matriz lineal, sino que la realiza, copiando cada elemento restante en su lugar final. Por el contrario, la implementación de referencia de LinkedListno se ha optimizado removeAllsino que realiza una removeoperación para cada elemento afectado, que actualizará hasta cinco referencias cada vez. Por lo tanto, dependiendo de la proporción entre los elementos eliminados y los restantes, los ArrayList's removeAlltodavía pueden tener un rendimiento significativamente mejor que los LinkedList' s, incluso para listas grandes.
Holger
9

EDITAR: La pregunta original no especificó el idioma. Mi respuesta está en C #.

En su lugar, debería utilizar HashSet para este propósito. Si debe utilizar ArrayList, puede utilizar los siguientes métodos de extensión:

var a = arrayListA.Cast<DateTime>();
var b = arrayListB.Cast<DateTime>();    
var c = b.Except(a);

var arrayListC = new ArrayList(c.ToArray());

usando HashSet ...

var a = new HashSet<DateTime>(); // ...and fill it
var b = new HashSet<DateTime>(); // ...and fill it
b.ExceptWith(a); // removes from b items that are in a
Josh
fuente
8

He usado Guava Sets.difference .

Los parámetros son conjuntos y no colecciones generales, pero una forma práctica de crear conjuntos a partir de cualquier colección (con elementos únicos) es Guava ImmutableSet.copyOf (Iterable).

(Primero publiqué esto en una pregunta relacionada / dupe , pero también lo estoy copiando aquí porque creo que es una buena opción que hasta ahora falta).

Peter Lamberg
fuente
8

Aunque esta es una pregunta muy antigua en Java 8, podría hacer algo como

 List<String> a1 = Arrays.asList("2009-05-18", "2009-05-19", "2009-05-21");
 List<String> a2 = Arrays.asList("2009-05-18", "2009-05-18", "2009-05-19", "2009-05-19", "2009-05-20", "2009-05-21","2009-05-21", "2009-05-22");

 List<String> result = a2.stream().filter(elem -> !a1.contains(elem)).collect(Collectors.toList());
jesantana
fuente
Me encanta Java 8, pero deberíamos pensar en la complejidad. Aunque las listas también tienen Collectionel método de contains, es muy ineficiente. Debe pasar por toda la lista si no se encuentra. Hacerlo para cada elemento de a2puede ser dolorosamente lento en listas más grandes, por lo que hago un conjunto a1en mi respuesta.
Vlasec
2

Supongo que estás hablando de C #. Si es así, puedes probar esto

    ArrayList CompareArrayList(ArrayList a, ArrayList b)
    {
        ArrayList output = new ArrayList();
        for (int i = 0; i < a.Count; i++)
        {
            string str = (string)a[i];
            if (!b.Contains(str))
            {
                if(!output.Contains(str)) // check for dupes
                    output.Add(str);
            }
        }
        return output;
    }
Pavels
fuente
Lo siento, no mencioné el lenguaje de programación, está bien, pero necesito java gracias por su repetición
naveen
Esto es correcto. Sin embargo, también es una forma muy ineficiente de hacerlo. Básicamente, recorrerás todos los tiempos de la blista a.Count. En su lugar, puede crear un método HashSetpara usarlo Containso usar el RemoveAllmétodo en el conjunto para obtener exactamente los resultados que desea.
Vlasec
1

Solo estás comparando cadenas.

Coloque los valores en ArrayList A como claves en HashTable A.
Coloque los valores en ArrayList B como claves en HashTable B.

Luego, para cada clave en HashTable A, elimínela de HashTable B si existe.

Lo que le queda en HashTable B son las cadenas (claves) que no eran valores en ArrayList A.

Ejemplo de C # (3.0) agregado en respuesta a la solicitud de código:

List<string> listA = new List<string>{"2009-05-18","2009-05-19","2009-05-21'"};
List<string> listB = new List<string>{"2009-05-18","2009-05-18","2009-05-19","2009-05-19","2009-05-20","2009-05-21","2009-05-21","2009-05-22"};

HashSet<string> hashA = new HashSet<string>();
HashSet<string> hashB = new HashSet<string>();

foreach (string dateStrA in listA) hashA.Add(dateStrA);
foreach (string dateStrB in listB) hashB.Add(dateStrB);

foreach (string dateStrA in hashA)
{
    if (hashB.Contains(dateStrA)) hashB.Remove(dateStrA);
}

List<string> result = hashB.ToList<string>();
Demi
fuente
En su código C #, la hashAvariable es efectivamente inútil. En su lugar, podría hacer un foreach listAya hashAque solo se itera y Containsnunca se llama.
Vlasec
(Además, siempre que C # tenga un método RemoveAll como lo hace Java, podría evitar hacer su propio ciclo ... pero nuevamente, lo voté porque esta solución es al menos mucho más eficiente que la seleccionada).
Vlasec
1

Hola, use esta clase, esto comparará ambas listas y muestra exactamente la falta de coincidencia en ambas listas.

import java.util.ArrayList;
import java.util.List;


public class ListCompare {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> dbVinList;
        dbVinList = new ArrayList<String>();
        List<String> ediVinList;
        ediVinList = new ArrayList<String>();           

        dbVinList.add("A");
        dbVinList.add("B");
        dbVinList.add("C");
        dbVinList.add("D");

        ediVinList.add("A");
        ediVinList.add("C");
        ediVinList.add("E");
        ediVinList.add("F");
        /*ediVinList.add("G");
        ediVinList.add("H");
        ediVinList.add("I");
        ediVinList.add("J");*/  

        List<String> dbVinListClone = dbVinList;
        List<String> ediVinListClone = ediVinList;

        boolean flag;
        String mismatchVins = null;
        if(dbVinListClone.containsAll(ediVinListClone)){
            flag = dbVinListClone.removeAll(ediVinListClone);   
            if(flag){
                mismatchVins = getMismatchVins(dbVinListClone);
            }
        }else{
            flag = ediVinListClone.removeAll(dbVinListClone);
            if(flag){
                mismatchVins = getMismatchVins(ediVinListClone);
            }
        }
        if(mismatchVins != null){
            System.out.println("mismatch vins : "+mismatchVins);
        }       

    }

    private static String getMismatchVins(List<String> mismatchList){
        StringBuilder mismatchVins = new StringBuilder();
        int i = 0;
        for(String mismatch : mismatchList){
            i++;
            if(i < mismatchList.size() && i!=5){
                mismatchVins.append(mismatch).append(",");  
            }else{
                mismatchVins.append(mismatch);
            }
            if(i==5){               
                break;
            }
        }
        String mismatch1;
        if(mismatchVins.length() > 100){
            mismatch1 = mismatchVins.substring(0, 99);
        }else{
            mismatch1 = mismatchVins.toString();
        }       
        return mismatch1;
    }

}
Raj Mohamad
fuente
¿Sabías que los clones en realidad no son clones en absoluto?
Vlasec
1

ESTE TRABAJA TAMBIÉN CON Arraylist

    // Create a couple ArrayList objects and populate them
    // with some delicious fruits.
    ArrayList<String> firstList = new ArrayList<String>() {/**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("pea");
    }};

    ArrayList<String> secondList = new ArrayList<String>() {

    /**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("banana");
        add("strawberry");
    }};

    // Show the "before" lists
    System.out.println("First List: " + firstList);
    System.out.println("Second List: " + secondList);

    // Remove all elements in firstList from secondList
    secondList.removeAll(firstList);

    // Show the "after" list
    System.out.println("Result: " + secondList);
psicópata
fuente
1
la salida: Primera lista: [manzana, naranja, pippo] Segunda lista: [manzana, naranja, plátano, fresa] Resultado: [plátano, fresa]
psycho
Lo hace. Pero cuando lo diga, no debe olvidar tener en cuenta que puede ser dolorosamente lento en listas grandes. Tenga en cuenta que los métodos como removey containsnecesitan buscar en toda la lista. Si se llama repetidamente en un ciclo (lo que ocurre en removeAll), obtiene una complejidad cuadrática. Sin embargo, podría usar un conjunto de hash y hacerlo simplemente lineal.
Vlasec