Многоязычные словари (другого рода) в С#?

Основываясь на этом вопросе, есть ли простое решение для наличия многокнопочного словаря, где каждый ключ может использоваться для идентификации значения?

т.

MultikeyDictionary<TKey1, TKey2, TValue> foo;
foo.Add(key1, key2, value);
myValue = foo[key1];
// value == myValue
foo.Remove(key2);
myValue = foo[key1]; // invalid, Exception or null returned

Ответы

Ответ 1

Это сообщение в блоге, кажется, детализирует довольно приличную реализацию.

Многоязычный общий класс словаря для С#

MultiKeyDictionary - это класс С# который обертывает и расширяет общий Объект словаря, предоставленный Microsoft в .NET 2.0 и выше. Эта позволяет разработчику создавать общий словарь значений и ссылку на список значений через два ключа вместо только тот, который предоставлен Microsoft реализации Общего Словарь и л... > . Вы можете увидеть мою статья о CodeProject (здесь), однако этот код более актуальный и ошибка свободный.

Ответ 2

Да, определите класс, который добавляет объект во внутреннюю хэш-таблицу с двумя ключами,

 public MyClass<k1, k2, T>: Dictionary<object, T>
  {
      private Dictionary<k1, k2> keyMap;
      public new Add(k1 key1Val, k2 key2Val, T object)
      {
         keyMap.Add(key1Val, key2Val);
         base.Add(k2, object)
      }
      public Remove(k1 key1Val) 
      { 
          base.Remove(keyMap[key1Val]); 
          keyMap.Remove(key1Val);
      }
      public Remove(k2 key2Val) 
      { 
        base.Remove(key2Val);
        keyMap.Remove(key2Val);
      }
  }

Ответ 3

В данный момент коллекция BC BC для этого типа коллекции ничего не встроена.

Я вижу два варианта:

  • Используйте двухуровневый словарь. Первый уровень отображает разные ключи на один общий уникальный ключ (пусть GUID), а второй уровень отображает GUID на фактическое значение.

  • Создайте собственный класс ключей и реализуйте Equals() и GetHashCode(), чтобы любой один компонент ключа был достаточным для нахождения всего ключа. Затем вы можете предоставить вспомогательные методы для создания экземпляров ключа, используя только одно из значений, чтобы вы могли выполнять поиск.

Ответ 4

Еще одна простая (и эффективная) реализация заключается в использовании PowerCollections 'Pair<TFirst, TSecond> типа в качестве словарного ключа, что-то вроде

Dictionary<Pair<TKey1, TKey2>, TValue> foo;
foo.Add(new Pair<TKey1, TKey2>(key1, key2), value);

Пара < > последовательно реализует Equals и GetHashCode, поэтому вам не нужно прибегать к многоуровневым словарям (которые являются более громоздкими и, вероятно, менее эффективными).

Также существует Triple<TFirst, TSecond, TThird>, если вам нужен словарь с 3 ключами.

Ответ 5

Я пробовал это, и он отлично работает (включая add, remove и indexer)

public class MultikeyDictionary<K1, K2, V> : Dictionary<KeyValuePair<K1, K2>, V>
{
    public V this[K1 index1, K2 index2]
    {
        get
        {
            return this[new KeyValuePair<K1, K2>(index1, index2)];
        }
        set
        {
            this[new KeyValuePair<K1, K2>(index1, index2)] = value;
        }
    }

    public bool Remove(K1 index1, K2 index2)
    {
        return base.Remove(new KeyValuePair<K1,K2>(index1, index2));
    }

    public void Add(K1 index1, K2 index2, V value)
    {
        base.Add(new KeyValuePair<K1, K2>(index1, index2), value);
    }
}

и даже я расширил его до 4 значений:

public class MultikeyDictionary<K1, K2, K3, V> : MultikeyDictionary<KeyValuePair<K1, K2>, K3, V>
{
    public V this[K1 index1, K2 index2, K3 index3]
    {
        get
        {
            return base[new KeyValuePair<K1, K2>(index1, index2), index3];
        }
        set
        {
            base[new KeyValuePair<K1, K2>(index1, index2), index3] = value;
        }
    }

    public bool Remove(K1 index1, K2 index2, K3 index3)
    {
        return base.Remove(new KeyValuePair<K1, K2>(index1, index2), index3);
    }

    public void Add(K1 index1, K2 index2, K3 index3, V value)
    {
        base.Add(new KeyValuePair<K1, K2>(index1, index2), index3, value);
    }
}

