uzyskaj klucz słownika według wartości


361

Jak uzyskać klucz słownika według wartości w języku C #?

Dictionary<string, string> types = new Dictionary<string, string>()
{
            {"1", "one"},
            {"2", "two"},
            {"3", "three"}
};

Chcę coś takiego:

getByValueKey(string value);

getByValueKey("one")musi być powrót "1".

Jak najlepiej to zrobić? Może HashTable, SortedLists?


9
Gabe

Przeczytałem ten artykuł wcześniej, ale odpowiedź się tam pojawi.
loviji

5
Tak, ale otrzymujesz akceptowaną odpowiedź od Skeet .
ruffin

7
Przyjęta tutaj odpowiedź jest zdecydowanie lepsza niż wszystko w duplikacie pytania. Ale to pytanie jest starsze; być może wyrażenia lambda nie istniały, gdy Jon odpowiedział.
Seth Battin

5
Ponownie otwierając to pytanie, ponieważ drugie dotyczy konkretnie .Net 2.0, podczas gdy to nie odpowiada i ma lepszą odpowiedź na bieżącą wersję frameworku .Net.
Rachel

Odpowiedzi:


645

Wartości niekoniecznie muszą być unikalne, więc musisz sprawdzić. Możesz zrobić coś takiego:

var myKey = types.FirstOrDefault(x => x.Value == "one").Key;

Jeśli wartości są unikalne i są wstawiane rzadziej niż czytane, utwórz słownik odwrotny, w którym wartości są kluczami, a klucze są wartościami.


3
@loviji: Pamiętaj, że w rozwiązaniu zapętlającym, jeśli wartość znajduje się na końcu słownika, będzie musiała przejść przez wszystkie inne wartości, aby ją znaleźć. Jeśli masz kilka wpisów, spowoduje to spowolnienie programu.
Zach Johnson

2
@Zach Johnson: Dzięki. zgadzam się z Tobą. a twoja odpowiedź też mi się podoba. ale w moim słowniku 8-10 wpisów. i nie są dodawane dynamicznie. i myślę, że użycie tej odpowiedzi nie jest złym rozwiązaniem.
loviji

4
Czy coś mi umyka? Powyższy kod zwraca wartość, a nie klucz. Czy typy.FirstOrDefault (x => x.Value == "one"). Klucz byłby bardziej odpowiedni?
floele

19
Ostrzeżenie dla wszystkich: zaakceptowana odpowiedź w postaci, w której stoi z edycjami, wyrzuci wyjątek, jeśli FirstOrDefault nie znajdzie dopasowania i spróbuje uzyskać dostęp do „Klucza” na obiekcie o wartości null.
Jim Yarbro,

11
@JimYarbro: skoro KeyValuePair<Tkey,Tvalue>jest strukturą, więc jest to typ wartości, nigdy nie może być null. FirstOrDefaultzwróci instancję, w której wszystkie pola są inicjowane z ich wartością domyślną (jak nulldla łańcuchów lub 0 dla liczb całkowitych). Więc nie dostaniesz wyjątku. Ale nie wiesz również, czy znalazłeś wartość, więc ta odpowiedź nie obejmuje przypadku, w którym wartość nie istnieje.
Tim Schmelter,

26

Możesz to zrobić:

  1. Pętlując wszystkie słowa KeyValuePair<TKey, TValue>w słowniku (co będzie dużym hitem wydajności, jeśli masz kilka pozycji w słowniku)
  2. Użyj dwóch słowników, jednego do mapowania wartości do klucza, a drugiego do mapowania klucza do wartości (który zajmowałby dwa razy więcej miejsca w pamięci).

Użyj metody 1, jeśli wydajność nie jest brana pod uwagę, użyj metody 2, jeśli pamięć nie jest brana pod uwagę.

Ponadto wszystkie klucze muszą być unikalne, ale wartości nie muszą być unikalne. Możesz mieć więcej niż jeden klucz o określonej wartości.

Czy jest jakiś powód, dla którego nie można odwrócić relacji klucz-wartość?


1
Aby programowo stworzyć słownik odwrotny, nadal potrzebowalibyśmy metody 1, prawda?
Kyle Delaney

Jeśli jest to częste zjawisko, polecam również tę zamianę (w odniesieniu do twojego ostatniego pytania).
Bonez024,

3

