c# - ¿Duplicar claves en los diccionarios.NET?
dictionary multimap (22)
¿Hay alguna clase de diccionario en la biblioteca de clases base .NET que permita el uso de claves duplicadas? La única solución que he encontrado es crear, por ejemplo, una clase como:
Dictionary<string, List<object>>
Pero esto es bastante irritante de usar realmente. En Java, creo que un MultiMap logra esto, pero no puede encontrar un análogo en .NET.
¿Quieres decir congruente y no un duplicado real? De lo contrario, una tabla hash no podría funcionar.
Congruente significa que dos claves separadas pueden hacer hash al valor equivalente, pero las claves no son iguales.
Por ejemplo: digamos que la función hash de su tabla hash era simplemente hashval = key mod 3. Tanto 1 como 4 se asignan a 1, pero son valores diferentes. Aquí es donde entra en juego tu idea de una lista.
Cuando necesita buscar 1, ese valor se ha troceado a 1, la lista se recorre hasta que se encuentra la Clave = 1.
Si permitiera la inserción de claves duplicadas, no podría diferenciar qué claves se asignan a qué valores.
Acabo de encontrar la biblioteca de PowerCollections que incluye, entre otras cosas, una clase llamada MultiDictionary. Esto envuelve perfectamente este tipo de funcionalidad.
Aquí hay una forma de hacer esto con List <KeyValuePair <string, string>>
public class ListWithDuplicates : List<KeyValuePair<string, string>>
{
public void Add(string key, string value)
{
var element = new KeyValuePair<string, string>(key, value);
this.Add(element);
}
}
var list = new ListWithDuplicates();
list.Add("k1", "v1");
list.Add("k1", "v2");
list.Add("k1", "v3");
foreach(var item in list)
{
string x = string.format("{0}={1}, ", item.Key, item.Value);
}
Salidas k1 = v1, k1 = v2, k1 = v3
Cambié la respuesta de @Hector Correa en una extensión con tipos genéricos y también le agregué un TryGetValue personalizado.
public static class ListWithDuplicateExtensions
{
public static void Add<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
{
var element = new KeyValuePair<TKey, TValue>(key, value);
collection.Add(element);
}
public static int TryGetValue<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, out IEnumerable<TValue> values)
{
values = collection.Where(pair => pair.Key.Equals(key)).Select(pair => pair.Value);
return values.Count();
}
}
Creo que algo como List<KeyValuePair<object, object>>
haría el trabajo.
Cuando use la List<KeyValuePair<string, object>>
, puede usar LINQ para hacer la búsqueda:
List<KeyValuePair<string, object>> myList = new List<KeyValuePair<string, object>>();
//fill it here
var q = from a in myList Where a.Key.Equals("somevalue") Select a.Value
if(q.Count() > 0){ //you''ve got your value }
En respuesta a la pregunta original. Algo como Dictionary<string, List<object>>
se implementa en una clase llamada MultiMap
en The Code Project
.
Puede encontrar más información en el siguiente enlace: http://www.codeproject.com/KB/cs/MultiKeyDictionary.aspx
Es bastante fácil "rodar su propia versión" de un diccionario que permite entradas de "clave duplicada". Aquí hay una implementación simple y aproximada. Es posible que desee considerar agregar soporte para la mayoría de los IDictionary<T>
(si no todos) en IDictionary<T>
.
public class MultiMap<TKey,TValue>
{
private readonly Dictionary<TKey,IList<TValue>> storage;
public MultiMap()
{
storage = new Dictionary<TKey,IList<TValue>>();
}
public void Add(TKey key, TValue value)
{
if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
storage[key].Add(value);
}
public IEnumerable<TKey> Keys
{
get { return storage.Keys; }
}
public bool ContainsKey(TKey key)
{
return storage.ContainsKey(key);
}
public IList<TValue> this[TKey key]
{
get
{
if (!storage.ContainsKey(key))
throw new KeyNotFoundException(
string.Format(
"The given key {0} was not found in the collection.", key));
return storage[key];
}
}
}
Un rápido ejemplo de cómo usarlo:
const string key = "supported_encodings";
var map = new MultiMap<string,Encoding>();
map.Add(key, Encoding.ASCII);
map.Add(key, Encoding.UTF8);
map.Add(key, Encoding.Unicode);
foreach (var existingKey in map.Keys)
{
var values = map[existingKey];
Console.WriteLine(string.Join(",", values));
}
Este es un diccionario concurrente simultáneo. Creo que esto te ayudará a:
public class HashMapDictionary<T1, T2> : System.Collections.IEnumerable
{
private System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>> _keyValue = new System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>>();
private System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>> _valueKey = new System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>>();
public ICollection<T1> Keys
{
get
{
return _keyValue.Keys;
}
}
public ICollection<T2> Values
{
get
{
return _valueKey.Keys;
}
}
public int Count
{
get
{
return _keyValue.Count;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
public List<T2> this[T1 index]
{
get { return _keyValue[index]; }
set { _keyValue[index] = value; }
}
public List<T1> this[T2 index]
{
get { return _valueKey[index]; }
set { _valueKey[index] = value; }
}
public void Add(T1 key, T2 value)
{
lock (this)
{
if (!_keyValue.TryGetValue(key, out List<T2> result))
_keyValue.TryAdd(key, new List<T2>() { value });
else if (!result.Contains(value))
result.Add(value);
if (!_valueKey.TryGetValue(value, out List<T1> result2))
_valueKey.TryAdd(value, new List<T1>() { key });
else if (!result2.Contains(key))
result2.Add(key);
}
}
public bool TryGetValues(T1 key, out List<T2> value)
{
return _keyValue.TryGetValue(key, out value);
}
public bool TryGetKeys(T2 value, out List<T1> key)
{
return _valueKey.TryGetValue(value, out key);
}
public bool ContainsKey(T1 key)
{
return _keyValue.ContainsKey(key);
}
public bool ContainsValue(T2 value)
{
return _valueKey.ContainsKey(value);
}
public void Remove(T1 key)
{
lock (this)
{
if (_keyValue.TryRemove(key, out List<T2> values))
{
foreach (var item in values)
{
var remove2 = _valueKey.TryRemove(item, out List<T1> keys);
}
}
}
}
public void Remove(T2 value)
{
lock (this)
{
if (_valueKey.TryRemove(value, out List<T1> keys))
{
foreach (var item in keys)
{
var remove2 = _keyValue.TryRemove(item, out List<T2> values);
}
}
}
}
public void Clear()
{
_keyValue.Clear();
_valueKey.Clear();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _keyValue.GetEnumerator();
}
}
ejemplos:
public class TestA
{
public int MyProperty { get; set; }
}
public class TestB
{
public int MyProperty { get; set; }
}
HashMapDictionary<TestA, TestB> hashMapDictionary = new HashMapDictionary<TestA, TestB>();
var a = new TestA() { MyProperty = 9999 };
var b = new TestB() { MyProperty = 60 };
var b2 = new TestB() { MyProperty = 5 };
hashMapDictionary.Add(a, b);
hashMapDictionary.Add(a, b2);
hashMapDictionary.TryGetValues(a, out List<TestB> result);
foreach (var item in result)
{
//do something
}
La clase List realmente funciona bastante bien para las colecciones de clave / valor que contienen duplicados donde le gustaría iterar sobre la colección. Ejemplo:
List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
// add some values to the collection here
for (int i = 0; i < list.Count; i++)
{
Print(list[i].Key, list[i].Value);
}
La forma en que uso es solo un
Dictionary<string, List<string>>
De esta manera usted tiene una sola tecla que contiene una lista de cadenas.
Ejemplo:
List<string> value = new List<string>();
if (dictionary.Contains(key)) {
value = dictionary[key];
}
value.Add(newValue);
Las claves duplicadas rompen todo el contrato del Diccionario. En un diccionario, cada clave es única y se asigna a un solo valor. Si desea vincular un objeto a un número arbitrario de objetos adicionales, la mejor apuesta podría ser algo similar a un DataSet (en el lenguaje común, una tabla). Ponga sus llaves en una columna y sus valores en la otra. Esto es significativamente más lento que un diccionario, pero esa es su desventaja por perder la capacidad de capturar los objetos clave.
Me topé con esta publicación en busca de la misma respuesta, y no encontré ninguna, así que armé una solución de ejemplo escueto usando una lista de diccionarios, anulando al operador [] para agregar un nuevo diccionario a la lista cuando todos los demás tienen una dada clave (set), y devolver una lista de valores (obtener).
Es feo e ineficiente, SOLO se obtiene / establece por clave, y siempre devuelve una lista, pero funciona:
class DKD {
List<Dictionary<string, string>> dictionaries;
public DKD(){
dictionaries = new List<Dictionary<string, string>>();}
public object this[string key]{
get{
string temp;
List<string> valueList = new List<string>();
for (int i = 0; i < dictionaries.Count; i++){
dictionaries[i].TryGetValue(key, out temp);
if (temp == key){
valueList.Add(temp);}}
return valueList;}
set{
for (int i = 0; i < dictionaries.Count; i++){
if (dictionaries[i].ContainsKey(key)){
continue;}
else{
dictionaries[i].Add(key,(string) value);
return;}}
dictionaries.Add(new Dictionary<string, string>());
dictionaries.Last()[key] =(string)value;
}
}
}
NameValueCollection admite varios valores de cadena en una clave (que también es una cadena), pero es el único ejemplo que conozco.
Tiendo a crear construcciones similares a la de tu ejemplo cuando me encuentro con situaciones en las que necesito ese tipo de funcionalidad.
Nota muy importante sobre el uso de Lookup:
Puede crear una instancia de una Lookup(TKey, TElement)
llamando a ToLookup
en un objeto que implementa IEnumerable(T)
No hay un constructor público para crear una nueva instancia de una Lookup(TKey, TElement)
. Además, los objetos de Lookup(TKey, TElement)
son inmutables, es decir, no puede agregar o eliminar elementos o claves de un objeto de Lookup(TKey, TElement)
después de que se haya creado.
Pensaría que esto sería un tapón de show para la mayoría de los usos.
Puede agregar las mismas llaves con diferentes casos como:
llave1
Llave1
KEY1
KeY1
kEy1
keY1
Sé que es una respuesta ficticia, pero trabajó para mí.
Si está usando> = .NET 4, entonces puede usar Tuple
Class:
// declaration
var list = new List<Tuple<string, List<object>>>();
// to add an item to the list
var item = Tuple<string, List<object>>("key", new List<object>);
list.Add(item);
// to iterate
foreach(var i in list)
{
Console.WriteLine(i.Item1.ToString());
}
Si está utilizando .NET 3.5, use la clase de Lookup
.
EDITAR: Generalmente crea una Lookup
utilizando Enumerable.ToLookup
. Esto supone que no es necesario cambiarlo después, pero normalmente encuentro que es lo suficientemente bueno.
Si eso no funciona para usted, no creo que haya nada en el marco que ayude, y usar el diccionario es tan bueno como puede :(
Si usa cadenas como claves y valores, puede usar System.Collections.Specialized.NameValueCollection , que devolverá una matriz de valores de cadena a través del método GetValues (clave de cadena).
También esto es posible:
Dictionary<string, string[]> previousAnswers = null;
De esta manera, podemos tener claves únicas. Espero que esto funcione para usted.
U puede definir un método para construir una clave de cadena compuesta en todos los lugares donde desea utilizar el diccionario. Debe usar este método para construir su clave, por ejemplo:
private string keyBuilder(int key1, int key2)
{
return string.Format("{0}/{1}", key1, key2);
}
Para usar:
myDict.ContainsKey(keyBuilder(key1, key2))