Pytanie Jaki jest najlepszy sposób na iteracje w słowniku?


Widziałem kilka różnych sposobów iteracji nad słownikiem w języku C #. Czy istnieje standardowy sposób?


1949
2017-09-26 18:20


pochodzenie


Jestem zaskoczony, że istnieje wiele odpowiedzi na to pytanie, a także jeden wznowiony 923 razy. (Używa foreach ofcourse) .. Twierdzę, lub przynajmniej dodam, że jeśli musisz powtórzyć słownik, są szanse, że jesteś Używam go niepoprawnie / niewłaściwie ... Musiałem zrobić ten komentarz, ponieważ widziałem, że słowniki są nadużywane w sposób, który nie był odpowiedni dla IMHO ... Tak, mogą występować rzadkie okoliczności, gdy przeglądasz słownik, a nie wyszukiwanie, co jest jest przeznaczony dla .. Należy o tym pamiętać, przed zastanawianiem się, jak iterować po słowniku. - Vikas Gupta
@VikasGupta Co zasugerowałbyś, aby zrobić coś z kolekcją par klucz-wartość, gdy nie wiesz, co to za klucze? - nasch
@nasch: myDictionary.Keys da ci kolekcję zawierającą klucze myDictionary. - displayName
@displayName Jeśli chcesz coś zrobić z każdą parą klucz-wartość, ale nie masz odniesienia do kluczy używanych do wyszukiwania wartości, to możesz dokonać iteracji w słowniku, prawda? Po prostu wskazywałem, że może się zdarzyć, że chciałbyś to zrobić, pomimo twierdzenia Vikasa, że ​​jest to zwykle niewłaściwe użycie. - nasch
Stwierdzenie, że jest to nieprawidłowe użycie, oznacza, że ​​istnieje lepsza alternatywa. Jaka jest ta alternatywa? - Kyle Delaney


Odpowiedzi:


foreach(KeyValuePair<string, string> entry in myDictionary)
{
    // do something with entry.Value or entry.Key
}

2901
2017-09-26 18:22



Co się stanie, jeśli dokładnie nie znam typu klucza / wartości w Słowniku. Za pomocą var entry w tym przypadku jest lepsza, a więc głosowałem ta odpowiedź na drugi rzut zamiast powyższego. - Ozair Kafray
@OzairKafray za pomocą var kiedy nie wiesz, typ jest złą praktyką. - Nate
Ta odpowiedź jest lepsza, ponieważ Pablo nie domyślnie używał leniwego kodera "var", który zaciemnia typ powrotu. - MonkeyWrench
@MonkeyWrench: Meh. Visual Studio wie, czym jest typ; wszystko, co musisz zrobić, to najechać kursorem na zmienną, aby się dowiedzieć. - Robert Harvey♦
Tak jak rozumiem, var działa tylko wtedy, gdy typ jest znany podczas kompilacji. Jeśli program Visual Studio zna typ, to można go również znaleźć. - Kyle Delaney


Jeśli próbujesz użyć ogólnego słownika w języku C #, możesz użyć tablicy asocjacyjnej w innym języku:

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

Lub, jeśli potrzebujesz tylko iterować po zbiorze kluczy, użyj

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

I na koniec, jeśli interesują Cię tylko wartości:

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

(Zauważ, że var słowo kluczowe jest opcjonalną funkcją C # 3.0 i wyżej, możesz również użyć dokładnego typu swoich kluczy / wartości tutaj)


646
2017-09-26 18:22



funkcja var jest najbardziej wymagana dla twojego pierwszego bloku kodu :) - nawfal
Rozumiem, że ta odpowiedź wskazuje, że możesz wielokrotnie dokonywać iteracji kluczy lub wartości. - Rotsiser Mho
Nie podoba mi się użycie var tutaj. Biorąc pod uwagę, że jest to po prostu cukier syntaktyczny, po co go tu używać? Gdy ktoś próbuje odczytać kod, będzie musiał przeskoczyć wokół kodu, aby określić typ myDictionary (chyba, że ​​jest to nazwa rzeczywista). Myślę, że używanie var jest dobre, gdy typ jest oczywisty, np. var x = "some string" ale kiedy nie jest to od razu oczywiste, myślę, że to leniwe kodowanie, które rani czytelnika kodu / recenzenta - James Wierzba
var powinien być stosowany oszczędnie, moim zdaniem. Szczególnie tutaj nie jest to konstruktywne: typ KeyValuePair jest prawdopodobnie istotne dla pytania. - Sinjai


W niektórych przypadkach może być potrzebny licznik, który może być dostarczony przez implementację pętli for. W tym celu zapewnia LINQ ElementAt co umożliwia następujące czynności:

for (int index = 0; index < dictionary.Count; index++) {
  var item = dictionary.ElementAt(index);
  var itemKey = item.Key;
  var itemValue = item.Value;
}

107
2018-03-10 20:44



