Каков наилучший способ перебора словаря?

1594

Я видел несколько разных способов перебора словаря на С#. Есть ли стандартный способ?

  • 80
    Я удивлен, что есть много ответов на этот вопрос, наряду с одним проголосовавшим 923 раза .. (использует foreach ofcourse) .. Я бы поспорил или, по крайней мере, добавил, что если вам придется перебирать словарь, скорее всего, вы использовать его неправильно / ненадлежащим образом. Я должен был сделать этот комментарий, потому что я видел злоупотребления в словарях способами, которые ИМХО были неуместны ... Да, могут быть редкие обстоятельства, когда вы перебираете словарь, а не ищите, вот что это разработано для .. Пожалуйста, имейте это в виду, прежде чем задаться вопросом, как перебрать словарь.
  • 58
    @VikasGupta Что бы вы предложили сделать с набором пар ключ-значение, если вы не знаете, какими будут ключи?
Показать ещё 6 комментариев
Теги:
loops
dictionary

20 ответов

2420
Лучший ответ
foreach(KeyValuePair<string, string> entry in myDic)
{
    // do something with entry.Value or entry.Key
}
  • 16
    Что делать, если я точно не знаю тип ключа / значения в словаре. В этом случае лучше использовать var entry , и поэтому я проголосовал за этот ответ по второму взгляду вместо вышеупомянутого.
  • 70
    @OzairKafray использование var когда вы не знаете тип, обычно плохая практика.
Показать ещё 22 комментария
502

Если вы пытаетесь использовать общий словарь в С#, как если бы вы использовали ассоциативный массив на другом языке:

foreach(var item in myDictionary)
{
  foo(item.Key);
  bar(item.Value);
}

Или, если вам нужно только перебирать коллекцию ключей, используйте

foreach(var item in myDictionary.Keys)
{
  foo(item);
}

И, наконец, если вас интересуют только значения:

foreach(var item in myDictionary.Values)
{
  foo(item);
}

(Обратите внимание, что ключевое слово var является необязательной функцией С# 3.0 и выше, вы также можете использовать точный тип своих ключей/значений здесь)

  • 9
    функция var наиболее необходима для вашего первого блока кода :)
  • 17
    Я ценю, что этот ответ указывает на то, что вы можете перебирать ключи или значения явно.
Показать ещё 3 комментария
98

В некоторых случаях вам может понадобиться счетчик, который может быть предоставлен для реализации цикла. Для этого LINQ предоставляет ElementAt, который позволяет следующее:

for (int index = 0; index < dictionary.Count; index++) {
  var item = dictionary.ElementAt(index);
  var itemKey = item.Key;
  var itemValue = item.Value;
}
  • 17
    Чтобы использовать метод .ElementAt, помните: using System.Linq; Это не входит в FX. автоматически сгенерированные тестовые классы.
  • 12
    Это путь, если вы изменяете значения, связанные с ключами. В противном случае выдается исключение при изменении и использовании foreach ().
Показать ещё 9 комментариев
71

В зависимости от того, находитесь ли вы за клавишами или значениями...

Из MSDN Словарь < (Of < (TKey, TValue > ) > ) Описание класса:

// When you use foreach to enumerate dictionary elements,
// the elements are retrieved as KeyValuePair objects.
Console.WriteLine();
foreach( KeyValuePair<string, string> kvp in openWith )
{
    Console.WriteLine("Key = {0}, Value = {1}", 
        kvp.Key, kvp.Value);
}

// To get the values alone, use the Values property.
Dictionary<string, string>.ValueCollection valueColl =
    openWith.Values;

// The elements of the ValueCollection are strongly typed
// with the type that was specified for dictionary values.
Console.WriteLine();
foreach( string s in valueColl )
{
    Console.WriteLine("Value = {0}", s);
}

// To get the keys alone, use the Keys property.
Dictionary<string, string>.KeyCollection keyColl =
    openWith.Keys;

// The elements of the KeyCollection are strongly typed
// with the type that was specified for dictionary keys.
Console.WriteLine();
foreach( string s in keyColl )
{
    Console.WriteLine("Key = {0}", s);
}
50

