it-swarm.com.ru

Дублирующиеся ключи в словарях .NET?

Есть ли в библиотеке базовых классов .NET какие-либо словарные классы, позволяющие использовать дублирующиеся ключи? Единственное решение, которое я нашел, - это создать, например, такой класс:

Dictionary<string, List<object>>

Но это довольно раздражает на самом деле использовать. Я полагаю, что в Java MultiMap выполняет это, но не может найти аналог в .NET.

232
waltersobchek.myopenid.com

Если вы используете .NET 3.5, используйте класс Lookup .

Правка: вы обычно создаете Lookup, используя Enumerable.ToLookup . Это предполагает, что вам не нужно менять его впоследствии - но я обычно нахожу, что это достаточно хорошо.

Если это не работает для вас, я не думаю, что в рамках есть что-то, что поможет - и использование словаря так же хорошо, как и получается :(

209
Jon Skeet

Класс List на самом деле работает довольно хорошо для коллекций ключ/значение, содержащих дубликаты, где вы хотели бы перебрать коллекцию. Пример:

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

Вот один из способов сделать это с помощью 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);
}

Выходы k1 = v1, k1 = v2, k1 = v3

37
Hector Correa

Если вы используете строки в качестве ключей и значений, вы можете использовать System.Collections.Specialized.NameValueCollection , которая будет возвращать массив строковых значений с помощью метода GetValues ​​(строковый ключ).

21
Matt

Я только что наткнулся на PowerCollections библиотеку, которая включает, помимо прочего, класс с именем MultiDictionary. Это аккуратно оборачивает этот тип функциональности.

17
waltersobchek.myopenid.com

Очень важное замечание относительно использования Lookup:

Вы можете создать экземпляр Lookup(TKey, TElement), вызвав ToLookup для объекта, который реализует IEnumerable(T)

Нет открытого конструктора для создания нового экземпляра функции Lookup(TKey, TElement). Кроме того, объекты Lookup(TKey, TElement) являются неизменяемыми, то есть вы не можете добавлять или удалять элементы или ключи из объекта Lookup(TKey, TElement) после его создания.

(из MSDN)

Я думаю, что это будет шоу-стоппер для большинства применений.

14
TheSoftwareJedi

Я думаю, что-то вроде List<KeyValuePair<object, object>> сделает работу.

10
MADMap

Если вы используете> = .NET 4, вы можете использовать класс Tuple:

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

Посмотрите на C5'sHashBag class.

6
Yuval

Достаточно просто «свернуть свою» версию словаря, которая позволяет вводить «дубликаты ключа». Вот грубая простая реализация. Возможно, вы захотите добавить поддержку большинства (если не всех) в 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];
        }
    }
}

Быстрый пример того, как его использовать:

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

В ответ на оригинальный вопрос. Нечто подобное Dictionary<string, List<object>> реализовано в классе под названием MultiMap в Code Project.

Вы можете найти дополнительную информацию по ссылке ниже: http://www.codeproject.com/KB/cs/MultiKeyDictionary.aspx

4
Dan

NameValueCollection поддерживает несколько строковых значений под одним ключом (который также является строкой), но это единственный известный мне пример.

Я склонен создавать конструкции, подобные той, что в вашем примере, когда я сталкиваюсь с ситуациями, когда мне нужна такая функциональность. 

3
ckramer

При использовании опции List<KeyValuePair<string, object>> вы можете использовать LINQ для поиска:

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 }
3
Greg

То, как я использую это просто

Dictionary<string, List<string>>

Таким образом, у вас есть один ключ, содержащий список строк.

Пример:

List<string> value = new List<string>();
if (dictionary.Contains(key)) {
     value = dictionary[key];
}
value.Add(newValue);
2
Stefan Mielke

Вы имеете в виду конгруэнтный, а не фактический дубликат? В противном случае хеш-таблица не сможет работать.

Конгруэнтный означает, что два отдельных ключа могут хэшировать до эквивалентного значения, но ключи не равны.

Например: скажем, хеш-функция вашей хеш-таблицы была просто hashval = key mod 3. И 1, и 4 соответствуют 1, но имеют разные значения. Вот где ваша идея списка вступает в игру.

Когда вам нужно выполнить поиск 1, это значение хэшируется до 1, список просматривается до тех пор, пока ключ = 1 не будет найден.

Если вы разрешите вставку дубликатов ключей, вы не сможете различить, какие ключи соответствуют каким значениям.

1
Nicholas Mancuso

Я изменил ответ @Hector Correa на расширение с универсальными типами, а также добавил собственный TryGetValue.

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

Я наткнулся на этот пост в поисках того же ответа и не нашел ни одного, поэтому я подготовил примерное решение с использованием списка словарей, переопределив оператор [], чтобы добавить новый словарь в список, когда все остальные имеют заданный ключ (установить) и вернуть список значений (получить).
Это уродливо и неэффективно, ТОЛЬКО получает/устанавливает по ключу, и всегда возвращает список, но работает:

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

Это параллельный словарь для буксировки, я думаю, это поможет вам:

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

примеры:

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
            }
0
Ali Yousefie

я использую этот простой класс:

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

использование:

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