Aby użyć metody ".ElementAt", należy pamiętać: using System.Linq; To nie jest uwzględnione w fx. automatycznie generowane klasy testowe. - Tinia
Jest to sposób postępowania, jeśli modyfikuje się wartości skojarzone z kluczami. W przeciwnym razie zostanie zgłoszony wyjątek podczas modyfikowania i używania foreach (). - Mike de Klerk
Zachowaj ostrożność podczas korzystania z tego. Spójrz tutaj: stackoverflow.com/a/2254480/253938 - RenniePet
Nie jest ElementAt operacja O (n)? - Arturo Torres Sánchez
Ta odpowiedź zupełnie nie zasługuje na tak wiele przebojów. Słownik nie ma domyślnej kolejności, więc używa .ElementAt w tym kontekście może prowadzić do subtelnych błędów. Znacznie poważniejszy jest punkt Artura powyżej. Będziesz iterować słownik dictionary.Count + 1 razy prowadzące do złożoności O (n ^ 2) dla operacji, która powinna być tylko O ​​(n). Jeśli naprawdę potrzebujesz indeksu (jeśli tak, prawdopodobnie używasz niewłaściwego typu kolekcji w pierwszej kolejności), powinieneś iterować dictionary.Select( (kvp, idx) => new {Index = idx, kvp.Key, kvp.Value}) zamiast tego i nie używać .ElementAt wewnątrz pętli. - spender


Zależy od tego, czy szukasz kluczy, czy wartości ...

Z MSDN Dictionary(TKey, TValue) Opis klasy:

// 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);
}

75
2017-09-26 18:27





Generalnie, prośba o "najlepszy sposób" bez określonego kontekstu jest jak pytanie, jaki jest najlepszy kolor.

Jedna z jednej strony, jest wiele kolorów i nie ma najlepszego koloru. To zależy od potrzeby, a często także od gustu.

Z drugiej strony, istnieje wiele sposobów na iterację Słownika w języku C # i nie ma najlepszego sposobu. To zależy od potrzeby, a często także od gustu.

Najprostszy sposób

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

Jeśli potrzebujesz tylko wartość (pozwala na wywołanie go item, bardziej czytelny niż kvp.Value).

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

Jeśli potrzebujesz określonej kolejności sortowania

Ogólnie rzecz biorąc, początkujący są zaskoczeni kolejnością wyliczania słownika.

LINQ zapewnia zwięzłą składnię, która pozwala określić kolejność (i wiele innych rzeczy), np .:

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

Znowu możesz potrzebować tylko tej wartości. LINQ zapewnia także zwięzłe rozwiązanie:

  • iteruj bezpośrednio na wartość (pozwala to wywołać) item, bardziej czytelny niż kvp.Value)
  • ale posortowane według kluczy

Oto ona:

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

W tych przykładach można znaleźć znacznie więcej praktycznych przypadków na świecie. Jeśli nie potrzebujesz konkretnego zamówienia, po prostu trzymaj się "najprostszej drogi" (patrz wyżej)!


55
2017-08-10 11:15



Ten ostatni powinien być .Values a nie klauzulę wyboru. - Mafii
@Mafii Jesteś pewien? Wartości zwrócone przez OrderBy nie są typu KeyValuePair, nie mają wartości Value pole. Dokładny typ widzę tutaj IOrderedEnumerable<KeyValuePair<TKey, TValue>>. Być może chodzi ci o coś innego? Czy możesz napisać kompletny wiersz pokazujący, co masz na myśli (i przetestować)? - Stéphane Gourichon
Myślę, że ta odpowiedź zawiera co mam na myśli: stackoverflow.com/a/141105/5962841 ale popraw mnie, jeśli coś pomylę - Mafii
@Mafii Ponownie przeczytaj całą moją odpowiedź, wyjaśnienia między sekcjami kodu mówią o kontekście. Odpowiedź, o której wspomniałeś, jest w mojej odpowiedzi jak druga sekcja kodu (nie jest wymagane zamówienie). Tam właśnie napisałem items.Value jak sugerujesz. W przypadku czwartej sekcji, którą skomentowałeś, Select() jest sposobem, aby spowodować foreach wyliczyć bezpośrednio wartości w słowniku zamiast par klucz-wartość. Jeśli w jakiś sposób ci się nie podoba Select()w tym przypadku możesz preferować trzecią sekcję kodu. Punkt czwartej części to pokazanie, że można wstępnie przetworzyć kolekcję za pomocą LINQ. - Stéphane Gourichon
Jeśli zrobisz .Keys.Orderby() będziesz sprawdzać na liście kluczy. Jeśli to wszystko, czego potrzebujesz, dobrze. Jeśli potrzebujesz wartości, to w pętli musisz wysłać zapytanie do słownika na każdym klawiszu, aby uzyskać wartość. W wielu sytuacjach nie będzie to miało praktycznego znaczenia. W scenariuszu o wysokiej wydajności będzie. Tak jak napisałem na początku odpowiedzi: "jest wiele sposobów (...) i nie ma najlepszej drogi, zależy to od potrzeby, a często także od gustu". - Stéphane Gourichon