Enjoy,

Офир

Ответ 6

Я нахожу много ответов здесь излишне сложными, менее эффективными или непригодными для использования. Наилучшим подходом было бы иметь KeyValuePair<> вторичного ключа и значение, сгруппированное вместе как Value обоих словарей. Это позволяет вам только один поиск для операций удаления и обновления. Простая реализация:

public class DualDictionary<TKey1, TKey2, TValue> : IEnumerable<KeyValuePair<Tuple<TKey1, TKey2>, TValue>>
{
    Dictionary<TKey1, KeyValuePair<TKey2, TValue>> _firstKeys;
    Dictionary<TKey2, KeyValuePair<TKey1, TValue>> _secondKeys;

    public int Count
    {
        get
        {
            if (_firstKeys.Count != _secondKeys.Count)
                throw new Exception("somewhere logic went wrong and your data got corrupt");

            return _firstKeys.Count;
        }
    }

    public ICollection<TKey1> Key1s
    {
        get { return _firstKeys.Keys; }
    }

    public ICollection<TKey2> Key2s
    {
        get { return _secondKeys.Keys; }
    }

    public IEnumerable<TValue> Values
    {
        get { return this.Select(kvp => kvp.Value); }
    }

    public DualDictionary(IEqualityComparer<TKey1> comparer1 = null, IEqualityComparer<TKey2> comparer2 = null)
    {
        _firstKeys = new Dictionary<TKey1, KeyValuePair<TKey2, TValue>>(comparer1);
        _secondKeys = new Dictionary<TKey2, KeyValuePair<TKey1, TValue>>(comparer2);
    }



    public bool ContainsKey1(TKey1 key)
    {
        return ContainsKey(key, _firstKeys);
    }

    private static bool ContainsKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
    {
        return dict.ContainsKey(key);
    }

    public bool ContainsKey2(TKey2 key)
    {
        return ContainsKey(key, _secondKeys);
    }

    public TValue GetValueByKey1(TKey1 key)
    {
        return GetValueByKey(key, _firstKeys);
    }

    private static TValue GetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
    {
        return dict[key].Value;
    }

    public TValue GetValueByKey2(TKey2 key)
    {
        return GetValueByKey(key, _secondKeys);
    }

    public bool TryGetValueByKey1(TKey1 key, out TValue value)
    {
        return TryGetValueByKey(key, _firstKeys, out value);
    }

