¿Cuál es la menor cantidad de código necesaria para actualizar una lista con otra lista?

StackOverflow https://stackoverflow.com/questions/148662

Pregunta

Supongamos que tengo una lista:

IList<int> originalList = new List<int>();
originalList.add(1);
originalList.add(5);
originalList.add(10);

Y otra lista ...

IList<int> newList = new List<int>();
newList.add(1);
newList.add(5);
newList.add(7);  
newList.add(11);

¿Cómo puedo actualizar la lista original para que:

  1. Si el int aparece en newList, mantenga
  2. Si el int no aparece en newList, elimínelo
  3. Agregue cualquier entrada de newList a originalList que ya no esté allí

Así, haciendo el contenido de la lista original:

{ 1, 5, 7, 11 }

La razón por la que pregunto es porque tengo un objeto con una colección de niños. Cuando el usuario actualiza esta colección, en lugar de simplemente eliminar a todos los niños y luego insertar sus selecciones, creo que sería más eficiente si actuara sobre los niños que se agregaron o eliminaron, en lugar de destruir toda la colección e insertar la newLista a los niños como si fueran todos nuevos.

EDITAR - Lo siento - Escribí un título horrible ... Debería haber escrito 'la menor cantidad de código' en lugar de 'eficiente'. Creo que eso arrojó muchas de las respuestas que he recibido. Todos son geniales ... ¡gracias!

¿Fue útil?

Solución

Lo siento, escribí mi primera respuesta antes de ver tu último párrafo.