Powiedziałbym, że foreach to standardowy sposób, choć oczywiście zależy to od tego, czego szukasz

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

Czy tego właśnie szukasz?


36
2017-09-26 18:22



Um, czy nazwa przedmiotu "wartość" nie jest myląca? Zwykle używasz składni takiej jak "value.Key" i "value.Value", która nie jest zbyt intuicyjna dla każdego, kto będzie czytał kod, zwłaszcza jeśli nie są obeznani ze sposobem implementacji słownika .Net . - RenniePet
@RenniePet kvp jest powszechnie używany do nazywania instancji KeyValuePair podczas iteracji po słownikach i powiązanych strukturach danych: foreach(var kvp in myDictionary){.... - mbx


Możesz także wypróbować to na dużych słownikach do przetwarzania wielowątkowego.

dictionary
.AsParallel()
.ForAll(pair => 
{ 
    // Process pair.Key and pair.Value here
});

28
2018-06-11 13:32



@ WiiMaxx i ważniejsze, jeśli te elementy NIE zależą od siebie nawzajem - Mafii


Istnieje wiele opcji. Moim osobistym faworytem jest KeyValuePair

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

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

Możesz także użyć kolekcji Keys and Values


22
2017-09-26 18:22





Doceniam to pytanie, które już dawało wiele odpowiedzi, ale chciałem rzucić trochę badań.

Iterowanie przez słownik może być raczej powolne w porównaniu z iterowaniem nad czymś takim jak tablica. W moich testach iteracja na tablicy zajęła 0,015003 sekundy, podczas gdy iteracja na słowniku (z tą samą liczbą elementów) zajęła 0,0365073 sekundy, czyli 2,4 razy dłużej! Chociaż widziałem znacznie większe różnice. Dla porównania lista była gdzieś pomiędzy 0,00215043 sekund.

Jest to jednak podobne do porównywania jabłek i pomarańczy. Chodzi mi o to, że iteracja po słownikach jest powolna.

Słowniki są zoptymalizowane pod kątem wyszukiwania, więc mając to na uwadze, stworzyłem dwie metody. Po prostu robi foreach, drugi iteruje klucze, a następnie podnosi wzrok.

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

    return "Normal";
}

Ten ładuje klucze i zamiast nich wykonuje iteracje (próbowałem też przeciągnąć klawisze na sznurki [], ale różnica była znikoma.

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";
}

W tym przykładzie normalny test foreach wynosił 0,0310062, a wersja kluczy zajęła 0,2205441. Ładowanie wszystkich kluczy i powtarzanie wszystkich wyszukiwań jest zdecydowanie wolniejsze!

Do końcowego testu wykonałem swoją iterację dziesięć razy, aby sprawdzić, czy są jakieś korzyści z używania kluczy tutaj (w tym momencie byłem po prostu ciekawy):

Oto metoda RunTest, która pomaga w wizualizacji tego, co się dzieje.

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);
}

Tutaj normalny bieg foreach wynosił 0.2820564 sekundy (około dziesięć razy dłużej niż jedna iteracja trwała - jak można się spodziewać). Iteracja kluczy trwała 2.2249449 sekund.

Edytowane w celu dodania: Czytanie niektórych innych odpowiedzi spowodowało, że zastanawiałem się, co by się stało, gdybym użył Słownika zamiast Słownika. W tym przykładzie tablica zajęła 0.0120024 sekund, lista 0.0185037 sekund i słownik 0.0465093 sekundy. Rozsądnie jest oczekiwać, że typ danych ma wpływ na to, o ile wolniejszy jest słownik.

Jakie są moje wnioski?

  • Unikaj iteracji po słowniku, jeśli możesz, są znacznie wolniejsze niż iterowanie po tablicy z tymi samymi danymi.
  • Jeśli zdecydujesz się na iterację w słowniku, nie staraj się być zbyt sprytny, choć wolniej możesz zrobić o wiele gorzej niż przy użyciu standardowej metody foreach.

22
2017-07-30 10:54



Powinieneś zmierzyć coś w stylu StopWatch zamiast DateTime: hanselman.com/blog/... - Even Mien
Czy mógłbyś opisać swój scenariusz testowy, ile elementów w słowniku, jak często uruchamiałeś swój scenariusz, aby obliczyć średni czas ... - WiiMaxx
Co ciekawe, otrzymasz różne wyniki w zależności od danych, które masz w słowniku. Podczas iteracji nad Słownikiem funkcja Enumerator musi pominąć wiele pustych slotów w słowniku, co powoduje, że jest wolniejsza niż iteracja po tablicy. Jeśli słownik jest pełny, będzie mniej pustych miejsc do pominięcia, niż w połowie pustych. - Martin Brown


Zasugerowałeś poniżej, aby powtórzyć

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 nie działa, jeśli wartość jest typu obiektu.


9
2017-10-28 20:49



Proszę opracuj: foreach nie zadziała, jeśli który wartość jest typu object? W przeciwnym razie nie ma to większego sensu. - Marc L.