Я ищу способ быстро удалить элементы из C # List<T>
. В документации говорится, что операции List.Remove()
и List.RemoveAt()
являются O(n)
Это серьезно влияет на мое заявление.
Я написал несколько разных методов удаления и протестировал их на List<String>
с 500 000 элементов. Тестовые случаи показаны ниже ...
Обзор
Я написал метод, который генерировал бы список строк, который просто содержит строковые представления каждого числа («1», «2», «3», ...). Затем я попытался remove
каждый 5-й элемент в списке. Вот метод, использованный для генерации списка:
private List<String> GetList(int size)
{
List<String> myList = new List<String>();
for (int i = 0; i < size; i++)
myList.Add(i.ToString());
return myList;
}
Тест 1: RemoveAt ()
Вот тест, который я использовал для проверки метода RemoveAt()
.
private void RemoveTest1(ref List<String> list)
{
for (int i = 0; i < list.Count; i++)
if (i % 5 == 0)
list.RemoveAt(i);
}
Тест 2: Удалить ()
Вот тест, который я использовал для проверки метода Remove()
.
private void RemoveTest2(ref List<String> list)
{
List<int> itemsToRemove = new List<int>();
for (int i = 0; i < list.Count; i++)
if (i % 5 == 0)
list.Remove(list[i]);
}
Тест 3: установить в null, отсортировать, затем RemoveRange
В этом тесте я один раз просмотрел список и установил элементы, которые должны быть удалены, на null
. Затем я отсортировал список (так что null был бы вверху) и удалил все элементы сверху, которые были установлены в null.
ПРИМЕЧАНИЕ. Это изменило порядок в моем списке, поэтому мне, возможно, придется вернуть его в правильном порядке.
private void RemoveTest3(ref List<String> list)
{
int numToRemove = 0;
for (int i = 0; i < list.Count; i++)
{
if (i % 5 == 0)
{
list[i] = null;
numToRemove++;
}
}
list.Sort();
list.RemoveRange(0, numToRemove);
// Now they're out of order...
}
Тест 4: Создайте новый список и добавьте все «хорошие» значения в новый список
В этом тесте я создал новый список и добавил все свои объекты хранения в новый список. Затем я помещаю все эти предметы в исходный список.
private void RemoveTest4(ref List<String> list)
{
List<String> newList = new List<String>();
for (int i = 0; i < list.Count; i++)
{
if (i % 5 == 0)
continue;
else
newList.Add(list[i]);
}
list.RemoveRange(0, list.Count);
list.AddRange(newList);
}
Тест 5: установить в ноль, а затем FindAll ()
В этом тесте я установил все подлежащие удалению элементы на null
, затем использовал функцию FindAll()
, чтобы найти все элементы, которые не null
private void RemoveTest5(ref List<String> list)
{
for (int i = 0; i < list.Count; i++)
if (i % 5 == 0)
list[i] = null;
list = list.FindAll(x => x != null);
}
Тест 6: установить в ноль и затем RemoveAll ()
В этом тесте я установил все подлежащие удалению элементы на null
, затем использовал функцию RemoveAll()
, чтобы удалить все элементы, которые не null
private void RemoveTest6(ref List<String> list)
{
for (int i = 0; i < list.Count; i++)
if (i % 5 == 0)
list[i] = null;
list.RemoveAll(x => x == null);
}
Клиентское приложение и выходы
int numItems = 500000;
Stopwatch watch = new Stopwatch();
// List 1...
watch.Start();
List<String> list1 = GetList(numItems);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
watch.Reset(); watch.Start();
RemoveTest1(ref list1);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
Console.WriteLine();
// List 2...
watch.Start();
List<String> list2 = GetList(numItems);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
watch.Reset(); watch.Start();
RemoveTest2(ref list2);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
Console.WriteLine();
// List 3...
watch.Reset(); watch.Start();
List<String> list3 = GetList(numItems);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
watch.Reset(); watch.Start();
RemoveTest3(ref list3);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
Console.WriteLine();
// List 4...
watch.Reset(); watch.Start();
List<String> list4 = GetList(numItems);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
watch.Reset(); watch.Start();
RemoveTest4(ref list4);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
Console.WriteLine();
// List 5...
watch.Reset(); watch.Start();
List<String> list5 = GetList(numItems);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
watch.Reset(); watch.Start();
RemoveTest5(ref list5);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
Console.WriteLine();
// List 6...
watch.Reset(); watch.Start();
List<String> list6 = GetList(numItems);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
watch.Reset(); watch.Start();
RemoveTest6(ref list6);
watch.Stop(); Console.WriteLine(watch.Elapsed.ToString());
Console.WriteLine();
Результаты
00:00:00.1433089 // Create list
00:00:32.8031420 // RemoveAt()
00:00:32.9612512 // Forgot to reset stopwatch :(
00:04:40.3633045 // Remove()
00:00:00.2405003 // Create list
00:00:01.1054731 // Null, Sort(), RemoveRange()
00:00:00.1796988 // Create list
00:00:00.0166984 // Add good values to new list
00:00:00.2115022 // Create list
00:00:00.0194616 // FindAll()
00:00:00.3064646 // Create list
00:00:00.0167236 // RemoveAll()
Примечания и комментарии
Первые два теста фактически не удаляют каждый 5-й элемент из списка, поскольку список переупорядочивается после каждого удаления. Фактически, из 500 000 предметов, только 83 334 были удалены (должно было быть 100 000). Я согласен с этим - очевидно, что методы Remove () / RemoveAt () не очень хорошая идея.
Хотя я пытался удалить 5-й элемент из списка, в реальности такого шаблона не будет. Записи, которые будут удалены, будут случайными.
Хотя я использовал List<String>
в этом примере, это не всегда будет так. Это может быть List<Anything>
Не помещать элементы в список для начала это не вариант.
Все остальные методы (3 - 6) работали намного лучше, относительно , но меня это немного беспокоило - в 3, 5 и 6 я был вынужден установить значение в null
, а затем удалите все предметы в соответствии с этим стражем. Мне не нравится такой подход, потому что я могу представить сценарий, в котором один из элементов в списке может быть null
, и он будет удален непреднамеренно.
Мой вопрос: каков наилучший способ быстрого удаления многих предметов из List<T>
? Большинство подходов, которые я пробовал, выглядят очень уродливо и потенциально опасно для меня. Является ли List
неправильной структурой данных?
Сейчас я склоняюсь к созданию нового списка и добавлению хороших предметов в новый список, но, похоже, должен быть лучший способ.