Enumerable.ElementAt против foreach

У меня есть словарь, который мне нужно обновлять с помощью входящих данных, после анализа входящих данных я должен проверить, есть ли в словаре какие-либо записи, которых нет во входящих данных (входящие данные при анализе представляют собой список, и мне нужно чтобы сопоставить его со словарными статьями).

Чтобы избежать нескольких циклов для удаления записей, я запустил цикл с уменьшением for для подсчета словаря, затем я извлекаю ключ словаря индекса с помощью ElementAt, затем проверяю, присутствует ли запись во входящих данных, если нет, то я удаляю эту запись из список. Я сделал это, потому что запуск цикла foreach для ключей словаря и удаление из него приведет к возникновению исключения, поскольку коллекция ключей словаря будет изменена.

Я хотел понять, что это повлияет на время выполнения. Я хочу понять, каков порядок работы ElementAt.


person is_this_the_way    schedule 06.10.2010    source источник


Ответы (6)


ElementAt полезен, если вам нужно предоставить семантику индексирования и вы не можете гарантировать, что семантика индексирования будет доступна в базовом перечислении. Он использует индексирование O (1), когда перечисление, на которое действует, представляет собой IList<T> (включая список и массивы), но в противном случае это O (n) *, что позволяет использовать его в последовательности для всего, начиная с O (n) операция это будет со списком до O(n * n).

Однако, если у вас есть копия ключей с dict.Keys.ToList(), вы можете безопасно foreach пройти через это, так как она не будет изменена изменениями в вашем словаре.

Что не ясно, так это то, почему вы просто не замените старый словарь новым, который снова будет значительно быстрее (простое присваивание ссылки).

*Обновление: в версии linq для .NET Core существует более широкий диапазон случаев, когда ElementAt() равно O(1), например, результаты Select(), выполненные на IList<T>. Также OrderBy(…).ElementAt(…) теперь равно O(n), а не O(n log n), поскольку комбинированная последовательность превращается в быстрый выбор, а не в быструю сортировку с последующей итерацией.

person Jon Hanna    schedule 06.10.2010
comment
Спасибо! Я рефакторил уже существующий код, а затем запутался, упустив некоторые очевидные способы. - person is_this_the_way; 06.10.2010

Используйте трюк «пометить, а затем удалить» в качестве обходного пути для невозможности изменить коллекцию во время итерации.

var dict = new Dictionary<int, string>
{ 
    {3, "kuku" },
    {1, "zOl"}
};

var newKeys = new List<int> { 1, 2, 4 };

var toRemove = dict.Keys.Except(newKeys).ToList();

foreach (var k in toRemove)
    dict.Remove(k);
person Grozz    schedule 06.10.2010
comment
да, я делал это раньше, а затем перешел на ElementAt, я хотел узнать, как лучше всего это сделать для оптимальной производительности. Я профилировал и обнаружил, что это быстрее, чем ElementAt, поскольку Itay и danijels указали, что он использует счетчик, что занимает дополнительное время. - person is_this_the_way; 06.10.2010

ElementAt() действительно использует перечислитель, как указано, поэтому, если вам нужен самый быстрый доступ к индексу, вы должны использовать массив. Конечно, это происходит за счет фиксированной длины, но если размер массива не меняется постоянно, вполне возможно, что Array.Resize() может быть подходящим вариантом.

person danijels    schedule 06.10.2010
comment
Я могу это сделать, но у меня также есть задача сопоставления входящих данных, это означает, что мне нужно будет отсортировать массив, и снова я должен выставить его как коллекцию, поэтому для этого потребуется операция копирования, которая, я думаю, будет дорогой. Поправьте меня, если я ошибаюсь здесь. - person is_this_the_way; 06.10.2010
comment
Не знаю, зачем вам копия. Массив реализует как ICollection, так и IEnumerable, поэтому все должно быть в порядке. - person danijels; 06.10.2010

Except() кажется, что это сработает здесь:

Dictionary<int, string> dict = new Dictionary<int, string>
{ 
    {3, "kuku" },
    {1, "zOl"}
};

IEnumerable<int> data = new List<int> { 1, 2, 4 };

IEnumerable<int> toRemove = dict.Keys.Except(data);

foreach(var x in toRemove)
    dict.Remove(x);
person dahlbyk    schedule 06.10.2010
comment
Этот ответ направлен на улучшение ответа Grozz. Однако я считаю, что Except — это противоположность тому, что здесь нужно. Как написано, toRemove будет содержать {2, 4}, который представляет те ключи, не в dict. Вместо этого я считаю, что Intersect(dict.Keys) даст желаемый результат... то есть удаление ключей в dict, которые совпадают в data. - person DavidRR; 14.02.2014
comment
Вы правы в том, что я не прав, но только потому, что мои аргументы для Except перевернуты: проверьте, есть ли в словаре какие-либо записи, которые не присутствуют во входящих данных, предлагает мне удалить все ключи кроме содержащихся в data. - person dahlbyk; 20.02.2014
comment
Да, действительно, toRemove = dict.Keys.Except(data); хорошо читается. - person DavidRR; 20.02.2014

Я думаю, что ElementAt() использует перечислитель, чтобы добраться до нужного элемента.

Это будет так же, как:

object returnedElement = null;
int i = 0;
foreach (var obj in dictionary.Keys)
{
   if (i++ == at)
    {
      returnedElement = obj;
      break;
    }
}
person Itay Karo    schedule 06.10.2010
comment
Спасибо! Спасибо за ваш ответ, я обнаружил, что он использует Enumerator - person is_this_the_way; 06.10.2010

Вы можете получить Словарь совпадающих записей в цели (Словарь) и источнике (Список) следующим образом:

using System;
using System.Collections.Generic;
using System.Linq;

Dictionary<string, string> target = new Dictionary<string, string>();
List<string> source = new List<string>();
target.Add("a", "this is a");
target.Add("b", "this is b");
source.Add("a");
source.Add("c");

target = Enumerable.Select(target, n => n.Key).
    Where(n => source.Contains(n)).ToDictionary(n => n, k => target[k]);

Мне не ясно, хотите ли вы включать новые записи из списка в словарь - если да, то я не уверен, какими будут новые значения записей, если у вас есть только список новых входящих данных.

person Steve Townsend    schedule 06.10.2010