Подтвердить что ты не робот

Лучший способ перебрать список и удалить из него элементы?

Мне нужно выполнить итерацию по List<myObject> и удалить элементы, которые отвечают определенному условию.

Я увидел этот ответ (qaru.site/info/20956/...):

Итерируйте свой список в обратном порядке с помощью цикла for:

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}

Пример:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
      list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

Но я понял, что for менее эффективен, чем foreach,

Итак, я подумал о том, чтобы использовать следующее:

foreach (var item in myList.ToList())
{
    // if certain condition applies:
    myList.Remove(item)
}

Один метод лучше другого?

EDIT:

Я не хочу использовать RemoveAll(...), так как в цикле есть большое количество кода, до условия.

4b9b3361

Ответ 1

Виллиально, вам нужно перебирать список, а цикл for является наиболее эффективным:

  for (int i = safePendingList.Count - 1; i >= 0; --i) 
    if (condition)
      safePendingList.RemoveAt(i);

Если вы хотите удалить диапазон (не во всем списке), просто измените для цикла:

  // No Enumarable.Range(1, 10) - put them into "for"
  for (int i = Math.Min(11, safePendingList.Count - 1); i >= 1; --i)
    if (condition)
      safePendingList.RemoveAt(i); 

Или, если вам нужно удалить элементы в прямом цикле:

  for (int i = 0; i < safePendingList.Count;) // notice ++i abscence
    if (condition)
      safePendingList.RemoveAt(i);
    else
      i += 1; // ++i should be here

Наоборот, safePendingList.ToList() создает копию начального safePendingList, и это означает, что служебные данные памяти и процессора:

  // safePendingList.ToList() - CPU and Memory overhead (copying)
  foreach (var item in safePendingList.ToList()) {
    if (condition)
      myList.Remove(item); // Overhead: searching
  }

Однако наиболее разумный план во многих случаях заключается в том, чтобы позволить .Net работать для вас:

  safePendingList.RemoveAll(item => condition);

Ответ 2

чтобы удалить элементы с определенным условием, которое вы можете использовать

list.RemoveAll(item => item > 5);

вместо

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
        list.RemoveAt(i);
}

Ответ 3

Проблема заключается в том, что foreach не может работать с коллекцией при ее изменении, это приведет к InvalidOperationException.

Использование .ToList() внутри foreach позволяет нам избежать этого, но это копирует всю коллекцию, в результате которой получается циклический цикл в коллекции дважды (первый раз, когда он копирует все элементы и второй раз фильтрует его).

Цикл for - лучший вариант, потому что мы можем пройти коллекцию один раз. Помимо этого вы можете фильтровать коллекцию с помощью метода LINQ Where(), что-то вроде строк:

var myFilteredCollection = myList.Where(i => i <= 5).ToList();

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

private bool IsItemOk(myObject item)
{
   ... your complicated logic.
   return true;
}

И используйте его в своем запросе LINQ:

var myFilteredCollection = myList.Where(i => IsItemOk(i)).ToList();

Другой вариант - это наоборот. Создайте новую коллекцию и добавьте элементы на основе условия. Чем вы можете использовать цикл foreach:

var newList = new List<MyObject>(myList.Count);

foreach (var item in myList)
{
    // if certain condition does not apply:
    newList.Add(item);
}

P.S.

Но как уже упоминалось, .RemoveAll() лучше, потому что вам не нужно "изобретать колесо"

Ответ 4

Ни один из методов не показывает существенных различий. for и RemoveAll выглядит немного быстрее (более вероятно, потому что, используя Remove, вы дублируете свой список, и его нужно воссоздать... используя типы значений, это также означает удвоение необходимой памяти). Я сделал некоторые профилирования:

public static void MethodA()
{
    var list = new List<int>(Enumerable.Range(1, 10));
    for (int i = list.Count - 1; i >= 0; i--)
    {
        if (list[i] > 5)
            list.RemoveAt(i);
    }
}

public static void MethodB()
{
    var list = new List<int>(Enumerable.Range(1, 10));
    foreach (var item in list.ToList())
    {
        if (item > 5)
            list.Remove(item);
    }
}

public static void MethodC()
{
    var list = new List<int>(Enumerable.Range(1, 10));
    list.RemoveAll(x => x > 5);
}

public static void Measure(string meas, Action act)
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    var st = new Stopwatch();
    st.Start();
    for (var i = 0; i < 10000000; i++)
        act();
    st.Stop();
    Console.WriteLine($"{meas}: {st.Elapsed}");
}

static void Main(string[] args)
{
    Measure("A", MethodA);
    Measure("B", MethodB);
    Measure("C", MethodC);
}

Результат:

A: 00: 00: 04.1179163

B: 00: 00: 07.7417853

C: 00: 00: 04.3525255

Другие прогоны дают еще более похожие результаты (обратите внимание, что это для 10 миллионов итераций в списке из 10 пунктов [таким образом, выполняется 100 миллионов операций]... разница в ~ 3 секунды - это не то, что я назвал бы "значительным", но ваш пробег может меняться)

Итак, пойдите с тем, что, по вашему мнению, вы можете читать лучше (я лично использовал метод RemoveAll)

Примечание: по мере роста вашего списка (это всего 10 элементов) MethodB (foreach тот, который дублирует список) будет медленнее, а различия будут выше, поскольку дублирование списка будет более дорогостоящим. Например, делая список из 1000 элементов (при условии, что на 500 вместо 5) и 100 000 итераций, MethodB составляет 34 секунды, в то время как обе остальные работают на моей машине менее 2 секунд. Так что да, определенно не используйте MethodB: -)

Это не означает, что foreach менее эффективен, чем for, это означает, что для использования foreach для удаления элементов из списка вам необходимо дублировать указанный список, и это дорого.

Этот другой метод (с использованием foreach) должен быть примерно таким же быстрым, как методы for или RemoveAll (он немного медленнее в моем профилировании), потому что он выделяет новый список, я фигурирую, но довольно очень незначительно):

public static void MethodD()
{
    var originalList = new List<int>(Enumerable.Range(1, 1000));
    var list = new List<int>(originalList.Count);
    foreach (var item in originalList)
    {
        if (item <= 500)
            list.Add(item);
    }
    list.Capacity = list.Count;
}

Вам просто нужно отменить условие.

Я не предлагаю этот метод, просто доказывая, что foreach не обязательно медленнее, чем for

Ответ 5

Другим способом может быть выбор списка, установка элемента в значение null, а затем удаление linq в (item = > null), как уже говорилось, это зависит от вас, чтобы выбрать способ, который вам подходит лучше