Byłem w sytuacji, gdy wiązanie Linq nie było dostępne i musiałem wyraźnie rozszerzyć lambda. Zaowocowało to prostą funkcją:

public static T KeyByValue<T, W>(this Dictionary<T, W> dict, W val)
{
    T key = default;
    foreach (KeyValuePair<T, W> pair in dict)
    {
        if (EqualityComparer<W>.Default.Equals(pair.Value, val))
        {
            key = pair.Key;
            break;
        }
    }
    return key;
}

Nazwij to w następujący sposób:

public static void Main()
{
    Dictionary<string, string> dict = new Dictionary<string, string>()
    {
        {"1", "one"},
        {"2", "two"},
        {"3", "three"}
    };

    string key = KeyByValue(dict, "two");       
    Console.WriteLine("Key: " + key);
}

Działa na .NET 2.0 i innych ograniczonych środowiskach.


Dodanie go jako metody rozszerzenia jest przyjemniejsze :-)
Chayim Friedman

-1

może coś takiego:

foreach (var keyvaluepair in dict)
{
    if(Object.ReferenceEquals(keyvaluepair.Value, searchedObject))
    {
        //dict.Remove(keyvaluepair.Key);
        break;
    }
}

-1

Utworzyłem klasę podwójnego wyszukiwania:

/// <summary>
/// dictionary with double key lookup
/// </summary>
/// <typeparam name="T1">primary key</typeparam>
/// <typeparam name="T2">secondary key</typeparam>
/// <typeparam name="TValue">value type</typeparam>
public class cDoubleKeyDictionary<T1, T2, TValue> {
    private struct Key2ValuePair {
        internal T2 key2;
        internal TValue value;
    }
    private Dictionary<T1, Key2ValuePair> d1 = new Dictionary<T1, Key2ValuePair>();
    private Dictionary<T2, T1> d2 = new Dictionary<T2, T1>();

    /// <summary>
    /// add item
    /// not exacly like add, mote like Dictionary[] = overwriting existing values
    /// </summary>
    /// <param name="key1"></param>
    /// <param name="key2"></param>
    public void Add(T1 key1, T2 key2, TValue value) {
        lock (d1) {
            d1[key1] = new Key2ValuePair {
                key2 = key2,
                value = value,
            };
            d2[key2] = key1;
        }
    }

    /// <summary>
    /// get key2 by key1
    /// </summary>
    /// <param name="key1"></param>
    /// <param name="key2"></param>
    /// <returns></returns>
    public bool TryGetValue(T1 key1, out TValue value) {
        if (d1.TryGetValue(key1, out Key2ValuePair kvp)) {
            value = kvp.value;
            return true;
        } else {
            value = default;
            return false;
        }
    }

    /// <summary>
    /// get key1 by key2
    /// </summary>
    /// <param name="key2"></param>
    /// <param name="key1"></param>
    /// <remarks>
    /// 2x O(1) operation
    /// </remarks>
    /// <returns></returns>
    public bool TryGetValue2(T2 key2, out TValue value) {
        if (d2.TryGetValue(key2, out T1 key1)) {
            return TryGetValue(key1, out value);
        } else {
            value = default;
            return false;
        }
    }

    /// <summary>
    /// get key1 by key2
    /// </summary>
    /// <param name="key2"></param>
    /// <param name="key1"></param>
    /// <remarks>
    /// 2x O(1) operation
    /// </remarks>
    /// <returns></returns>
    public bool TryGetKey1(T2 key2, out T1 key1) {
        return d2.TryGetValue(key2, out key1);
    }

    /// <summary>
    /// get key1 by key2
    /// </summary>
    /// <param name="key2"></param>
    /// <param name="key1"></param>
    /// <remarks>
    /// 2x O(1) operation
    /// </remarks>
    /// <returns></returns>
    public bool TryGetKey2(T1 key1, out T2 key2) {
        if (d1.TryGetValue(key1, out Key2ValuePair kvp1)) {
            key2 = kvp1.key2;
            return true;
        } else {
            key2 = default;
            return false;
        }
    }

    /// <summary>
    /// remove item by key 1
    /// </summary>
    /// <param name="key1"></param>
    public void Remove(T1 key1) {
        lock (d1) {
            if (d1.TryGetValue(key1, out Key2ValuePair kvp)) {
                d1.Remove(key1);
                d2.Remove(kvp.key2);
            }
        }
    }

    /// <summary>
    /// remove item by key 2
    /// </summary>
    /// <param name="key2"></param>
    public void Remove2(T2 key2) {
        lock (d1) {
            if (d2.TryGetValue(key2, out T1 key1)) {
                d1.Remove(key1);
                d2.Remove(key2);
            }
        }
    }

    /// <summary>
    /// clear all items
    /// </summary>
    public void Clear() {
        lock (d1) {
            d1.Clear();
            d2.Clear();
        }
    }

    /// <summary>
    /// enumerator on key1, so we can replace Dictionary by cDoubleKeyDictionary
    /// </summary>
    /// <param name="key1"></param>
    /// <returns></returns>
    public TValue this[T1 key1] {
        get => d1[key1].value;
    }

    /// <summary>
    /// enumerator on key1, so we can replace Dictionary by cDoubleKeyDictionary
    /// </summary>
    /// <param name="key1"></param>
    /// <returns></returns>
    public TValue this[T1 key1, T2 key2] {
        set {
            lock (d1) {
                d1[key1] = new Key2ValuePair {
                    key2 = key2,
                    value = value,
                };
                d2[key2] = key1;
            }
        }
    }

-3
types.Values.ToList().IndexOf("one");

Values.ToList () konwertuje wartości ze słownika na listę obiektów. IndexOf („one”) przeszukuje twoją nową Listę szukając „one” i zwraca Indeks, który pasowałby do indeksu pary Klucz / Wartość w słowniku.

Ta metoda nie dba o klucze słownika, po prostu zwraca indeks wartości, której szukasz.

Pamiętaj, że w twoim słowniku może znajdować się więcej niż jedna „jedna” wartość. I dlatego nie ma metody „zdobądź klucz”.


-4

Poniższy kod działa tylko wtedy, gdy zawiera unikalne dane wartości

public string getKey(string Value)
{
    if (dictionary.ContainsValue(Value))
    {
        var ListValueData=new List<string>();
        var ListKeyData = new List<string>();

        var Values = dictionary.Values;
        var Keys = dictionary.Keys;

        foreach (var item in Values)
        {
            ListValueData.Add(item);
        }

        var ValueIndex = ListValueData.IndexOf(Value);
        foreach (var item in Keys)
        {
            ListKeyData.Add(item);
        }

        return  ListKeyData[ValueIndex];

    }
    return string.Empty;
}

3
-1 Zbyt dużo kodu na wydajność, która będzie gorsza niż najlepsza odpowiedź Kimi (która została opublikowana 6 lat wcześniej). Nie musisz uprzedzać właściwości Keys and Values, aby utworzyć te 2 listy (ToList Linqa zrobi to za Ciebie). Poza tym, jeśli zamierzasz używać IndexOf, mogłeś uniknąć wywołania ContainsValue (unikając w ten sposób 2 pętli przez wszystkie elementy tego samego zadania).
Mariano Desanze

2
Wydajność tej sugestii jest po prostu okropna. Równie dobrze możesz stworzyć ogólną klasę z dwoma słownikami. Jeden z nich zawiera klucz 1 i klucz 2, a drugi zawiera klucz 2 i klucz 1. W ten sposób możesz uzyskać dowolny klucz bez ... cóż ... wszystkiego, co sugeruje twoja odpowiedź.
Krythic,

-12

Mam na to bardzo prosty sposób. Wyszło mi to idealnie.

Dictionary<string, string> types = new Dictionary<string, string>();

types.Add("1", "one");
types.Add("2", "two");
types.Add("3", "three");

Console.WriteLine("Please type a key to show its value: ");
string rLine = Console.ReadLine();

if(types.ContainsKey(rLine))
{
    string value_For_Key = types[rLine];
    Console.WriteLine("Value for " + rLine + " is" + value_For_Key);
}

3
Przepraszamy, ale twoja odpowiedź nie odpowiada na pytanie. Pytanie dotyczyło tego, jak znaleźć klucz według wartości, twoja odpowiedź pokazuje standard: znalezienie wartości według klucza
Breeze

1
Przeczytaj najpierw questiion, następnym razem
Tommix

4
A to, panie i panowie, właśnie dlatego czytamy pytania przed opublikowaniem odpowiedzi.
Krythic,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.