Удаление/добавление элементов в/из списка при его повторном

Во-первых, я знаю, что это невозможно из-за очевидных причин.

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

Это улучшает ответ, удаляя ненужные суммы и вычитания.