    private static bool TryGetValueByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, out TValue value)
    {
        KeyValuePair<T, TValue> otherPairing;
        bool b = TryGetValue(key, dict, out otherPairing);
        value = otherPairing.Value;
        return b;
    }

    private static bool TryGetValue<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict,
                                          out KeyValuePair<T, TValue> otherPairing)
    {
        return dict.TryGetValue(key, out otherPairing);
    }

    public bool TryGetValueByKey2(TKey2 key, out TValue value)
    {
        return TryGetValueByKey(key, _secondKeys, out value);
    }

    public bool Add(TKey1 key1, TKey2 key2, TValue value)
    {
        if (ContainsKey1(key1) || ContainsKey2(key2))   // very important
            return false;

        AddOrUpdate(key1, key2, value);
        return true;
    }

    // dont make this public; a dangerous method used cautiously in this class
    private void AddOrUpdate(TKey1 key1, TKey2 key2, TValue value)
    {
        _firstKeys[key1] = new KeyValuePair<TKey2, TValue>(key2, value);
        _secondKeys[key2] = new KeyValuePair<TKey1, TValue>(key1, value);
    }

    public bool UpdateKey1(TKey1 oldKey, TKey1 newKey)
    {
        return UpdateKey(oldKey, _firstKeys, newKey, (key1, key2, value) => AddOrUpdate(key1, key2, value));
    }

    private static bool UpdateKey<S, T>(S oldKey, Dictionary<S, KeyValuePair<T, TValue>> dict, S newKey,
                                        Action<S, T, TValue> updater)
    {
        KeyValuePair<T, TValue> otherPairing;
        if (!TryGetValue(oldKey, dict, out otherPairing) || ContainsKey(newKey, dict))
            return false;

        Remove(oldKey, dict);
        updater(newKey, otherPairing.Key, otherPairing.Value);
        return true;
    }

    public bool UpdateKey2(TKey2 oldKey, TKey2 newKey)
    {
        return UpdateKey(oldKey, _secondKeys, newKey, (key1, key2, value) => AddOrUpdate(key2, key1, value));
    }

    public bool UpdateByKey1(TKey1 key, TValue value)
    {
        return UpdateByKey(key, _firstKeys, (key1, key2) => AddOrUpdate(key1, key2, value));
    }

    private static bool UpdateByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict, Action<S, T> updater)
    {
        KeyValuePair<T, TValue> otherPairing;
        if (!TryGetValue(key, dict, out otherPairing))
            return false;

        updater(key, otherPairing.Key);
        return true;
    }

    public bool UpdateByKey2(TKey2 key, TValue value)
    {
        return UpdateByKey(key, _secondKeys, (key1, key2) => AddOrUpdate(key2, key1, value));
    }

    public bool RemoveByKey1(TKey1 key)
    {
        return RemoveByKey(key, _firstKeys, _secondKeys);
    }

    private static bool RemoveByKey<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> keyDict,
                                          Dictionary<T, KeyValuePair<S, TValue>> valueDict)
    {
        KeyValuePair<T, TValue> otherPairing;
        if (!TryGetValue(key, keyDict, out otherPairing))
            return false;

        if (!Remove(key, keyDict) || !Remove(otherPairing.Key, valueDict))
            throw new Exception("somewhere logic went wrong and your data got corrupt");

        return true;
    }

    private static bool Remove<S, T>(S key, Dictionary<S, KeyValuePair<T, TValue>> dict)
    {
        return dict.Remove(key);
    }

    public bool RemoveByKey2(TKey2 key)
    {
        return RemoveByKey(key, _secondKeys, _firstKeys);
    }

    public void Clear()
    {
        _firstKeys.Clear();
        _secondKeys.Clear();
    }

    public IEnumerator<KeyValuePair<Tuple<TKey1, TKey2>, TValue>> GetEnumerator()
    {
        if (_firstKeys.Count != _secondKeys.Count)
            throw new Exception("somewhere logic went wrong and your data got corrupt");

        return _firstKeys.Select(kvp => new KeyValuePair<Tuple<TKey1, TKey2>, TValue>(Tuple.Create(kvp.Key, kvp.Value.Key),
                                                                                      kvp.Value.Value)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Несколько заметок:

  • Я реализовал только IEnumerable<>. Я не думаю, что ICollection<> имеет смысл здесь, так как имена методов все могут быть разными для этой специальной структуры коллекции. Вам решать, что должно идти внутри IEnumerable<>.

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

  • Я назвал методы таким образом, что его можно скомпилировать, даже если Key1 и Key2 имеют один и тот же тип.

  • Производительность: вы можете искать Value с любым из Key s. Get и Contains требуют всего 1 поиск (O (1)). Add требует 2 поисковых запросов и 2 добавления. Update требует 1 поиск и 2 добавления. Remove занимает 3 поисковых запроса.

Ответ 7

Конечно, это язык OO, и вы можете реализовать все, что захотите. У вас будет некоторая двусмысленность для решения (что, если TKey1 и TKey2 - это тот же тип, какие методы вызываются тогда?)

Ответ 8

Вы не сможете определить перегрузки для обоих типов, а система generics не допускает произвольного количества типов (например, методы разрешают параметры). Таким образом, вы застряли бы с набором классов, которые определяли бы одновременные ключи 2, 3, 4 и т.д. Кроме того, вам нужно будет использовать объект в качестве параметра для get и set, используя проверки типа времени выполнения для имитации перегрузки.

Кроме того, вы сохранили бы только один словарь <TKEY1,VAL>, другие словари были бы <TKEY2,TKEY1>, <TKEY3,TKEY1> и действовали бы как индексы в главном словаре.

В основном это код котловой плиты.

Ответ 9

Вы можете найти мою IndexMap, чтобы быть хорошей базой для переписывания ее с Java на С#. Модель программирования не такая элегантная, как я бы предпочел, но она не предназначена для разработки напрямую. Скорее, он находится за библиотекой кэширования, которая предоставляет стандартные аннотации, чтобы обеспечить краткий стиль кодирования. Используя интерфейс карты, он обеспечивает чистую композиционную модель, комбинируя ее с самонастраивающимися, экспирационными и высекаемыми декораторами карт. Я уверен, что кто-то может придумать хороший программный интерфейс для прямого использования, где приемлемо потерять преимущество интерфейса карты.