Я ищу способ быстрого удаления элементов из С# 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, sort, а затем RemoveRange
В этом тесте я зациклился на списке один раз и установил подлежащие удалению элементы в 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: установите значение null, а затем 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: установите значение null, а затем 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()
Примечания и комментарии
-
Первые два теста фактически не удаляют каждый пятый элемент из списка, потому что список переупорядочивается после каждого удаления. Фактически, из 500 000 предметов было удалено только 83 334 человека (должно быть 100 000). Я в порядке с этим - очевидно, что методы Remove()/RemoveAt() в любом случае не являются хорошей идеей.
-
Хотя я попытался удалить 5-й элемент из списка, на самом деле такого шаблона не будет. Записи, которые нужно удалить, будут случайными.
-
Хотя в этом примере я использовал
List<String>
, это не всегда так. Это может бытьList<Anything>
-
Не ставить элементы в списке, начиная с не.
-
Другие методы (3-6) выполнялись намного лучше, сравнительно, но я немного заинтересован - в 3, 5 и 6 я был вынужден установить значение
null
, а затем удалите все предметы в соответствии с этим стражем. Мне не нравится этот подход, потому что я могу представить себе сценарий, в котором один из элементов в списке может бытьnull
, и он будет удален из-за непреднамеренно.
Мой вопрос: какой лучший способ быстро удалить многие элементы из List<T>
? Большинство подходов, которые я пробовал, выглядят действительно уродливыми и потенциально опасными для меня. Является ли List
неправильной структурой данных?
Сейчас я склоняюсь к созданию нового списка и добавлению хороших элементов в новый список, но кажется, что должен быть лучший способ.