for(int i = originalList.length-1; i >=0; --i)
{
     if (!newList.Contains(originalList[i])
            originalList.RemoveAt(i);
}

foreach(int n in newList)
{
     if (!originaList.Contains(n))
           originalList.Add(n);
}

Otros consejos

originalList = newList;

O si prefieres que sean listas distintas:

originalList = new List<int>(newList);

Pero, de cualquier manera hace lo que quieres. Según sus reglas, después de la actualización, la lista original será idéntica a la lista nueva.

ACTUALIZACIÓN: Les agradezco a todos por el apoyo de esta respuesta, pero después de una lectura más detallada de la pregunta, creo que mi otra respuesta (a continuación) es la correcta.

Si usa algunos métodos de extensión LINQ, puede hacerlo en dos líneas:

originalList.RemoveAll(x => !newList.Contains(x));
originalList.AddRange(newList.Where(x => !originalList.Contains(x)));

Esto supone (al igual que las soluciones de otras personas) que ha anulado los iguales en su objeto original. Pero si no puede anular Equals por alguna razón, puede crear un IEqualityOperator como este:

class EqualThingTester : IEqualityComparer<Thing>
{
    public bool Equals(Thing x, Thing y)
    {
        return x.ParentID.Equals(y.ParentID);
    }

    public int GetHashCode(Thing obj)
    {
        return obj.ParentID.GetHashCode();
    }
}

Entonces las líneas anteriores se convierten en:

originalList.RemoveAll(x => !newList.Contains(x, new EqualThingTester()));
originalList.AddRange(newList.Where(x => !originalList.Contains(x, new EqualThingTester())));

Y, de todos modos, si está pasando un IEqualityOperator, puede acortar aún más la segunda línea:

originalList.RemoveAll(x => !newList.Contains(x, new EqualThingTester()));
originalList.AddRange(newList.Except(originalList, new EqualThingTester()));

Si no le preocupa el eventual pedido, un Hashtable / HashSet probablemente sea el más rápido.

solución LINQ:

originalList = new List<int>(
                      from x in newList
                      join y in originalList on x equals y into z
                      from y in z.DefaultIfEmpty()
                      select x);

Mi pensamiento inicial era que podía llamar a originalList.AddRange (newList) y luego eliminar los duplicados, pero no estoy seguro de si eso sería más eficiente que borrar la lista y volver a llenarla.

List<int> firstList = new List<int>() {1, 2, 3, 4, 5};
List<int> secondList = new List<int>() {1, 3, 5, 7, 9};

List<int> newList = new List<int>();

foreach (int i in firstList)
{
  newList.Add(i);
}

foreach (int i in secondList)
{
  if (!newList.Contains(i))
  {
    newList.Add(i);
  }
}

No muy limpio, pero funciona.

No hay una forma integrada de hacer esto, lo más cerca que puedo pensar es en la forma en que DataTable maneja los elementos nuevos y eliminados.

Qué @James Curran sugiere que simplemente se reemplace el objeto originalList con el objeto newList. Volcará la lista antigua, pero mantendrá la variable (es decir, el puntero todavía está allí).

En cualquier caso, debe considerar si optimizar este es un tiempo bien empleado. Es la mayoría del tiempo de ejecución dedicado a copiar valores de una lista a la siguiente, podría valer la pena. Si no lo es, sino una optimización prematura que está realizando, debe ignorarlo.

Pasar tiempo puliendo la GUI o el perfil de la aplicación antes de comenzar a optimizar es mi $ .02.

Este es un problema común que los desarrolladores encuentran al escribir las IU para mantener relaciones de base de datos de muchos a muchos. No sé qué tan eficiente es esto, pero escribí una clase auxiliar para manejar este escenario:

public class IEnumerableDiff<T>
{
    private delegate bool Compare(T x, T y);

    private List<T> _inXAndY;
    private List<T> _inXNotY;
    private List<T> _InYNotX;

    /// <summary>
    /// Compare two IEnumerables.
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="compareKeys">True to compare objects by their keys using Data.GetObjectKey(); false to use object.Equals comparison.</param>
    public IEnumerableDiff(IEnumerable<T> x, IEnumerable<T> y, bool compareKeys)
    {
        _inXAndY = new List<T>();
        _inXNotY = new List<T>();
        _InYNotX = new List<T>();
        Compare comparer = null;
        bool hit = false;

        if (compareKeys)
        {
            comparer = CompareKeyEquality;
        }
        else
        {
            comparer = CompareObjectEquality;
        }


        foreach (T xItem in x)
        {
            hit = false;
            foreach (T yItem in y)
            {
                if (comparer(xItem, yItem))
                {
                    _inXAndY.Add(xItem);
                    hit = true;
                    break;
                }
            }
            if (!hit)
            {
                _inXNotY.Add(xItem);
            }
        }

        foreach (T yItem in y)
        {
            hit = false;
            foreach (T xItem in x)
            {
                if (comparer(yItem, xItem))
                {
                    hit = true;
                    break;
                }
            }
            if (!hit)
            {
                _InYNotX.Add(yItem);
            }
        }
    }

    /// <summary>
    /// Adds and removes items from the x (current) list so that the contents match the y (new) list.
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="compareKeys"></param>
    public static void SyncXList(IList<T> x, IList<T> y, bool compareKeys)
    {
        var diff = new IEnumerableDiff<T>(x, y, compareKeys);
        foreach (T item in diff.InXNotY)
        {
            x.Remove(item);
        }
        foreach (T item in diff.InYNotX)
        {
            x.Add(item);
        }
    }

    public IList<T> InXAndY
    {
        get { return _inXAndY; }
    }

    public IList<T> InXNotY
    {
        get { return _inXNotY; }
    }

    public IList<T> InYNotX
    {
        get { return _InYNotX; }
    }

    public bool ContainSameItems
    {
        get { return _inXNotY.Count == 0 && _InYNotX.Count == 0; }
    }

    private bool CompareObjectEquality(T x, T y)
    {
        return x.Equals(y);
    }

    private bool CompareKeyEquality(T x, T y)
    {
        object xKey = Data.GetObjectKey(x);
        object yKey = Data.GetObjectKey(y);
        return xKey.Equals(yKey);
    }

}

si estás usando .Net 3.5

var List3 = List1.Intersect(List2);

Crea una nueva lista que contiene la intersección de las dos listas, que es lo que creo que estás buscando aquí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top