Предположим, у меня есть один список:
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, чтобы:
Таким образом - делаем содержимое originalList:
{ 1, 5, 7, 11 }
Я спрашиваю, потому что у меня есть объект с коллекцией потомков. Когда пользователь обновляет эту коллекцию, вместо того, чтобы просто удалять все дочерние элементы, а затем вставлять их выбор, я думаю, было бы более эффективно, если бы я просто действовал на дочерних элементов, которые были добавлены или удалены, вместо того, чтобы срывать всю коллекцию и вставлять newList дочерних элементов, как будто они все новые.
РЕДАКТИРОВАТЬ - Извините - я написал ужасный заголовок ... Я должен был написать «наименьшее количество кода» вместо «эффективно». Я думаю, что это отбросило многие ответы, которые я получил. Все они молодцы ... спасибо!
Могут ли быть повторяющиеся значения? например {1, 5, 5, 7, 11}
Кстати, если вы используете NHibernate и имеете дело с недублирующими объектами, вы можете использовать Iesi.Collections.ISet <T>, а не IList <T>. ISet <T> обеспечивает отсутствие дубликатов.
@rick - Нет, мне не нужны повторяющиеся значения.
@Kyralessa - Да, именно поэтому я пишу этот вопрос - все возвращается к nhibernate. Я передаю родительский элемент вместе с его дочерним элементом в качестве JSON в свой код. Поскольку он сериализуется без родительского свойства для дочернего элемента, мне нужно назначить его в .NET вручную. :( Значит, это?





originalList = newList;
Или, если вы предпочитаете, чтобы они были отдельными списками:
originalList = new List<int>(newList);
Но в любом случае делайте то, что хотите. По вашим правилам после обновления originalList будет идентичен newList.
ОБНОВЛЕНИЕ: я благодарю вас всех за поддержку этого ответа, но после более внимательного прочтения вопроса я считаю, что мой другой ответ (ниже) является правильным.
Я собираюсь убить себя, если все будет так просто. Я добавлю это в свой код и посмотрю!
В понедельник еще рано;)
Моя первоначальная мысль заключалась в том, что вы можете вызвать 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);
}
}
Не очень чисто - но работает.
Да, вот как я это делаю сейчас, и, как и вы, я чувствовал, что это не очень чисто и, возможно, было бы проще.
Извините, написал свой первый ответ до того, как увидел ваш последний абзац.
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 объединяет их, и это очень мило ... однако этот ответ - именно то, что мне было нужно, и содержит намного меньше кода, чем то, что было у меня для моего первоначального решения! Благодарность
Обратите внимание, что это комплексный метод O (n * n) (считается, что вызов Contains равен O (n))
Обратите внимание, что после объединения с указанным выше алгоритмом порядок элементов в OriginalList не обязательно будет соответствовать порядку в NewList.
Если вас не беспокоит окончательный порядок, 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, представляющие записи базы данных. Мне следовало изменить заголовок так, чтобы он был с наименьшим количеством кода, а не с «эффективным», поскольку я думаю, что это могло сбить с толку некоторых людей.
Это распространенная проблема, с которой сталкиваются разработчики при написании пользовательских интерфейсов для поддержания отношений "многие ко многим" с базами данных. Я не знаю, насколько это эффективно, но я написал вспомогательный класс для обработки этого сценария:
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, если вы используете ссылочные типы. Это намного больше кода.
Допустим, я использовал ссылочные типы ... и вместо целых чисел я использовал объект, у которого был guid ParentId, а затем некоторые другие поля. Как мне тогда провести сравнение свойства ParentId каждого объекта?
Самый простой способ - переопределить Equals. Обе строки выше будут использовать переопределение. (Я тестировал его, и поэтому я не использовал Except; он не использует переопределение Equals.) Если вы не могу переопределяете Equals, вы можете создать IEqualityComparer и передать его в качестве второго аргумента в Contains.
Спасибо за образец кода, я опробую его в своем проекте сегодня (потому что на самом деле я использую ссылочные типы, но не хотел испортить свой вопрос и подумал: «Сделайте это просто». Спасибо!
если вы используете .Net 3.5
var List3 = List1.Intersect(List2);
Создает новый список, который содержит пересечение двух списков, что, как я полагаю, вы здесь преследуете.
Сколько времени ваше приложение тратит на копирование значений?