Удаление/добавление элементов в/из списка при его повторном
Во-первых, я знаю, что это невозможно из-за очевидных причин.
foreach(string item in myListOfStrings) {
myListOfStrings.Remove(item);
}
Перевернутый выше - одна из самых ужасных вещей, которые я когда-либо видел. Итак, как вы это достигаете? Вы можете прокручивать список назад, используя for
, но мне тоже не нравится это решение.
Мне интересно: есть ли метод/расширения, который возвращает IEnumerable из текущего списка, что-то вроде плавающей копии? LINQ имеет множество методов расширения, которые делают именно это, но вам всегда нужно что-то делать с ним, например, с фильтрацией (где, принимать...).
Я с нетерпением жду чего-то вроде этого:
foreach(string item in myListOfStrings.Shadow()) {
myListOfStrings.Remove(item);
}
где as.Shadow():
public static IEnumerable<T> Shadow<T>(this IEnumerable<T> source) {
return new IEnumerable<T>(source);
// or return source.Copy()
// or return source.TakeAll();
}
Пример
foreach(ResponseFlags flag in responseFlagsList.Shadow()) {
switch(flag) {
case ResponseFlags.Case1:
...
case ResponseFlags.Case2:
...
}
...
this.InvokeSomeVoidEvent(flag)
responseFlagsList.Remove(flag);
}
Решение
Вот как я решил это, и он работает как шарм:
public static IEnumerable<T> Shadow<T>(this IEnumerable<T> source) where T: new() {
foreach(T item in source)
yield return item;
}
Это не очень быстро (очевидно), но это безопасно и точно, что я намеревался сделать.
Ответы
Ответ 1
Удаление нескольких элементов из списка 1 на 1 является анти-шаблоном С# из-за того, как реализованы списки.
Конечно, это можно сделать с помощью цикла for (вместо foreach). Или это можно сделать, сделав копию списка. Но вот почему это не должно быть сделано. В списке из 100000 случайных чисел это занимает 2500 мс на моей машине:
foreach (var x in listA.ToList())
if (x % 2 == 0)
listA.Remove(x);
и это занимает 1250 мс:
for (int i = 0; i < listA.Count; i++)
if (listA[i] % 2 == 0)
listA.RemoveAt(i--);
в то время как эти два принимают соответственно 5 и 2 мс:
listB = listB.Where(x => x % 2 != 0).ToList();
listB.RemoveAll(x => x % 2 == 0);
Это связано с тем, что когда вы удаляете элемент из списка, вы фактически удаляете его из массива, и это время O (N), так как вам нужно сдвинуть каждый элемент после удалённого элемента на одну позицию слева. В среднем это будет N/2 элемента.
Удалить (элемент) также необходимо найти элемент перед его удалением. Таким образом, Remove (element) на самом деле всегда будет выполнять N шагов - elementindex
, чтобы найти элемент, N - elementindex
шаги для его удаления - всего, N шагов.
RemoveAt (index) не нужно искать элемент, но он все равно должен смещать базовый массив, поэтому в среднем RemoveAt имеет N/2 шага.
Конечным результатом является сложность O (N ^ 2) в любом случае, поскольку вы удаляете до N элементов.
Вместо этого вы должны использовать Linq, который будет изменять весь список в O (N) раз, или катить свой собственный, но вы не должны использовать Remove (или RemoveAt) в цикле.
Ответ 2
Почему бы просто не сделать:
foreach(string item in myListOfStrings.ToList())
{
myListOfStrings.Remove(item);
}
Чтобы создать копию оригинала и использовать для итерации, затем удалите из существующего.
Если вам действительно нужен ваш метод расширения, вы могли бы создать что-то более читаемое пользователю, например:
public static IEnumerable<T> Shadow<T>(this IEnumerable<T> items)
{
if (items == null)
throw new NullReferenceException("Items cannot be null");
List<T> list = new List<T>();
foreach (var item in items)
{
list.Add(item);
}
return list;
}
Это по существу то же самое, что и .ToList()
.
Призвание:
foreach(string item in myListOfStrings.Shadow())
Ответ 3
Для этого вы не используете методы расширения LINQ - вы можете создать новый список явно, например:
foreach(string item in new List<string>(myListOfStrings)) {
myListOfStrings.Remove(item);
}
Ответ 4
Вам нужно создать копию исходного списка, итерации, как показано ниже:
var myListOfStrings = new List<string>();
myListOfStrings.Add("1");
myListOfStrings.Add("2");
myListOfStrings.Add("3");
myListOfStrings.Add("4");
myListOfStrings.Add("5");
foreach (string item in myListOfStrings.ToList())
{
myListOfStrings.Remove(item);
}
Ответ 5
В вашем примере удаляются все элементы из строки, что эквивалентно:
myListOfStrings.Clear();
Он также эквивалентен:
myListOfStrings.RemoveAll(x => true); // Empties myListOfStrings
Но то, что я думаю, что вы ищете, - это способ удалить элементы, для которых предикат является истинным, - это то, что делает RemoveAll()
.
Итак, вы можете написать, например:
myListOfStrings.RemoveAll(x => x == "TEST"); // Modifies myListOfStrings
Или использовать любой другой предикат.
Однако это изменяет список ORIGINAL; Если вам просто нужна копия списка с удаленными элементами, вы можете просто использовать обычный Linq:
// Note != instead of == as used in Removeall(),
// because the logic here is reversed.
var filteredList = myListOfStrings.Where(x => x != "TEST").ToList();
Ответ 6
Ответ на вопрос svinja Я считаю, что наиболее эффективным способом решения этой проблемы является:
for (int i = 0; i < listA.Count;) {
if (listA[i] % 2 == 0)
listA.RemoveAt(i);
else
i++;
}
Это улучшает ответ, удаляя ненужные суммы и вычитания.