Widziałem kilka różnych sposobów iteracji słownika w języku C #. Czy istnieje standardowy sposób?
Widziałem kilka różnych sposobów iteracji słownika w języku C #. Czy istnieje standardowy sposób?
Odpowiedzi:
foreach(KeyValuePair<string, string> entry in myDictionary)
{
// do something with entry.Value or entry.Key
}
var entry
W takim przypadku użycie jest lepsze, dlatego głosowałem na tę odpowiedź po raz drugi zamiast powyższego.
var
gdy nie wiesz, że typ jest ogólnie złą praktyką.
var
działa tylko wtedy, gdy typ jest znany w czasie kompilacji. Jeśli program Visual Studio zna ten typ, możesz go również dowiedzieć.
Jeśli próbujesz użyć ogólnego słownika w języku C #, tak jakbyś użył tablicy asocjacyjnej w innym języku:
foreach(var item in myDictionary)
{
foo(item.Key);
bar(item.Value);
}
Lub, jeśli potrzebujesz tylko iteracji po kolekcji 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);
}
(Pamiętaj, że var
słowo kluczowe jest opcjonalną funkcją C # 3.0 i nowszą, tutaj możesz również użyć dokładnego rodzaju swoich kluczy / wartości)
myDictionary
(chyba że jest to oczywiście nazwa). Myślę, że używanie var jest dobre, gdy typ jest oczywisty, np. var x = "some string"
Ale gdy nie jest to od razu oczywiste, myślę, że to leniwe kodowanie, które boli czytnika / recenzenta kodu
var
moim zdaniem powinien być stosowany oszczędnie. Szczególnie w tym przypadku nie jest konstruktywny: typ KeyValuePair
jest prawdopodobnie istotny dla pytania.
var
ma wyjątkowy cel i nie sądzę, że jest to cukier „syntaktyczny”. Używanie go celowo jest właściwym podejściem.
W niektórych przypadkach może być potrzebny licznik, który może być zapewniony przez implementację pętli For. W tym celu LINQ zapewnia, ElementAt
co umożliwia:
for (int index = 0; index < dictionary.Count; index++) {
var item = dictionary.ElementAt(index);
var itemKey = item.Key;
var itemValue = item.Value;
}
ElementAt
jest operacja O (n)?
.ElementAt
w tym kontekście może prowadzić do subtelnych błędów. O wiele poważniejszy jest punkt Arturo powyżej. Będziesz iterował dictionary.Count + 1
czasy słownikowe 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, to prawdopodobnie używasz niewłaściwego typu kolekcji), powinieneś iterować dictionary.Select( (kvp, idx) => new {Index = idx, kvp.Key, kvp.Value})
i nie używać .ElementAt
wewnątrz pętli.
Zależy, czy szukasz kluczy, czy wartości ...
Z Dictionary(TKey, TValue)
opisu klasy MSDN :
// 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);
}
Ogólnie rzecz biorąc, pytanie o „najlepszy sposób” bez określonego kontekstu jest jak pytanie o najlepszy kolor ?
Z jednej strony istnieje wiele kolorów i nie ma najlepszego koloru. To zależy od potrzeby, a często także od smaku.
Z drugiej strony istnieje wiele sposobów na iterację w Słowniku w języku C # i nie ma najlepszego sposobu. To zależy od potrzeby, a często także od smaku.
foreach (var kvp in items)
{
// key is kvp.Key
doStuff(kvp.Value)
}
Jeśli potrzebujesz tylko wartości (pozwala to nazwać item
, bardziej czytelna niż kvp.Value
).
foreach (var item in items.Values)
{
doStuff(item)
}
Zasadniczo 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)
}
Ponownie możesz potrzebować tylko tej wartości. LINQ zapewnia również zwięzłe rozwiązanie dla:
item
, bardziej czytelną niżkvp.Value
)Oto on:
foreach (var item in items.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value))
{
doStuff(item)
}
Z tych przykładów można zrobić wiele innych rzeczywistych przypadków użycia. Jeśli nie potrzebujesz konkretnego zamówienia, po prostu trzymaj się „najprostszej drogi” (patrz wyżej)!
.Values
a nie klauzulą select.
Value
pola. Dokładny typ, który tu widzę, to IOrderedEnumerable<KeyValuePair<TKey, TValue>>
. Może miałeś na myśli coś innego? Czy potrafisz napisać pełną linię, która ma na myśli (i przetestować)?
items.Value
jak zasugerowałeś. W przypadku czwartej sekcji, którą skomentowałeś, Select()
jest to sposób foreach
na wyliczenie bezpośrednio wartości w słowniku zamiast par klucz-wartość. Jeśli jakoś ci się nie podoba Select()
w tym przypadku, możesz wybrać trzecią sekcję kodu. Celem czwartej sekcji jest pokazanie, że można wstępnie przetworzyć kolekcję za pomocą LINQ.
.Keys.Orderby()
, przejdziesz do listy kluczy. Jeśli to wszystko, czego potrzebujesz, w porządku. Jeśli potrzebujesz wartości, w pętli będziesz musiał sprawdzić słownik każdego klucza, aby uzyskać wartość. W wielu scenariuszach nie będzie to miało praktycznej różnicy. W scenariuszu o wysokiej wydajności tak będzie. Tak jak napisałem na początku odpowiedzi: „istnieje wiele sposobów (...) i nie ma najlepszego sposobu. Zależy to od potrzeby, a często także od smaku”.
Powiedziałbym, że foreach jest standardowym sposobem, choć oczywiście zależy od tego, czego szukasz
foreach(var kvp in my_dictionary) {
...
}
Czy tego szukasz?
kvp
jest powszechnie stosowany do nazwy instancji KeyValuePair podczas iteracji nad słownikami i związanych z nimi struktur danych: foreach(var kvp in myDictionary){...
.
W wersji C # 7.0 wprowadzono dekonstruktory, a jeśli używasz aplikacji .NET Core 2.0+ , strukturaKeyValuePair<>
już zawieraDeconstruct()
dla ciebie. Możesz więc zrobić:
var dic = new Dictionary<int, string>() { { 1, "One" }, { 2, "Two" }, { 3, "Three" } };
foreach (var (key, value) in dic) {
Console.WriteLine($"Item [{key}] = {value}");
}
//Or
foreach (var (_, value) in dic) {
Console.WriteLine($"Item [NO_ID] = {value}");
}
//Or
foreach ((int key, string value) in dic) {
Console.WriteLine($"Item [{key}] = {value}");
}
foreach (var (key, value) in dic.Select(x => (x.Key, x.Value)))
Rozumiem, że na to pytanie udzielono już wielu odpowiedzi, ale chciałem przeprowadzić trochę badań.
Iterowanie po słowniku może być dość powolne w porównaniu z iteracją po czymś takim jak tablica. W moich testach iteracja na tablicy zajęła 0,015003 sekundy, podczas gdy iteracja na słowniku (z taką 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.
To jednak jak porównywanie jabłek i pomarańczy. Chodzi mi o to, że iteracja po słownikach jest powolna.
Słowniki są zoptymalizowane do wyszukiwania, więc mając to na uwadze, stworzyłem dwie metody. Jeden po prostu robi foreach, drugi iteruje klucze, a potem 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 tego iteruje je (próbowałem też wciągnąć klucze do łańcucha [], 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 trwał 0,0310062, a wersja kluczy - 0,2205441. Ładowanie wszystkich kluczy i powtarzanie wszystkich wyszukiwań jest zdecydowanie DUŻO wolniejsze!
W ostatnim teście wykonałem iterację dziesięć razy, aby sprawdzić, czy korzystanie z klawiszy jest w tym przypadku korzystne (w tym momencie byłem po prostu ciekawy):
Oto metoda RunTest, jeśli pomaga to zobrazować, 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 przebieg Foreach trwał 0,2820564 sekund (około dziesięć razy dłużej niż pojedyncza iteracja - jak można się spodziewać). Iteracja klawiszy zajęła 2.2249449 sekund.
Edytowane w celu dodania: przeczytanie niektórych innych odpowiedzi skłoniło mnie do pytania, co by się stało, gdybym użył Słownika zamiast Słownika. W tym przykładzie tablica zajęła 0,0120024 sekundy, lista 0,0185037 sekund i słownik 0,0465093 sekundy. Można oczekiwać, że typ danych ma wpływ na to, o ile wolniej działa słownik.
Jakie są moje wnioski ?
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 kluczy i wartości
Z .NET Framework 4.7
jednym można użyć rozkładu
var fruits = new Dictionary<string, int>();
...
foreach (var (fruit, number) in fruits)
{
Console.WriteLine(fruit + ": " + number);
}
Aby ten kod działał w niższych wersjach C #, dodaj System.ValueTuple NuGet package
i napisz gdzieś
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;
}
}
ValueTuple
wbudowane. Jest dostępny jako pakiet nuget dla wcześniejszych wersji. Co ważniejsze, C # 7.0+ jest potrzebny, aby Deconstruct
metoda działała jako dekoder var (fruit, number) in fruits
.
Począwszy od C # 7, można dekonstruować obiekty na zmienne. Uważam, że jest to najlepszy sposób na iterację słownika.
Przykład:
Utwórz metodę rozszerzenia, KeyValuePair<TKey, TVal>
która go dekonstruuje:
public static void Deconstruct<TKey, TVal>(this KeyValuePair<TKey, TVal> pair, out TKey key, out TVal value)
{
key = pair.Key;
value = pair.Value;
}
Powtarzaj dowolne Dictionary<TKey, TVal>
w następujący sposób
// Dictionary can be of any types, just using 'int' and 'string' as examples.
Dictionary<int, string> dict = new Dictionary<int, string>();
// Deconstructor gets called here.
foreach (var (key, value) in dict)
{
Console.WriteLine($"{key} : {value}");
}
Sugerujesz poniżej, aby iterować
Dictionary<string,object> myDictionary = new Dictionary<string,object>();
//Populate your dictionary here
foreach (KeyValuePair<string,object> kvp in myDictionary) {
//Do some interesting things;
}
Do Twojej wiadomości, foreach
nie działa, jeśli wartość jest typu obiekt.
foreach
nie będzie działać, jeśli która wartość jest typu object
? W przeciwnym razie nie ma to większego sensu.
Korzystając z C # 7 , dodaj tę metodę rozszerzenia do dowolnego projektu swojego rozwiązania:
public static class IDictionaryExtensions
{
public static IEnumerable<(TKey, TValue)> Tuples<TKey, TValue>(
this IDictionary<TKey, TValue> dict)
{
foreach (KeyValuePair<TKey, TValue> kvp in dict)
yield return (kvp.Key, kvp.Value);
}
}
I użyj tej prostej składni
foreach (var(id, value) in dict.Tuples())
{
// your code using 'id' and 'value'
}
Lub ten, jeśli wolisz
foreach ((string id, object value) in dict.Tuples())
{
// your code using 'id' and 'value'
}
W miejsce tradycyjnego
foreach (KeyValuePair<string, object> kvp in dict)
{
string id = kvp.Key;
object value = kvp.Value;
// your code using 'id' and 'value'
}
Metoda rozszerzenia przekształca KeyValuePair
twoją IDictionary<TKey, TValue>
w silnie typowanątuple
, umożliwiając ci użycie tej nowej wygodnej składni.
Konwertuje -just- wymagane pozycje słownika na tuples
, więc NIE konwertuje całego słownika natuples
, więc nie ma z tym żadnych problemów związanych z wydajnością.
Istnieje tylko niewielki koszt wywołania metody rozszerzenia dla utworzenia tuple
w porównaniu z KeyValuePair
bezpośrednim użyciem , co NIE powinno stanowić problemu, jeśli przypisujesz KeyValuePair
właściwości Key
iValue
nowe zmienne pętli.
W praktyce ta nowa składnia bardzo dobrze pasuje do większości przypadków, z wyjątkiem scenariuszy o bardzo wysokiej wydajności na niskim poziomie, w których nadal możesz po prostu nie używać jej w tym konkretnym miejscu.
Sprawdź to: Blog MSDN - Nowe funkcje w C # 7
kvp.Key
i kvp.Value
do używania odpowiednio klucza i wartości. Dzięki krotkom możesz dowolnie nazywać klucz i wartość, bez potrzeby używania dalszych deklaracji zmiennych w bloku foreach. Na przykład możesz nazwać swój klucz jako factoryName
, a wartość jako models
, co jest szczególnie przydatne, gdy otrzymujesz zagnieżdżone pętle (słowniki słowników): obsługa kodu staje się znacznie łatwiejsza. Po prostu daj temu szansę! ;-)
Wiem, że to bardzo stare pytanie, ale stworzyłem kilka metod rozszerzenia, które mogą być przydatne:
public static void ForEach<T, U>(this Dictionary<T, U> d, Action<KeyValuePair<T, U>> a)
{
foreach (KeyValuePair<T, U> p in d) { a(p); }
}
public static void ForEach<T, U>(this Dictionary<T, U>.KeyCollection k, Action<T> a)
{
foreach (T t in k) { a(t); }
}
public static void ForEach<T, U>(this Dictionary<T, U>.ValueCollection v, Action<U> a)
{
foreach (U u in v) { a(u); }
}
W ten sposób mogę napisać taki kod:
myDictionary.ForEach(pair => Console.Write($"key: {pair.Key}, value: {pair.Value}"));
myDictionary.Keys.ForEach(key => Console.Write(key););
myDictionary.Values.ForEach(value => Console.Write(value););
Czasami, jeśli potrzebujesz tylko wyliczyć wartości, skorzystaj z kolekcji wartości słownika:
foreach(var value in dictionary.Values)
{
// do something with entry.Value only
}
Zgłoszone przez ten post, który stwierdza, że jest to najszybsza metoda: http://alexpinsker.blogspot.hk/2010/02/c-fastest-way-to-iterate-over.html
Znalazłem tę metodę w dokumentacji klasy DictionaryBase na MSDN:
foreach (DictionaryEntry de in myDictionary)
{
//Do some stuff with de.Value or de.Key
}
To był jedyny, w którym mogłem poprawnie funkcjonować w klasie odziedziczonej ze DictionaryBase.
Hashtable
ContainsKey()
w for
wersji? To dodaje dodatkowy narzut, którego nie ma w kodzie, z którym porównujesz. TryGetValue()
istnieje, aby zastąpić dokładny wzór „jeśli klucz istnieje, zdobądź przedmiot z kluczem”. Ponadto, jeśli dict
zawiera ciągły zakres liczb całkowitych od 0
do dictCount - 1
, wiesz, że indeksator nie może zawieść; w przeciwnym dict.Keys
razie powinieneś powtarzać. Tak czy inaczej, nie ContainsKey()
/ TryGetValue()
potrzebne. Na koniec nie publikuj zrzutów ekranu kodu.
Napisałem rozszerzenie do pętli w słowniku.
public static class DictionaryExtension
{
public static void ForEach<T1, T2>(this Dictionary<T1, T2> dictionary, Action<T1, T2> action) {
foreach(KeyValuePair<T1, T2> keyValue in dictionary) {
action(keyValue.Key, keyValue.Value);
}
}
}
Następnie możesz zadzwonić
myDictionary.ForEach((x,y) => Console.WriteLine(x + " - " + y));
ForEach
metodę, w której masz foreach (...) { }
... Wydaje się niepotrzebny.
Jeśli powiedzmy, że chcesz domyślnie iterować kolekcję wartości, uważam, że możesz zaimplementować IEnumerable <>, gdzie T to typ obiektu wartości w słowniku, a „this” to Dictionary.
public new IEnumerator<T> GetEnumerator()
{
return this.Values.GetEnumerator();
}
Chciałem tylko dodać moje 2 centy, ponieważ większość odpowiedzi dotyczy pętli foreach. Proszę spojrzeć na następujący kod:
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));
});
Mimo że dodaje to dodatkowe wywołanie funkcji „.ToList ()”, może wystąpić nieznaczna poprawa wydajności (jak wskazano tutaj foreach vs someList.Foreach () {} ), szczególnie w przypadku pracy z dużymi słownikami i równoległego działania nie opcja / nie przyniesie żadnego efektu.
Należy również pamiętać, że nie będzie można przypisać wartości do właściwości „Wartość” w pętli foreach. Z drugiej strony będziesz mógł również manipulować „kluczem”, co może spowodować kłopoty w czasie wykonywania.
Gdy chcesz po prostu „odczytać” klucze i wartości, możesz także użyć IEnumerable.Select ().
var newProductPrices = myProductPrices.Select(kvp => new { Name = kvp.Key, Price = kvp.Value * 1.15 } );
foreach
wymusza widoczność efektu ubocznego tam, gdzie należy.
var dictionary = new Dictionary<string, int>
{
{ "Key", 12 }
};
var aggregateObjectCollection = dictionary.Select(
entry => new AggregateObject(entry.Key, entry.Value));
AggregateObject
dodaje KeyValuePair
? Gdzie jest „iteracja”, zgodnie z pytaniem w pytaniu?
foreach
, ale często go używałem. Czy moja odpowiedź naprawdę zasługiwała na głos?
Select
używa iteracji do uzyskania wyniku, ale nie jest samą iteratorem. Rodzaje rzeczy, do których foreach
używana jest iteracja ( ) - zwłaszcza operacje z efektami ubocznymi - nie wchodzą w zakres Linq, w tym Select
. Lambda nie uruchomi się, dopóki nie aggregateObjectCollection
zostanie wyliczona. Jeśli ta odpowiedź jest traktowana jako „pierwsza ścieżka” (tj. Stosowana przed stritem foreach
), zachęca do złych praktyk. W niektórych sytuacjach operacje Linq mogą być pomocne przed iteracją słownika, ale nie rozwiązują zadanego pytania.
Słownik <TKey, TValue> Jest to ogólna klasa kolekcji w języku c # i przechowuje dane w formacie wartości klucza. Klucz musi być unikalny i nie może mieć wartości zerowej, natomiast wartość może być zduplikowana i pusta. Każda pozycja w słowniku jest traktowane jako struktura KeyValuePair <TKey, TValue> reprezentująca klucz i jego wartość. i dlatego powinniśmy wziąć typ elementu KeyValuePair <TKey, TValue> podczas iteracji elementu. Poniżej znajduje się przykład.
Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1,"One");
dict.Add(2,"Two");
dict.Add(3,"Three");
foreach (KeyValuePair<int, string> item in dict)
{
Console.WriteLine("Key: {0}, Value: {1}", item.Key, item.Value);
}
Jeśli chcesz użyć pętli, możesz to zrobić:
var keyList=new List<string>(dictionary.Keys);
for (int i = 0; i < keyList.Count; i++)
{
var key= keyList[i];
var value = dictionary[key];
}
foreach
pętla i gorsza wydajność, ponieważ new List<string>(dictionary.Keys)
będą iterować dictionary.Count
czasy, zanim jeszcze będziesz mieć szansę na iterację. Pomijając fakt, że pytanie o „najlepszy sposób” jest subiektywne, nie rozumiem, jak można by to zakwalifikować jako „najlepszy sposób” lub „standardowy sposób”, którego szuka pytanie. Do „Jeśli chcesz użyć pętli ...” odpowiedziałbym „ Nie używaj for
pętli”.
foreach (var pair in dictionary.ToArray()) { }
. Mimo to myślę, że dobrze byłoby wyjaśnić w odpowiedzi konkretny scenariusz (scenariusze), w których chciałoby się użyć tego kodu oraz konsekwencje takiego postępowania.
proste z linq
Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1, "American Ipa");
dict.Add(2, "Weiss");
dict.ToList().ForEach(x => Console.WriteLine($"key: {x.Key} | value: {x.Value}") );
ToList()
ponieważ ForEach()
jest zdefiniowany tylko w List<>
klasie, ale dlaczego to wszystko zamiast po prostu foreach (var pair in dict) { }
? Powiedziałbym, że jest to jeszcze prostsze i nie ma takich samych implikacji pamięci / wydajności. W każdym razie dokładnie to rozwiązanie zostało już zaproponowane w tej odpowiedzi sprzed 3,5 roku.
oprócz postów o najwyższym rankingu, w których istnieje dyskusja między używaniem
foreach(KeyValuePair<string, string> entry in myDictionary)
{
// do something with entry.Value or entry.Key
}
lub
foreach(var entry in myDictionary)
{
// do something with entry.Value or entry.Key
}
najbardziej kompletny jest następujący, ponieważ można zobaczyć typ słownika z inicjalizacji, kvp to KeyValuePair
var myDictionary = new Dictionary<string, string>(x);//fill dictionary with x
foreach(var kvp in myDictionary)//iterate over dictionary
{
// do something with kvp.Value or kvp.Key
}