Какой наименьший объем кода необходим для обновления одного списка другим списком?

Предположим, у меня есть один список:

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

И еще список ...

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

Как мне обновить originalList, чтобы:

  1. Если int появляется в newList, оставьте
  2. Если int не отображается в newList, удалите
  3. Добавьте любые ints из newList в originalList, которых там еще нет

Таким образом - делаем содержимое originalList:

{ 1, 5, 7, 11 }

Я спрашиваю, потому что у меня есть объект с коллекцией потомков. Когда пользователь обновляет эту коллекцию, вместо того, чтобы просто удалять все дочерние элементы, а затем вставлять их выбор, я думаю, было бы более эффективно, если бы я просто действовал на дочерних элементов, которые были добавлены или удалены, вместо того, чтобы срывать всю коллекцию и вставлять newList дочерних элементов, как будто они все новые.

РЕДАКТИРОВАТЬ - Извините - я написал ужасный заголовок ... Я должен был написать «наименьшее количество кода» вместо «эффективно». Я думаю, что это отбросило многие ответы, которые я получил. Все они молодцы ... спасибо!

Сколько времени ваше приложение тратит на копирование значений?

Mats Fredriksson 29.09.2008 17:52

Могут ли быть повторяющиеся значения? например {1, 5, 5, 7, 11}

RickL 29.09.2008 20:26

Кстати, если вы используете NHibernate и имеете дело с недублирующими объектами, вы можете использовать Iesi.Collections.ISet <T>, а не IList <T>. ISet <T> обеспечивает отсутствие дубликатов.

Ryan Lundy 29.09.2008 20:54

@rick - Нет, мне не нужны повторяющиеся значения.

EvilSyn 29.09.2008 22:11

@Kyralessa - Да, именно поэтому я пишу этот вопрос - все возвращается к nhibernate. Я передаю родительский элемент вместе с его дочерним элементом в качестве JSON в свой код. Поскольку он сериализуется без родительского свойства для дочернего элемента, мне нужно назначить его в .NET вручную. :( Значит, это?

EvilSyn 29.09.2008 22:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
457
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

originalList = newList;

Или, если вы предпочитаете, чтобы они были отдельными списками:

originalList = new List<int>(newList);

Но в любом случае делайте то, что хотите. По вашим правилам после обновления originalList будет идентичен newList.

ОБНОВЛЕНИЕ: я благодарю вас всех за поддержку этого ответа, но после более внимательного прочтения вопроса я считаю, что мой другой ответ (ниже) является правильным.

Я собираюсь убить себя, если все будет так просто. Я добавлю это в свой код и посмотрю!

EvilSyn 29.09.2008 17:44

В понедельник еще рано;)

Vivek 29.09.2008 18:02

Моя первоначальная мысль заключалась в том, что вы можете вызвать originalList.AddRange (newList), а затем удалить дубликаты, но я не уверен, что это будет более эффективно, чем очистка списка и его повторное заполнение.

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

Не очень чисто - но работает.

Да, вот как я это делаю сейчас, и, как и вы, я чувствовал, что это не очень чисто и, возможно, было бы проще.

EvilSyn 29.09.2008 17:48
Ответ принят как подходящий

Извините, написал свой первый ответ до того, как увидел ваш последний абзац.

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

Йепперс, это именно то, что мне было нужно ... Мне нужны были «неизменные» ответы, чтобы остаться прежними. Итак, сегодня я узнал две вещи: oldList = newList объединяет их, и это очень мило ... однако этот ответ - именно то, что мне было нужно, и содержит намного меньше кода, чем то, что было у меня для моего первоначального решения! Благодарность

EvilSyn 29.09.2008 19:07

Обратите внимание, что это комплексный метод O (n * n) (считается, что вызов Contains равен O (n))

xtofl 30.12.2008 15:56

Обратите внимание, что после объединения с указанным выше алгоритмом порядок элементов в OriginalList не обязательно будет соответствовать порядку в NewList.

Lightman 01.10.2015 23:53

Если вас не беспокоит окончательный порядок, Hashtable / HashSet, вероятно, будет самым быстрым.

Решение 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);

Нет встроенного способа сделать это, самое близкое, что я могу придумать, - это способ, которым DataTable обрабатывает новые и удаленные элементы.

@ Джеймс Карран предлагает просто заменить объект originalList на объект newList. Он сбросит старый список, но сохранит переменную (т.е. указатель все еще там).

Тем не менее, вам следует подумать, стоит ли оптимизировать это время с пользой. Если большая часть времени выполнения тратится на копирование значений из одного списка в другой, оно того стоит. Если это не так, а вы делаете некоторую преждевременную оптимизацию, вам следует игнорировать это.

Тратить время на полировку графического интерфейса или профилирование приложения, прежде чем начинать оптимизацию, - это мои 0,02 доллара.

Собственно, я спрашиваю об этом из-за проблем с сохранением NHibernate ... Вместо int у меня есть Entities, представляющие записи базы данных. Мне следовало изменить заголовок так, чтобы он был с наименьшим количеством кода, а не с «эффективным», поскольку я думаю, что это могло сбить с толку некоторых людей.

EvilSyn 29.09.2008 17:56

Это распространенная проблема, с которой сталкиваются разработчики при написании пользовательских интерфейсов для поддержания отношений "многие ко многим" с базами данных. Я не знаю, насколько это эффективно, но я написал вспомогательный класс для обработки этого сценария:

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

}

Если вы используете какие-либо методы расширения LINQ, вы можете сделать это двумя строками:

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

Это предполагает (как и решения других людей), что вы переопределили Equals в исходном объекте. Но если по какой-то причине вы не можете переопределить Equals, вы можете создать IEqualityOperator следующим образом:

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

Затем приведенные выше строки становятся:

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

И если вы все равно передаете IEqualityOperator, вы можете сделать вторую строку еще короче:

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

Кстати, использование Except делает его еще короче, но тогда вам нужно создать и передать собственный IEqualityComparer, если вы используете ссылочные типы. Это намного больше кода.

Ryan Lundy 29.09.2008 20:19

Допустим, я использовал ссылочные типы ... и вместо целых чисел я использовал объект, у которого был guid ParentId, а затем некоторые другие поля. Как мне тогда провести сравнение свойства ParentId каждого объекта?

EvilSyn 29.09.2008 20:48

Самый простой способ - переопределить Equals. Обе строки выше будут использовать переопределение. (Я тестировал его, и поэтому я не использовал Except; он не использует переопределение Equals.) Если вы не могу переопределяете Equals, вы можете создать IEqualityComparer и передать его в качестве второго аргумента в Contains.

Ryan Lundy 29.09.2008 20:58

Спасибо за образец кода, я опробую его в своем проекте сегодня (потому что на самом деле я использую ссылочные типы, но не хотел испортить свой вопрос и подумал: «Сделайте это просто». Спасибо!

EvilSyn 29.09.2008 21:06

если вы используете .Net 3.5

var List3 = List1.Intersect(List2);

Создает новый список, который содержит пересечение двух списков, что, как я полагаю, вы здесь преследуете.

Другие вопросы по теме