Pregunta

¿Existen clases de diccionario en la biblioteca de clases base .NET que permitan el uso de claves duplicadas?La única solución que encontré es crear, por ejemplo, una clase como:

Dictionary<string, List<object>>

Pero esto es bastante irritante de usar.En Java, creo que MultiMap logra esto, pero no puedo encontrar un análogo en .NET.

¿Fue útil?

Solución

Si está utilizando .NET 3.5, use el Lookup clase.

EDITAR: generalmente crea un Enumerable.ToLookup utilizando <=> . Esto supone que no es necesario cambiarlo después, pero normalmente encuentro que es lo suficientemente bueno.

Si ese no funciona para usted, no creo que haya nada en el marco que ayude, y usar el diccionario es tan bueno como se pone :(

Otros consejos

La clase Lista en realidad funciona bastante bien para colecciones de clave / valor que contienen duplicados en los que desea 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);
}

Aquí hay una forma de hacerlo con List < KeyValuePair & Lt; cadena, cadena > >

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

Si está utilizando 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).

Acabo de encontrar la PowerCollections biblioteca que incluye, entre otras cosas, una clase llamada MultiDictionary. Esto envuelve perfectamente este tipo de funcionalidad.

Nota muy importante sobre el uso de la búsqueda:

Puede crear una instancia de un 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 un <=>. Además, los objetos <=> son inmutables, es decir, no puede agregar o quitar elementos o claves de un objeto <=> después de que se haya creado.

(desde MSDN)

Creo que esto sería un show stopper para la mayoría de los usos.

Creo que algo como List<KeyValuePair<object, object>> haría el trabajo.

Si está usando > = .NET 4, entonces puede usar Tuple Clase:

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

Eche un vistazo a C5's Clase HashBag .

Es bastante fácil & "; tira tu propio &"; versión de un diccionario que permite " clave duplicada " entradas. Aquí hay una implementación simple y aproximada. Es posible que desee considerar agregar soporte para la mayoría (si no todos) básicamente 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 ejemplo rápido sobre 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));
}

En respuesta a la pregunta original. Algo así 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

NameValueCollection admite múltiples valores de cadena bajo 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.

Cuando use la opción 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 }

La forma en que uso es solo una

Dictionary<string, List<string>>

De esta manera, 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);

¿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 dividir en 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 convierte en hash a 1, la lista se recorre hasta que se encuentra Key = 1.

Si permitiera que se insertaran claves duplicadas, no podría diferenciar qué claves se asignan a qué valores.

Me topé con esta publicación en busca de la misma respuesta, y no encontré ninguna, así que preparé una solución de ejemplo con una lista de diccionarios, anulando el operador [] para agregar un nuevo diccionario a la lista cuando todo otros tienen una clave determinada (conjunto) y devuelven una lista de valores (get).
Es feo e ineficiente, SOLO 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;
            }
        }
    }

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

Este es un diccionario simultáneo de dos vías. Creo que esto te ayudará:

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
            }

uso esta clase simple:

public class ListMap<T,V> : List<KeyValuePair<T, V>>
{
    public void Add(T key, V value) {
        Add(new KeyValuePair<T, V>(key, value));
    }

    public List<V> Get(T key) {
        return FindAll(p => p.Key.Equals(key)).ConvertAll(p=> p.Value);
    }
}

uso:

var fruits = new ListMap<int, string>();
fruits.Add(1, "apple");
fruits.Add(1, "orange");
var c = fruits.Get(1).Count; //c = 2;

U puede definir un método para construir una clave de cadena compuesta cada vez que quieras usar el diccionario debes usar este método para construir tu clave por ejemplo:

private string keyBuilder(int key1, int key2)
{
    return string.Format("{0}/{1}", key1, key2);
}

para usar:

myDict.ContainsKey(keyBuilder(key1, key2))

Las claves duplicadas rompen el contrato completo 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 opción podría ser algo similar a un DataSet (en el lenguaje común de una tabla). Coloque sus claves en una columna y sus valores en la otra. Esto es significativamente más lento que un diccionario, pero esa es su compensación por perder la capacidad de trocear los objetos clave.

También esto es posible:

Dictionary<string, string[]> previousAnswers = null;

De esta manera, podemos tener claves únicas. Espero que esto funcione para ti.

Puede agregar las mismas teclas con diferentes mayúsculas y minúsculas como:

clave1
Key1
KEY1
KeY1
kEy1
keY1

Sé que es una respuesta ficticia, pero funcionó para mí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top