Какой наименьший объем кода необходим для обновления одного списка другим списком?
-
02-07-2019 - |
Вопрос
Предположим, у меня есть один список:
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, чтобы:
- Если int появляется в newList, сохраните
- Если int не отображается в newList, удалите
- Добавьте любые целые числа из newList в originalList, которых там еще нет.
Таким образом — создание содержимого originalList:
{ 1, 5, 7, 11 }
Причина, по которой я спрашиваю, заключается в том, что у меня есть объект с коллекцией дочерних элементов.Когда пользователь обновляет эту коллекцию, вместо того, чтобы просто удалять всех дочерних элементов, а затем вставлять их выборки, я думаю, было бы более эффективно, если бы я просто действовал с дочерними элементами, которые были добавлены или удалены, вместо того, чтобы разбирать всю коллекцию и вставлять newСписок дочерних элементов, как будто они все новые.
РЕДАКТИРОВАТЬ. Извините, я написал ужасный заголовок...Мне следовало написать «наименьшее количество кода» вместо «эффективный».Я думаю, что это отбросило многие ответы, которые я получил.Они все великолепны...Спасибо!
Решение
Извините, я написал свой первый ответ до того, как увидел ваш последний абзац.
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);
}
Другие советы
originalList = newList;
Или, если вы предпочитаете, чтобы они были отдельными списками:
originalList = new List<int>(newList);
Но в любом случае вы делаете то, что хотите.По вашим правилам, после обновления originalList будет идентичен newList.
ОБНОВЛЯТЬ:Я благодарю всех вас за поддержку этого ответа, но после более внимательного прочтения вопроса я считаю, что другой мой ответ (ниже) является правильным.
Если вы используете некоторые методы расширения 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()));
Если вас не беспокоит конечный порядок, 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);
Моя первоначальная мысль заключалась в том, что вы могли бы вызвать 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);
}
}
Не очень чисто, но работает.
Для этого не существует встроенного способа. Самое близкое, что я могу придумать, — это способ, которым DataTable обрабатывает новые и удаленные элементы.
Что @Джеймс Карран предлагает просто заменить объект originalList объектом newList.Он удалит старый список, но сохранит переменную (т. е.указатель все еще там).
В любом случае, вам следует подумать, стоит ли тратить время на оптимизацию.Если большая часть времени выполнения тратится на копирование значений из одного списка в другой, возможно, оно того стоит.Если это не так, а вы делаете какую-то преждевременную оптимизацию, вам следует игнорировать это.
Потратьте время на доработку графического интерфейса или профилируйте приложение, прежде чем приступить к оптимизации, — это мой доллар 0,02 доллара.
Это распространенная проблема, с которой сталкиваются разработчики при написании пользовательских интерфейсов для поддержания связей базы данных «многие ко многим».Я не знаю, насколько это эффективно, но я написал вспомогательный класс для обработки этого сценария:
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);
}
}
если вы используете .Net 3.5
var List3 = List1.Intersect(List2);
Создает новый список, содержащий пересечение двух списков, и я полагаю, что именно это вы здесь и делаете.