Qual è la quantità minima di codice necessaria per aggiornare un elenco con un altro elenco?

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

Domanda

Supponiamo di avere un elenco:

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

E un altro elenco ...

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

Come posso aggiornare originalList in modo che:

  1. Se l'int appare in newList, mantieni
  2. Se int non appare in newList, rimuovi
  3. Aggiungi eventuali ints da newList a originalList che non sono già presenti

Quindi - rendendo il contenuto di originalList:

{ 1, 5, 7, 11 }

Il motivo che sto chiedendo è perché ho un oggetto con una collezione di bambini. Quando l'utente aggiorna questa raccolta, invece di eliminare solo tutti i figli, quindi inserendo le loro selezioni, penso che sarebbe più efficace se agissi solo sui figli che sono stati aggiunti o rimossi, piuttosto che abbattere l'intera raccolta e inserire il new Elenca i bambini come se fossero tutti nuovi.

MODIFICA - Scusa - ho scritto un titolo orribile ... Avrei dovuto scrivere "minima quantità di codice" anziché "efficiente". Penso che abbia buttato via molte delle risposte che ho ottenuto. Sono tutti fantastici ... grazie!

È stato utile?

Soluzione

Scusa, ho scritto la mia prima risposta prima di vedere il tuo ultimo paragrafo.

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

Altri suggerimenti

originalList = newList;

O se preferisci che siano elenchi distinti:

originalList = new List<int>(newList);

Ma in entrambi i casi fa quello che vuoi. Secondo le tue regole, dopo l'aggiornamento, originalList sarà identico a newList.

AGGIORNAMENTO: ringrazio tutti per il supporto di questa risposta, ma dopo una lettura più approfondita della domanda, credo che la mia altra risposta (sotto) sia quella corretta.

Se usi alcuni metodi di estensione LINQ, puoi farlo in due righe:

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

Ciò presuppone (così come le soluzioni di altre persone) che hai sovrascritto Uguali nell'oggetto originale. Ma se non riesci a ignorare Equals per qualche motivo, puoi creare un IEqualityOperator in questo modo:

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

Quindi le righe sopra diventano:

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

E se stai comunque passando a IEqualityOperator, puoi rendere la seconda riga ancora più corta:

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

Se non sei preoccupato per l'eventuale ordinamento, un Hashtable / HashSet sarà probabilmente il più veloce.

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

Il mio pensiero iniziale era che avresti potuto chiamare originalList.AddRange (newList) e quindi rimuovere i duplicati, ma non sono sicuro che sarebbe più efficiente che cancellare l'elenco e ripopolarlo.

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

Non molto pulito, ma funziona.

Non esiste un modo per farlo, il più vicino a cui riesco a pensare è il modo in cui DataTable gestisce gli elementi nuovi ed eliminati.

Cosa @James Curran suggerisce semplicemente di sostituire l'oggetto originalList con l'oggetto newList. Dump della oldList, ma manterrà la variabile (cioè il puntatore è ancora lì).

Indipendentemente da ciò, dovresti considerare se l'ottimizzazione è tempo ben speso. È la maggior parte del tempo di esecuzione impiegato per copiare valori da un elenco all'altro, potrebbe valerne la pena. Se non lo è, ma piuttosto qualche ottimizzazione prematura che stai facendo, dovresti ignorarlo.

Trascorrere del tempo a lucidare la GUI o il profilo dell'applicazione prima di iniziare l'ottimizzazione è $ 0,02.

Questo è un problema comune che gli sviluppatori incontrano quando scrivono le interfacce utente per mantenere relazioni di database molti-a-molti. Non so quanto sia efficiente, ma ho scritto una classe di supporto per gestire questo scenario:

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

}

se stai utilizzando .Net 3.5

var List3 = List1.Intersect(List2);

Crea un nuovo elenco che contiene l'intersezione dei due elenchi, ed è per questo che credo tu stia girando qui.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top