Как правило, запрос "наилучшего пути" без конкретного контекста - это вопрос о том, какой лучший цвет.

С одной стороны, есть много цветов и нет лучшего цвета. Это зависит от необходимости и часто от вкуса.

С другой стороны, существует множество способов перебора словаря в С#, и нет лучшего способа. Это зависит от необходимости и часто от вкуса.

Самый простой способ

foreach (var kvp in items)
{
    // key is kvp.Key
    doStuff(kvp.Value)
}

Если вам нужно только значение (позволяет называть его item, более читаемым, чем kvp.Value).

foreach (var item in items.Values)
{
    doStuff(item)
}

Если вам нужен определенный порядок сортировки

Как правило, новички удивляются порядку перечисления Словаря.

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

foreach (var kvp in items.OrderBy(kvp => kvp.Key))
{
    // key is kvp.Key
    doStuff(kvp.Value)
}

Снова вам может понадобиться только значение. LINQ также обеспечивает краткое решение:

  • итерация непосредственно на значение (позволяет называть его item, более читаемым, чем kvp.Value)
  • но отсортированы по клавишам

Вот он:

foreach (var item in items.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value))
{
    doStuff(item)
}

В этих примерах есть много примеров использования в реальном мире. Если вам не нужен конкретный заказ, просто придерживайтесь "самого простого пути" (см. Выше)!

  • 0
    Последний должен быть .Values а не предложение select.
  • 0
    @Mafii Ты уверен? Значения, возвращаемые OrderBy, не относятся к типу KeyValuePair, у них нет поля Value . Точный тип, который я вижу здесь, это IOrderedEnumerable<KeyValuePair<TKey, TValue>> . Возможно, вы имели в виду что-то еще? Можете ли вы написать полную строку, показывающую, что вы имеете в виду (и проверить это)?
Показать ещё 7 комментариев
31

Я бы сказал, что foreach - стандартный способ, хотя он, очевидно, зависит от того, что вы ищете

foreach(var value in my_dictionary) {
  ...
}

