c# - recorrer - Obtener la clave de valor de un diccionario genérico?
dictionary c# get value by key (13)
¿No puedes crear una subclase de Diccionario que tenga esa funcionalidad?
public class MyDict < TKey, TValue > : Dictionary < TKey, TValue >
{
private Dictionary < TValue, TKey > _keys;
public TValue this[TKey key]
{
get
{
return base[key];
}
set
{
base[key] = value;
_keys[value] = key;
}
}
public MyDict()
{
_keys = new Dictionary < TValue, TKey >();
}
public TKey GetKeyFromValue(TValue value)
{
return _keys[value];
}
}
EDITAR: Perdón, no obtuve el código correcto la primera vez.
Es fácil obtener el valor de una clave de un diccionario genérico .Net 2.0:
Dictionary<int, string> greek = new Dictionary<int, string>();
greek.Add(1, "Alpha");
greek.Add(2, "Beta");
string secondGreek = greek[2]; // Beta
Pero, ¿hay una forma simple de obtener la clave de un valor?
int[] betaKeys = greek.WhatDoIPutHere("Beta"); // expecting single 2
Como quería un diccionario bi-direccional completo (y no solo un mapa), agregué las funciones que faltaban para convertirlo en una clase compatible con IDictionary. Esto se basa en la versión con pares clave-valor únicos. Aquí está el archivo si lo desea (la mayoría del trabajo fue a través de XMLDoc):
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Common
{
/// <summary>Represents a bidirectional collection of keys and values.</summary>
/// <typeparam name="TFirst">The type of the keys in the dictionary</typeparam>
/// <typeparam name="TSecond">The type of the values in the dictionary</typeparam>
[System.Runtime.InteropServices.ComVisible(false)]
[System.Diagnostics.DebuggerDisplay("Count = {Count}")]
//[System.Diagnostics.DebuggerTypeProxy(typeof(System.Collections.Generic.Mscorlib_DictionaryDebugView<,>))]
//[System.Reflection.DefaultMember("Item")]
public class BiDictionary<TFirst, TSecond> : Dictionary<TFirst, TSecond>
{
IDictionary<TSecond, TFirst> _ValueKey = new Dictionary<TSecond, TFirst>();
/// <summary> PropertyAccessor for Iterator over KeyValue-Relation </summary>
public IDictionary<TFirst, TSecond> KeyValue => this;
/// <summary> PropertyAccessor for Iterator over ValueKey-Relation </summary>
public IDictionary<TSecond, TFirst> ValueKey => _ValueKey;
#region Implemented members
/// <Summary>Gets or sets the value associated with the specified key.</Summary>
/// <param name="key">The key of the value to get or set.</param>
/// <Returns>The value associated with the specified key. If the specified key is not found,
/// a get operation throws a <see cref="KeyNotFoundException"/>, and
/// a set operation creates a new element with the specified key.</Returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
/// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
/// The property is retrieved and <paramref name="key"/> does not exist in the collection.</exception>
/// <exception cref="T:System.ArgumentException"> An element with the same key already
/// exists in the <see cref="ValueKey"/> <see cref="Dictionary<TFirst,TSecond>"/>.</exception>
public new TSecond this[TFirst key]
{
get { return base[key]; }
set { _ValueKey.Remove(base[key]); base[key] = value; _ValueKey.Add(value, key); }
}
/// <Summary>Gets or sets the key associated with the specified value.</Summary>
/// <param name="val">The value of the key to get or set.</param>
/// <Returns>The key associated with the specified value. If the specified value is not found,
/// a get operation throws a <see cref="KeyNotFoundException"/>, and
/// a set operation creates a new element with the specified value.</Returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="val"/> is null.</exception>
/// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
/// The property is retrieved and <paramref name="val"/> does not exist in the collection.</exception>
/// <exception cref="T:System.ArgumentException"> An element with the same value already
/// exists in the <see cref="KeyValue"/> <see cref="Dictionary<TFirst,TSecond>"/>.</exception>
public TFirst this[TSecond val]
{
get { return _ValueKey[val]; }
set { base.Remove(_ValueKey[val]); _ValueKey[val] = value; base.Add(value, val); }
}
/// <Summary>Adds the specified key and value to the dictionary.</Summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add.</param>
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> is null.</exception>
/// <exception cref="T:System.ArgumentException">An element with the same key or value already exists in the <see cref="Dictionary<TFirst,TSecond>"/>.</exception>
public new void Add(TFirst key, TSecond value) {
base.Add(key, value);
_ValueKey.Add(value, key);
}
/// <Summary>Removes all keys and values from the <see cref="Dictionary<TFirst,TSecond>"/>.</Summary>
public new void Clear() { base.Clear(); _ValueKey.Clear(); }
/// <Summary>Determines whether the <see cref="Dictionary<TFirst,TSecond>"/> contains the specified
/// KeyValuePair.</Summary>
/// <param name="item">The KeyValuePair to locate in the <see cref="Dictionary<TFirst,TSecond>"/>.</param>
/// <Returns>true if the <see cref="Dictionary<TFirst,TSecond>"/> contains an element with
/// the specified key which links to the specified value; otherwise, false.</Returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception>
public bool Contains(KeyValuePair<TFirst, TSecond> item) => base.ContainsKey(item.Key) & _ValueKey.ContainsKey(item.Value);
/// <Summary>Removes the specified KeyValuePair from the <see cref="Dictionary<TFirst,TSecond>"/>.</Summary>
/// <param name="item">The KeyValuePair to remove.</param>
/// <Returns>true if the KeyValuePair is successfully found and removed; otherwise, false. This
/// method returns false if <paramref name="item"/> is not found in the <see cref="Dictionary<TFirst,TSecond>"/>.</Returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="item"/> is null.</exception>
public bool Remove(KeyValuePair<TFirst, TSecond> item) => base.Remove(item.Key) & _ValueKey.Remove(item.Value);
/// <Summary>Removes the value with the specified key from the <see cref="Dictionary<TFirst,TSecond>"/>.</Summary>
/// <param name="key">The key of the element to remove.</param>
/// <Returns>true if the element is successfully found and removed; otherwise, false. This
/// method returns false if <paramref name="key"/> is not found in the <see cref="Dictionary<TFirst,TSecond>"/>.</Returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception>
public new bool Remove(TFirst key) => _ValueKey.Remove(base[key]) & base.Remove(key);
/// <Summary>Gets the key associated with the specified value.</Summary>
/// <param name="value">The value of the key to get.</param>
/// <param name="key">When this method returns, contains the key associated with the specified value,
/// if the value is found; otherwise, the default value for the type of the key parameter.
/// This parameter is passed uninitialized.</param>
/// <Returns>true if <see cref="ValueKey"/> contains an element with the specified value;
/// otherwise, false.</Returns>
/// <exception cref="T:System.ArgumentNullException"><paramref name="value"/> is null.</exception>
public bool TryGetValue(TSecond value, out TFirst key) => _ValueKey.TryGetValue(value, out key);
#endregion
}
}
Como todos han dicho, no hay mapeo dentro de un diccionario de valor a clave.
Me acabo de dar cuenta de que desea asignar un valor de varias claves: dejo esta solución aquí para la versión de valor único, pero luego agregaré otra respuesta para un mapa bidireccional de varias entradas.
El enfoque normal a seguir aquí es tener dos diccionarios, uno de mapeo y otro de mapeo. Encapsúlelos en una clase separada, y resuelva lo que quiere hacer cuando tiene una clave duplicada o valor (por ejemplo, lanzar una excepción, sobrescribir la entrada existente o ignorar la nueva entrada). Personalmente, es probable que arroje una excepción: hace que el comportamiento de éxito sea más fácil de definir. Algo como esto:
using System;
using System.Collections.Generic;
class BiDictionary<TFirst, TSecond>
{
IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
public void Add(TFirst first, TSecond second)
{
if (firstToSecond.ContainsKey(first) ||
secondToFirst.ContainsKey(second))
{
throw new ArgumentException("Duplicate first or second");
}
firstToSecond.Add(first, second);
secondToFirst.Add(second, first);
}
public bool TryGetByFirst(TFirst first, out TSecond second)
{
return firstToSecond.TryGetValue(first, out second);
}
public bool TryGetBySecond(TSecond second, out TFirst first)
{
return secondToFirst.TryGetValue(second, out first);
}
}
class Test
{
static void Main()
{
BiDictionary<int, string> greek = new BiDictionary<int, string>();
greek.Add(1, "Alpha");
greek.Add(2, "Beta");
int x;
greek.TryGetBySecond("Beta", out x);
Console.WriteLine(x);
}
}
Como un giro de la respuesta aceptada ( https://.com/a/255638/986160 ) suponiendo que las claves estarán asociadas a los valores de signle en el diccionario. Similar a ( https://.com/a/255630/986160 ) pero un poco más elegante. La novedad radica en que la clase consumidora puede usarse como una alternativa de enumeración (pero también para cadenas) y que el diccionario implementa IEnumerable.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace MyApp.Dictionaries
{
class BiDictionary<TFirst, TSecond> : IEnumerable
{
IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
public void Add(TFirst first, TSecond second)
{
firstToSecond.Add(first, second);
secondToFirst.Add(second, first);
}
public TSecond this[TFirst first]
{
get { return GetByFirst(first); }
}
public TFirst this[TSecond second]
{
get { return GetBySecond(second); }
}
public TSecond GetByFirst(TFirst first)
{
return firstToSecond[first];
}
public TFirst GetBySecond(TSecond second)
{
return secondToFirst[second];
}
public IEnumerator GetEnumerator()
{
return GetFirstEnumerator();
}
public IEnumerator GetFirstEnumerator()
{
return firstToSecond.GetEnumerator();
}
public IEnumerator GetSecondEnumerator()
{
return secondToFirst.GetEnumerator();
}
}
}
Y como una clase consumidora, podrías tener
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyApp.Dictionaries
{
class Greek
{
public static readonly string Alpha = "Alpha";
public static readonly string Beta = "Beta";
public static readonly string Gamma = "Gamma";
public static readonly string Delta = "Delta";
private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();
static Greek() {
Dictionary.Add(1, Alpha);
Dictionary.Add(2, Beta);
Dictionary.Add(3, Gamma);
Dictionary.Add(4, Delta);
}
public static string getById(int id){
return Dictionary.GetByFirst(id);
}
public static int getByValue(string value)
{
return Dictionary.GetBySecond(value);
}
}
}
De acuerdo, aquí está la versión bidireccional múltiple:
using System;
using System.Collections.Generic;
using System.Text;
class BiDictionary<TFirst, TSecond>
{
IDictionary<TFirst, IList<TSecond>> firstToSecond = new Dictionary<TFirst, IList<TSecond>>();
IDictionary<TSecond, IList<TFirst>> secondToFirst = new Dictionary<TSecond, IList<TFirst>>();
private static IList<TFirst> EmptyFirstList = new TFirst[0];
private static IList<TSecond> EmptySecondList = new TSecond[0];
public void Add(TFirst first, TSecond second)
{
IList<TFirst> firsts;
IList<TSecond> seconds;
if (!firstToSecond.TryGetValue(first, out seconds))
{
seconds = new List<TSecond>();
firstToSecond[first] = seconds;
}
if (!secondToFirst.TryGetValue(second, out firsts))
{
firsts = new List<TFirst>();
secondToFirst[second] = firsts;
}
seconds.Add(second);
firsts.Add(first);
}
// Note potential ambiguity using indexers (e.g. mapping from int to int)
// Hence the methods as well...
public IList<TSecond> this[TFirst first]
{
get { return GetByFirst(first); }
}
public IList<TFirst> this[TSecond second]
{
get { return GetBySecond(second); }
}
public IList<TSecond> GetByFirst(TFirst first)
{
IList<TSecond> list;
if (!firstToSecond.TryGetValue(first, out list))
{
return EmptySecondList;
}
return new List<TSecond>(list); // Create a copy for sanity
}
public IList<TFirst> GetBySecond(TSecond second)
{
IList<TFirst> list;
if (!secondToFirst.TryGetValue(second, out list))
{
return EmptyFirstList;
}
return new List<TFirst>(list); // Create a copy for sanity
}
}
class Test
{
static void Main()
{
BiDictionary<int, string> greek = new BiDictionary<int, string>();
greek.Add(1, "Alpha");
greek.Add(2, "Beta");
greek.Add(5, "Beta");
ShowEntries(greek, "Alpha");
ShowEntries(greek, "Beta");
ShowEntries(greek, "Gamma");
}
static void ShowEntries(BiDictionary<int, string> dict, string key)
{
IList<int> values = dict[key];
StringBuilder builder = new StringBuilder();
foreach (int value in values)
{
if (builder.Length != 0)
{
builder.Append(", ");
}
builder.Append(value);
}
Console.WriteLine("{0}: [{1}]", key, builder);
}
}
En realidad, los diccionarios no están diseñados para funcionar así porque, aunque se garantiza la exclusividad de las claves, la exclusividad de los valores no lo es. Por ejemplo, si tuvieras
var greek = new Dictionary<int, string> { { 1, "Alpha" }, { 2, "Alpha" } };
¿Qué esperarías obtener para greek.WhatDoIPutHere("Alpha")
?
Por lo tanto, no puede esperar que algo como esto se incorpore al marco. Necesitarás tu propio método para tus propios usos: ¿quieres devolver una matriz (o IEnumerable<T>
)? ¿Desea lanzar una excepción si hay varias claves con el valor dado? ¿Qué pasa si no hay ninguno?
Personalmente, me gustaría un enumerable, como ese:
IEnumerable<TKey> KeysFromValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, TValue val)
{
if (dict == null)
{
throw new ArgumentNullException("dict");
}
return dict.Keys.Where(k => dict[k] == val);
}
var keys = greek.KeysFromValue("Beta");
int exceptionIfNotExactlyOne = greek.KeysFromValue("Beta").Single();
La clase de diccionario no está optimizada para este caso, pero si realmente desea hacerlo (en C # 2.0), puede hacer:
public List<TKey> GetKeysFromValue<TKey, TVal>(Dictionary<TKey, TVal> dict, TVal val)
{
List<TKey> ks = new List<TKey>();
foreach(TKey k in dict.Keys)
{
if (dict[k] == val) { ks.Add(k); }
}
return ks;
}
Prefiero la solución LINQ por elegancia, pero esta es la manera 2.0.
La solución de diccionario bidireccional "simple" aquí propuesta es compleja y puede ser difícil de entender, mantener o ampliar. También la pregunta original pedía "la clave para un valor", pero claramente podría haber varias claves (desde entonces he editado la pregunta). Todo el enfoque es bastante sospechoso.
Cambios de software El código de escritura que es fácil de mantener debe tener prioridad sobre otras soluciones complejas "inteligentes". La forma de recuperar las claves de los valores en un diccionario es realizar un ciclo. Un diccionario no está diseñado para ser bidireccional.
Quizás la forma más fácil de hacerlo, sin Linq, puede ser repetir los pares:
int betaKey;
foreach (KeyValuePair<int, string> pair in lookup)
{
if (pair.Value == value)
{
betaKey = pair.Key; // Found
break;
}
}
betaKey = -1; // Not found
Si tuviera Linq, podría haberlo hecho de esta manera:
int betaKey = greek.SingleOrDefault(x => x.Value == "Beta").Key;
Un diccionario no mantiene un hash de los valores, solo las claves, por lo que cualquier búsqueda sobre él usando un valor tomará al menos tiempo lineal. Su mejor opción es simplemente iterar sobre los elementos en el diccionario y realizar un seguimiento de las claves coincidentes o cambiar a una estructura de datos diferente, tal vez mantener dos mapas de correspondencia key-> value y value-> List_of_keys. Si haces esto último cambiarás el almacenamiento por la velocidad de búsqueda. No tomaría mucho convertir el ejemplo de @Cybis en esa estructura de datos.
Use LINQ para hacer una búsqueda inversa del Dictionary<K, V>
. Pero tenga en cuenta que los valores en los valores de Dictionary<K, V>
pueden no ser distintos.
Demostración:
using System;
using System.Collections.Generic;
using System.Linq;
class ReverseDictionaryLookupDemo
{
static void Main()
{
var dict = new Dictionary<int, string>();
dict.Add(4, "Four");
dict.Add(5, "Five");
dict.Add(1, "One");
dict.Add(11, "One"); // duplicate!
dict.Add(3, "Three");
dict.Add(2, "Two");
dict.Add(44, "Four"); // duplicate!
Console.WriteLine("/n== Enumerating Distinct Values ==");
foreach (string value in dict.Values.Distinct())
{
string valueString =
String.Join(", ", GetKeysFromValue(dict, value));
Console.WriteLine("{0} => [{1}]", value, valueString);
}
}
static List<int> GetKeysFromValue(Dictionary<int, string> dict, string value)
{
// Use LINQ to do a reverse dictionary lookup.
// Returns a ''List<T>'' to account for the possibility
// of duplicate values.
return
(from item in dict
where item.Value.Equals(value)
select item.Key).ToList();
}
}
Rendimiento esperado:
== Enumerating Distinct Values ==
Four => [4, 44]
Five => [5]
One => [1, 11]
Three => [3]
Two => [2]
revisado: está bien tener algún tipo de hallazgo que necesitaría algo más que el diccionario, ya que si lo piensas, el diccionario tiene teclas de una sola dirección. es decir, los valores pueden no ser únicos
Dicho esto, parece que estás usando c # 3.0 por lo que es posible que no tengas que recurrir al bucle y puedas usar algo como:
var key = (from k in yourDictionary where string.Compare(k.Value, "yourValue", true) == 0 select k.Key).FirstOrDefault();
Dictionary<string, string> dic = new Dictionary<string, string>();
dic["A"] = "Ahmed";
dic["B"] = "Boys";
foreach (string mk in dic.Keys)
{
if(dic[mk] == "Ahmed")
{
Console.WriteLine("The key that contains /"Ahmed/" is " + mk);
}
}