obtener clave de diccionario por valor


361

¿Cómo obtengo una clave de diccionario por valor en C #?

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

Quiero algo como esto:

getByValueKey(string value);

getByValueKey("one")Debe ser devuelto "1".

¿Cuál es la mejor manera de hacer esto? ¿Quizás HashTable, SortedLists?



Leí este artículo antes, pero la respuesta llega allí.
loviji

55
Sí, pero allí obtienes una respuesta aceptada de Skeet .
ruffin

77
La respuesta aceptada aquí es drásticamente mejor que todo en la pregunta duplicada. Pero esa pregunta es más antigua; quizás las expresiones lambda no existían cuando Jon respondió.
Seth Battin

55
Reabrimos esta pregunta ya que la otra aborda específicamente .Net 2.0, mientras que esta no lo hace y tiene una mejor respuesta para la versión actual del marco .Net.
Rachel

Respuestas:


645

Los valores no necesariamente tienen que ser únicos, por lo que debe realizar una búsqueda. Puedes hacer algo como esto:

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

Si los valores son únicos y se insertan con menos frecuencia que la lectura, cree un diccionario inverso donde los valores son claves y las claves son valores.


3
@loviji: tenga en cuenta que en la solución de bucle, si el valor se encuentra al final del diccionario, tendrá que repasar todos los demás valores para encontrarlo. Si tiene varias entradas, esto retrasará su programa.
Zach Johnson

2
@Zach Johnson: Gracias. Estoy de acuerdo contigo. y tu respuesta también me gusta. pero en mi diccionario 8-10 entradas. y no se agregan dinámicamente. y creo que usar esta respuesta no es una mala solución.
loviji

44
¿Me estoy perdiendo de algo? El código anterior devuelve el valor, no la clave. ¿No sería type.FirstOrDefault (x => x.Value == "one"). La clave sería más apropiada?
floele

19
Advertencia a todos, la respuesta aceptada tal como está con las ediciones arrojará una excepción si FirstOrDefault no encuentra ninguna coincidencia e intenta acceder a "Clave" en el objeto nulo.
Jim Yarbro

11
@JimYarbro: como KeyValuePair<Tkey,Tvalue>es una estructura, por lo que es un tipo de valor, nunca puede serlo null. FirstOrDefaultdevolverá una instancia donde todos los campos se inicializan con su valor predeterminado (como nullpara cadenas o 0 para entradas). Entonces no obtendrás una excepción. Pero tampoco sabe si ha encontrado un valor, por lo que esta respuesta no cubre el caso de que el valor no exista.
Tim Schmelter

26

Podrías hacer eso:

  1. Al recorrer todas las KeyValuePair<TKey, TValue>'s en el diccionario (que será un éxito considerable si tiene varias entradas en el diccionario)
  2. Use dos diccionarios, uno para la asignación de valor a clave y otro para la asignación de clave a valor (que ocuparía el doble de espacio en la memoria).

Use el Método 1 si el rendimiento no es una consideración, use el Método 2 si la memoria no es una consideración.

Además, todas las claves deben ser únicas, pero no es necesario que los valores sean únicos. Puede tener más de una clave con el valor especificado.

¿Hay alguna razón por la que no puedas revertir la relación clave-valor?


1
Para crear el diccionario inverso mediante programación, todavía tendríamos que usar el método 1, ¿verdad?
Kyle Delaney

Si esto es una ocurrencia común, recomendaría este intercambio también (con respecto a su última pregunta).
Bonez024

3

Estaba en una situación en la que el enlace Linq no estaba disponible y tuve que expandir lambda explícitamente. Resultó en una función simple:

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

Llámalo como sigue:

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 en .NET 2.0 y en otros entornos limitados.


Agregarlo como método de extensión es más agradable :-)
Chayim Friedman

-1

tal vez algo como esto:

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

-1

He creado una clase de doble búsqueda:

/// <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 () convierte los valores de tu diccionario en una Lista de objetos. IndexOf ("uno") busca en su nueva Lista buscando "uno" y devuelve el Índice que coincidiría con el índice del par Clave / Valor en el diccionario.

Este método no se preocupa por las claves del diccionario, simplemente devuelve el índice del valor que está buscando.

Tenga en cuenta que puede haber más de un valor "uno" en su diccionario. Y esa es la razón por la que no existe un método de "obtención de clave".


-4

El siguiente código solo funciona si contiene datos de valor único

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 Demasiado código para una actuación que será peor que la respuesta principal de Kimi (que se publicó 6 años antes que la tuya). No es necesario predecir las propiedades de Claves y Valores para crear esas 2 listas (la lista ToList de Linq lo hará por usted). Además, si va a utilizar IndexOf, podría haber evitado la llamada a ContainsValue (evitando así 2 bucles aunque todos los elementos para la misma tarea).
Mariano Desanze

2
El rendimiento de esta sugerencia es simplemente horrible. También podría crear una clase genérica con dos diccionarios. Uno de los cuales contiene Key1 y Key2, y el otro que contiene Key2 y Key1. De esta manera, puede obtener cualquiera de las claves sin ... bueno ... todo lo que sugiere su respuesta.
Krythic

-11

Tengo una manera muy simple de hacer esto. Funcionó perfecto para mí.

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
Lo sentimos, pero tu respuesta no responde a la pregunta. La pregunta era sobre cómo encontrar la clave por el valor, su respuesta muestra el estándar: encontrar el valor por la clave
Breeze

1
Lea la pregunta primero, la próxima vez
Tommix

44
Y esto, damas y caballeros, es exactamente por qué leemos las preguntas antes de publicar una respuesta.
Krythic
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.