Это то, что вы ищете?

  • 38
    Хм, не смущает ли название предмета "ценность"? Обычно вы используете синтаксис «value.Key» и «value.Value», который не очень понятен для тех, кто будет читать код, особенно если он не знаком с реализацией словаря .Net. ,
  • 1
    @RenniePet kvp обычно используется для именования экземпляров foreach(var kvp in myDictionary){... словарей и связанных структур данных: foreach(var kvp in myDictionary){...
22

Есть много вариантов. Мой личный фаворит - KeyValuePair

Dictionary<string,object> myDictionary = new Dictionary<string,object>();
//Populate your dictionary here

Foreach (KeyValuePair<string,object> kvp in myDictionary)
{
     //Do some interesting things;
}

Вы также можете использовать Коллекции клавиш и ценностей

19

Вы также можете попробовать это на больших словарях для многопоточной обработки.

dictionary
.AsParallel()
.ForAll(pair => 
{ 
    // Process pair.Key and pair.Value here
});
  • 6
    @WiiMaxx и более важно, если эти элементы НЕ зависят друг от друга
17

Я понимаю, что у этого вопроса уже было много ответов, но я хотел сделать небольшое исследование.

Итерация по словарю может быть довольно медленной по сравнению с итерацией над чем-то вроде массива. В моих тестах итерация по массиву заняла 0.015003 секунды, тогда как итерация по словарю (с таким же количеством элементов) заняла 0.0365073 секунды, что в 2,4 раза длиннее! Хотя я видел гораздо большие различия. Для сравнения, List был где-то посередине между 0.00215043 секундами.

Однако это похоже на сравнение яблок и апельсинов. Я хочу сказать, что итерация по словарям медленная.

Словари оптимизированы для поиска, поэтому, имея в виду, я создал два метода. Один просто делает foreach, другой выполняет итерации ключей, затем смотрит вверх.

    public static string Normal(Dictionary<string, string> dictionary)
    {
        string value;
        int count = 0;
        foreach (var kvp in dictionary)
        {
            value = kvp.Value;
            count++;
        }

        return "Normal";
    }

Этот загружает ключи и повторяет их вместо них (я также пытался потянуть ключи в строку [], но разница была незначительной.

    public static string Keys(Dictionary<string, string> dictionary)
    {
        string value;
        int count = 0;
        foreach (var key in dictionary.Keys)
        {
            value = dictionary[key];
            count++;
        }

        return "Keys";
    }

В этом примере обычный тест foreach занял 0.0310062, а версия ключей заняла 0.2205441. Загрузка всех ключей и повторение всех поисков явно медленнее!

Для окончательного теста я выполнил свою итерацию десять раз, чтобы узнать, есть ли какие-либо преимущества при использовании ключей здесь (к этому моменту мне было просто любопытно):

Здесь используется метод RunTest, если это поможет вам понять, что происходит.

    private static string RunTest<T>(T dictionary, Func<T, string> function)
    {            
        DateTime start = DateTime.Now;
        string name = null;
        for (int i = 0; i < 10; i++)
        {
            name = function(dictionary);
        }
        DateTime end = DateTime.Now;
        var duration = end.Subtract(start);
        return string.Format("{0} took {1} seconds", name, duration.TotalSeconds);
    }

Здесь обычный прогон foreach занял 0.2820564 секунды (примерно в десять раз дольше, чем одна итерация, как и следовало ожидать). Итерация по клавишам заняла 2.2249449 секунд.

Отредактировано для добавления: Прочитав некоторые другие ответы, я задал вопрос, что произойдет, если бы я использовал словарь вместо словаря. В этом примере массив занял 0.0120024 секунды, список 0.0185037 секунд и словарь 0.0465093 секунды. Разумно ожидать, что тип данных повлияет на то, насколько медленнее словарь.

Каковы мои выводы?

  • Избегайте повторения по словарю, если это возможно, они существенно медленнее, чем итерация по массиву с теми же данными в нем.
  • Если вы предпочитаете перебирать словарь, не пытайтесь быть слишком умным, хотя медленнее вы можете сделать намного хуже, чем использовать стандартный метод foreach.
  • 9
    Вы должны измерять что-то вроде StopWatch вместо DateTime: hanselman.com/blog/…
  • 2
    Не могли бы вы описать ваш тестовый сценарий, сколько элементов в вашем словаре, как часто вы запускали свой сценарий для расчета среднего времени, ...
Показать ещё 1 комментарий
9

Вы предложили выполнить итерацию

Dictionary<string,object> myDictionary = new Dictionary<string,object>();
//Populate your dictionary here

foreach (KeyValuePair<string,object> kvp in myDictionary) {
    //Do some interesting things;
}

FYI, foreach не работает, если значение имеет объект типа.

  • 3
    Пожалуйста, уточните: foreach не будет работать, если какое значение имеет тип object ? В противном случае это не имеет особого смысла.
8

Простейшая форма для итерации словаря:

foreach(var item in myDictionary)
{ 
    Console.WriteLine(item.Key);
    Console.WriteLine(item.Value);
}
6

Иногда, если вам нужны только значения, которые нужно перечислить, используйте набор значений словаря:

foreach(var value in dictionary.Values)
{
    // do something with entry.Value only
}

Сообщается этим сообщением, в котором говорится, что это самый быстрый метод: http://alexpinsker.blogspot.hk/2010/02/c-fastest-way-to-iterate-over.html

  • 0
    +1 для повышения производительности. Действительно, итерация по самому словарю включает некоторые издержки, если все, что вам нужно, это значения. Это происходит главным образом из-за копирования значений или ссылок в структуры KeyValuePair.
  • 1
    К вашему сведению, последний тест в этой ссылке - это проверка не того: он измеряет итерацию по ключам, игнорируя значения, в то время как предыдущие два теста используют это значение.
5

Я нашел этот метод в документации для класса DictionaryBase в MSDN:

foreach (DictionaryEntry de in myDictionary)
{
     //Do some stuff with de.Value or de.Key
}

Это был единственный, с которым я смог нормально функционировать в классе, унаследованном от DictionaryBase.

  • 3
    Это похоже на использование не словарной версии словаря ... т.е. до .NET Framework 2.0.
  • 0
    @ joed0tnot: это не универсальная версия, используемая для объектов Hashtable
3

Стандартный способ перебора словаря в соответствии с официальной документацией на MSDN:

foreach (DictionaryEntry entry in myDictionary)
{
     //Read entry.Key and entry.Value here
}
  • 0
    Это не работает для словарей, реализующих дженерики ...
3

Я воспользуюсь преимуществом .NET 4.0+ и предоставил обновленный ответ на первоначально принятый вариант:

foreach(var entry in MyDic)
{
    // do something with entry.Value or entry.Key
}
2

Если, скажем, вы хотите перебирать коллекцию значений по умолчанию, я считаю, что вы можете реализовать IEnumerable < > , где T - тип объекта значений в словаре, а "this" - словарь.

public new IEnumerator<T> GetEnumerator()
{
   return this.Values.GetEnumerator();
}
  • 0
    Это не объясняет многое о том, как повторять ...
1

Просто хотел добавить мой 2 цента, так как большинство ответов относятся к foreach-loop. Пожалуйста, взгляните на следующий код:

Dictionary<String, Double> myProductPrices = new Dictionary<String, Double>();

//Add some entries to the dictionary

myProductPrices.ToList().ForEach(kvP => 
{
    kvP.Value *= 1.15;
    Console.Writeline(String.Format("Product '{0}' has a new price: {1} $", kvp.Key, kvP.Value));
});

Это добавило дополнительный вызов '.ToList()', возможно, будет небольшое улучшение производительности (как указано здесь foreach vs someList.Foreach() {}), espacially при работе с большими словарями и параллельном запуске не является параметром/не будет иметь никакого эффекта вообще.

Также обратите внимание, что вы не сможете присвоить значения свойству "Значение" внутри цикла foreach. С другой стороны, вы также сможете манипулировать "ключом", возможно, вы столкнетесь с трудностями во время выполнения.

Когда вы просто хотите "читать" клавиши и значения, вы также можете использовать IEnumerable.Select().

var newProductPrices = myProductPrices.Select(kvp => new { Name = kvp.Key, Price = kvp.Value * 1.15 } );
  • 5
    Копирование всей коллекции без всякой причины не улучшит производительность. Это значительно замедлит код, а также удвоит объем памяти, занимаемой кодом, который практически не потребляет дополнительной памяти.
  • 0
    Я избегаю метода foreach побочными эффектами: foreach видимость побочных эффектов там, где они есть.
0

С .NET Framework 4.7 можно использовать разложение

var fruits = new Dictionary<string, int>();
...
foreach (var (fruit, number) in fruits)
{
    Console.WriteLine(fruit + ": " + number);
}

Чтобы этот код работал в более низких версиях С#, добавьте System.ValueTuple NuGet package и напишите где-нибудь

public static class MyExtensions
{
    public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> tuple,
        out T1 key, out T2 value)
    {
        key = tuple.Key;
        value = tuple.Value;
    }
}
  • 6
    Это неверно В .NET 4.7 просто встроен ValueTuple . Он доступен как пакет nuget для более ранних версий. Что еще более важно, C # 7.0+ необходим, чтобы метод Deconstruct работал в качестве деконструктора для var (fruit, number) in fruits .
0

Не стандартный способ, но работающий:

Dictionary<string, int> dict = new Dictionary<string, int>
{
    {"Foo", 1 },
    {"Bar", 2 }
};

dict.Select(kvp => { Console.WriteLine(kvp.Key + " - " + kvp.Value.ToString()); return kvp; }).Count();
0
var dictionary = new Dictionary<string, int>
{
    { "Key", 12 }
};

var aggregateObjectCollection = dictionary.Select(
    entry => new AggregateObject(entry.Key, entry.Value));
  • 2
    Там должно быть больше обоснования / описания в этом ответе. Что AggregateObject добавляет в KeyValuePair ? Где находится «итерация», как было указано в вопросе?
  • 0
    Select перебирает словарь и позволяет нам работать над каждым объектом. Он не такой общий, как foreach , но я часто его использовал. Действительно ли мой ответ заслуживает отрицательного ответа?
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню