obter chave do dicionário por valor

361

Como obtenho uma chave de dicionário por valor em c #?

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

Eu quero algo como isto:

getByValueKey(string value);

getByValueKey("one")deve ser retorno "1".

Qual é a melhor maneira de fazer isso? Talvez HashTable, SortedLists?

loviji
fonte
9
Duplicado exato: stackoverflow.com/questions/255341
Gabe #
eu li este artigo antes, mas a resposta chega lá.
loviji
5
Sim, mas você recebe uma resposta aceita da Skeet .
ruffin
7
A resposta aceita aqui é drasticamente melhor do que tudo na pergunta duplicada. Mas essa pergunta é mais antiga; talvez as expressões lambda não existissem quando Jon respondeu.
Seth Battin
5
Reabrindo essa questão, já que a outra aborda especificamente o .Net 2.0, enquanto este não tem e tem uma resposta melhor para a versão atual da estrutura .Net.
Rachel

Respostas:

645

Os valores não necessariamente precisam ser exclusivos, portanto, você deve fazer uma pesquisa. Você pode fazer algo assim:

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

Se os valores forem exclusivos e forem inseridos com menos frequência que a leitura, crie um dicionário inverso em que os valores sejam chaves e as chaves sejam valores.

Kimi
fonte
3
@loviji: Lembre-se de que, na solução de loop, se o valor estiver no final do dicionário, ele terá que passar por todos os outros valores para encontrá-lo. Se você tiver várias entradas, isso tornará seu programa mais lento.
Zach Johnson
2
@Zach Johnson: Obrigado. Eu concordo com você. e sua resposta eu também gosto. mas no meu dicionário 8-10 entradas. e eles não são adicionados dinamicamente. e eu acho que, usando esta resposta não é uma solução ruim.
loviji
4
Estou faltando alguma coisa aqui? O código acima retorna o valor, não a chave. Types.FirstOrDefault (x => x.Value == "one"). A chave seria mais apropriada?
floele
19
Atenção para todos, a resposta aceita como está nas edições lançará uma exceção se FirstOrDefault não encontrar correspondência e tentar acessar "Key" no objeto nulo.
Jim Yarbro 29/08
11
@ JimYarbro: uma vez que KeyValuePair<Tkey,Tvalue>é uma estrutura, portanto, um tipo de valor, nunca pode ser null. FirstOrDefaultretornará uma instância em que todos os campos são inicializados com seu valor padrão (como nullpara strings ou 0 para ints). Então você não receberá uma exceção. Mas você também não sabe se encontrou um valor, portanto, esta resposta não cobre o caso de o valor não existir.
Tim Schmelter
26

Você poderia fazer isso:

  1. Ao percorrer todos os KeyValuePair<TKey, TValue>'s' no dicionário (que será um sucesso considerável se você tiver várias entradas no dicionário)
  2. Use dois dicionários, um para mapeamento de valor para chave e outro para mapeamento de chave para valor (que ocuparia o dobro de espaço na memória).

Use o método 1 se o desempenho não for uma consideração, use o método 2 se a memória não for uma consideração.

Além disso, todas as chaves devem ser exclusivas, mas os valores não precisam ser exclusivos. Você pode ter mais de uma chave com o valor especificado.

Existe algum motivo para você não poder reverter a relação de valor-chave?

Zach Johnson
fonte
11
Para criar o dicionário inverso programaticamente, ainda precisamos usar o método 1, certo?
Kyle Delaney
Se essa é uma ocorrência comum, eu recomendaria essa troca também (em relação à sua última pergunta).
Bonez024 06/08/19
3

Eu estava em uma situação em que a ligação do Linq não estava disponível e tive que expandir o lambda explicitamente. Isso resultou em uma função simples:

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

Chame da seguinte forma:

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

Funciona no .NET 2.0 e em outros ambientes limitados.

Boris Zinchenko
fonte
Adicioná-lo como um método de extensão é mais agradável :-)
Chayim Friedman
-1

talvez algo como isto:

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

Eu criei uma classe de pesquisa dupla:

/// <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;
            }
        }
    }
PTK
fonte
-3
types.Values.ToList().IndexOf("one");

Values.ToList () converte os valores do dicionário em uma lista de objetos. IndexOf ("one") pesquisa sua nova lista procurando "one" e retorna o índice que corresponderia ao índice do par Chave / Valor no dicionário.

Este método não se importa com as chaves do dicionário, simplesmente retorna o índice do valor que você está procurando.

Lembre-se de que pode haver mais de um valor "um" no seu dicionário. E é por isso que não existe um método "obter chave".

EricM
fonte
-4

O código abaixo funciona apenas se ele contém dados de valor exclusivos

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;
}
Pradeep Kumar Das
fonte
3
-1 Demasiado código para um desempenho pior do que a resposta principal de Kimi (publicada 6 anos antes da sua). Você não precisa buscar as propriedades Chaves e Valores para criar essas 2 listas (o ToList do Linq fará isso por você). Além disso, se você for usar o IndexOf, poderá ter evitado a chamada para ContainsValue (evitando assim 2 loops de todos os elementos para a mesma tarefa).
Mariano Desanze
2
O desempenho desta sugestão é simplesmente horrível. Você também pode criar uma classe genérica com dois dicionários. Um deles contém as Teclas 1 e 2 e o outro as teclas 2 e 1. Dessa forma, você pode obter qualquer uma das teclas sem ... bem ... tudo o que sua resposta sugeriu.
Krythic
-11

Eu tenho uma maneira muito simples de fazer isso. Funcionou perfeito para mim.

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);
}
Dushyant Patel
fonte
3
Desculpe, mas sua resposta não atende à pergunta. A pergunta era sobre como encontrar a chave pelo valor, sua resposta mostra o padrão: encontrando o valor pela chave
Breeze
11
Leia as perguntas primeiro, da próxima vez
Tommix 04/08/2015
4
Senhoras e Senhores Deputados, é exatamente por isso que lemos as perguntas antes de postar uma resposta.
Krythic