2009-03-27 4 views
49

Я в основном ищу способ доступа к значению хэш-таблицы с использованием двумерного типизированного ключа в C#.Hashtable с многомерным ключом в C#

В конце концов, я мог бы сделать что-то вроде этого

HashTable[1][false] = 5; 
int a = HashTable[1][false]; 
//a = 5 

Это то, что я пытался ... не работал

Hashtable test = new Hashtable(); 
test.Add(new Dictionary<int, bool>() { { 1, true } }, 555); 
Dictionary<int, bool> temp = new Dictionary<int, bool>() {{1, true}}; 
string testz = test[temp].ToString(); 

ответ

60

Я думаю, что лучший подход заключается в инкапсуляции многих полей вашего многомерного ключа в класс/структуру. Например,

struct Key { 
    public readonly int Dimension1; 
    public readonly bool Dimension2; 
    public Key(int p1, bool p2) { 
    Dimension1 = p1; 
    Dimension2 = p2; 
    } 
    // Equals and GetHashCode ommitted 
} 

Теперь вы можете создать и использовать обычную HashTable и использовать эту оболочку в качестве ключа.

+9

Не забывайте, что вам нужно переопределить GetHashCode и Equals, чтобы использовать это в Hashtable. –

+7

@ Давид, не в этом случае. По умолчанию реализация Equals будет выполнять равенство во всех полях, которые будут работать в этом случае. GetHashcode может быть не так эффективен, как хотелось бы пользователю, но он также будет работать с реализацией по умолчанию. – JaredPar

+5

@ Давид, это, как говорится, обычно является хорошей практикой на самом деле. – JaredPar

11

Я думаю, что это может быть ближе к что вы ищете ...

var data = new Dictionary<int, Dictionary<bool, int>>(); 
1

Я бы предположил, что вы создаете небольшой пользовательский класс, отображающий параметры bool и int properti es и переопределить его методы GetHashCode и Equals, а затем использовать это как ключ.

0

Возможно, вы сможете «удвоить» свои хеш-таблицы - другими словами, ваш основной словарь имеет тип Dictionary<int, Dictionary<bool, my_return_type>>.

Это позволяет вам использовать нотацию двойной скобки в вашем первом фрагменте кода.

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

22

Как насчет использования обычного словаря с какой-либо структурой Tuple в качестве ключа?

public class TwoKeyDictionary<K1,K2,V> 
{ 
    private readonly Dictionary<Pair<K1,K2>, V> _dict; 

    public V this[K1 k1, K2 k2] 
    { 
     get { return _dict[new Pair(k1,k2)]; } 
    } 

    private struct Pair 
    { 
     public K1 First; 
     public K2 Second; 

     public override Int32 GetHashCode() 
     { 
      return First.GetHashCode()^Second.GetHashCode(); 
     } 

     // ... Equals, ctor, etc... 
    } 
} 
+5

Вам не нужно реализовывать пару, используйте System.Tuple – Mugen

+5

@ Mugen Tuples не было в '09. – Defenestrator

0

Не могли бы вы использовать Dictionary<KeyValuePair<int,bool>,int>?

0

Оберните свой двумерный ключ в отдельный type и используйте этот тип в качестве ключа. Также рассмотрите переопределение GetHashCode() и Equals() методов. Предпочтительно использовать Dictionary<> вместо HashTable, поскольку, по-видимому, вы можете использовать это.

0

Здесь example, вы можете использовать обычный Hashtable вместо того, который я использовал.

0

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

IDictionary<string, int> values = new Dictionary<string, int>(); 
int i = ...; 
bool b = ...; 
string key = string.Concat(i, '\0', b); 
values[key] = 555; 

Чтобы инкапсулировать это немного лучше, вы можете обернуть словарь:

public class MyDict 
{ 
    private readonly IDictionary<string, int> values = new Dictionary<string, int>(); 

    public int this[int i, bool b] 
    { 
     get 
     { 
      string key = BuildKey(i, b); 
      return values[key]; 
     } 

     set 
     { 
      string key = BuildKey(i, b); 
      values[key] = value; 
     } 
    } 

    private static string BuildKey(int i, bool b) 
    { 
     return string.Concat(i, '\0', b); 
    } 
} 

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

+0

Две части ключа. В исходном сообщении это было целое число 1 и логическое значение false, поэтому я связал эти два в примере кода. (В этом примере ограничитель не является строго необходимым.) –

4

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

класс пары ключей

public class KeyPair<Tkey1, Tkey2> 
{ 
    public KeyPair(Tkey1 key1, Tkey2 key2) 
    { 
     Key1 = key1; 
     Key2 = key2; 
    } 

    public Tkey1 Key1 { get; set; } 
    public Tkey2 Key2 { get; set; } 

    public override int GetHashCode() 
    { 
     return Key1.GetHashCode()^Key2.GetHashCode(); 
    } 
    public override bool Equals(object obj) 
    { 
     KeyPair<Tkey1, Tkey2> o = obj as KeyPair<Tkey1, Tkey2>; 
     if (o == null) 
      return false; 
     else 
      return Key1.Equals(o.Key1) && Key2.Equals(o.Key2); 
    } 
} 

расширить справочник <>

public class KeyPairDictonary<Tkey1, Tkey2, Tvalue> 
    : Dictionary<KeyPair<Tkey1, Tkey2>, Tvalue> 
{ 
    public Tvalue this[Tkey1 key1, Tkey2 key2] 
    { 
     get 
     { 
      return this[new KeyPair<Tkey1, Tkey2>(key1, key2)]; 
     } 
     set 
     { 
      this[new KeyPair<Tkey1, Tkey2>(key1, key2)] = value; 
     } 
    } 
} 

и использовать его как этот

 KeyPairDictonary<int, bool, string> dict = 
      new KeyPairDictonary<int, bool, string>(); 
     dict[1, false] = "test"; 
     string test = dict[1, false]; 
5

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

public class MultiDictionary<K1, K2, V> 
{ 
    private Dictionary<K1, Dictionary<K2, V>> dict = 
     new Dictionary<K1, Dictionary<K2, V>>(); 

    public V this[K1 key1, K2 key2] 
    { 
     get 
     { 
      return dict[key1][key2]; 
     } 

     set 
     { 
      if (!dict.ContainsKey(key1)) 
      { 
       dict[key1] = new Dictionary<K2, V>(); 
      } 
      dict[key1][key2] = value; 
     } 
    } 
} 
+3

Почему? Я подумал об этом на секунду, но мне это кажется хуже, потому что у него большие накладные расходы (большее давление в ГК, худшая локальность), и нет никакой пользы для него - поиск в хэш-таблице имеет постоянную эффективность во времени в среднем, поэтому нет что нужно сделать дважды. – 2009-03-27 22:01:54

+3

Не для каких-либо причин эффективности, а потому, что я думаю, что код проще (посмотрите, что вам нужно было преодолеть). Если эффективность является главной проблемой, я согласен с тем, что ваше оригинальное решение лучше. Мне не нравятся типы, такие как Pair , особенно когда .NET 4.0 будет содержать кортежи из коробки. – 2009-04-01 23:44:26

+1

@jachymko Если эффективность определяется как скорость, а не как использование памяти, то прямой словарь в словарном решении, использующий более простые типы, на самом деле имеет тенденцию работать быстрее. Использование 'struct' или других составных ключей, созданных типами, приводит к снижению производительности по причинам, которые я еще не определил. –

0

Посмотрите, этот код работает отлично:

public Form1() 
    { 
      InitializeComponent(); 
    } 

    private void Form1_Load(object sender, EventArgs e) 
    { 

     this.Services = new Dictionary<object, Hashtable>(); 
     this.Services.Add("array1", new Hashtable()); 

     this.Services["array1"]["qwe"] = "123"; 
     this.Services["array1"][22] = 223; 

     object zz = null; 
     zz = this.Services["array1"]["qwe"]; 
     MessageBox.Show(zz.ToString()); // shows qwe 

     zz = this.Services["array1"][22]; 
     MessageBox.Show(zz.ToString()); // shows 22 
    } 

Теперь нам просто нужно обертку, чтобы избежать вручную делать this.Services.Add («array1», новый Hashtable());

1

По существу вам необходимо использовать встроенную хеш-таблицу. Если вы думаете о своем вопросе , хэш-таблица с двумя ключами является функцией с двумя независимыми переменными , а f(x,y) является двумерным по определению.

Но вы хотите использовать его, как будто это одна хеш-таблица, а не встроенные хеши. Так что вам нужно сделать, это создать объект, который обертывает эту встроенную идею хэш-таблицы и работает как один хэш.

пару коряг:

  • Вы хотите перебрать его, так что вам нужно переписать метод GetEnumerator(). И вам нужен собственный Итератор, который будет правильно итератировать в двух измерениях.
  • Вам нужно сделать больше проверок, чтобы убедиться, что дубликатов нет.

Я включил свой код, чтобы сделать это:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Collections; 
using System.Windows.Forms; 

namespace YourProjectNameHere 
{ 
    public class Hashtable2D 
    { 
     /// <summary> 
     /// This is a hashtable of hashtables 
     /// The X dim is the root key, and the y is the internal hashes key 
     /// </summary> 
     /// 
     private Hashtable root = new Hashtable(); 
     public bool overwriteDuplicates = false; 
     public bool alertOnDuplicates = true; 

     public void Add(object key_x, object key_y, object toStore) 
     { 
      if(root[key_x]!=null)//If key_x has already been entered 
      { 
       Hashtable tempHT = (Hashtable)root[key_x];//IF the hash table does not exist then focus will skip to the catch statement 
       if (tempHT[key_y] == null) tempHT.Add(key_y, toStore); 
       else handleDuplicate(tempHT, key_y, toStore); 
      }else{//Making a new hashtable 
       Hashtable tempHT = new Hashtable(); 
       tempHT.Add(key_y, toStore); 
       root.Add(key_x, tempHT); 
      } 

     } 

     public void Remove(object key_x, object key_y) 
     { 
      try{ 
       ((Hashtable)root[key_x]).Remove(key_y); 
      }catch(Exception e){ 
       MessageBox.Show("That item does not exist"); 
      } 

     } 

     public void handleDuplicate (Hashtable tempHT, object key_y, object toStore) 
     { 
      if (alertOnDuplicates) MessageBox.Show("This Item already Exists in the collection"); 

      if (overwriteDuplicates) 
      { 
       tempHT.Remove(key_y); 
       tempHT.Add(key_y,toStore); 
      } 
     } 

     public object getItem(object key_x, object key_y) 
     { 
      Hashtable tempHT = (Hashtable)root[key_x]; 
      return tempHT[key_y]; 
     } 

     public ClassEnumerator GetEnumerator() 
     { 
      return new ClassEnumerator(root); 
     } 

     public class ClassEnumerator : IEnumerator 
     { 
      private Hashtable ht; 
      private IEnumerator iEnumRoot; 
      private Hashtable innerHt; 
      private IEnumerator iEnumInner; 

      public ClassEnumerator(Hashtable _ht) 
      { 
       ht = _ht; 
       iEnumRoot = ht.GetEnumerator(); 

       iEnumRoot.MoveNext();//THIS ASSUMES THAT THERE IS AT LEAST ONE ITEM 

       innerHt = (Hashtable)((DictionaryEntry)iEnumRoot.Current).Value; 
       iEnumInner = innerHt.GetEnumerator(); 
      } 

      #region IEnumerator Members 

      public void Reset() 
      { 
       iEnumRoot = ht.GetEnumerator(); 
      } 

      public object Current 
      { 
       get 
       { 
        return iEnumInner.Current; 
       } 
      } 

      public bool MoveNext() 
      { 
       if(!iEnumInner.MoveNext()) 
       { 
        if (!iEnumRoot.MoveNext()) return false; 
        innerHt = (Hashtable)((DictionaryEntry)iEnumRoot.Current).Value; 
        iEnumInner = innerHt.GetEnumerator(); 
        iEnumInner.MoveNext(); 
       } 
       return true; 
      } 

      #endregion 
     } 

    } 
} 
13

Только в случае, если кто-то здесь в последнее время, пример того, как сделать это быстрый и грязный путь в .Net 4.0, как описано один из комментаторов.

class Program 
{ 
    static void Main(string[] args) 
    { 
    var twoDic = new Dictionary<Tuple<int, bool>, String>(); 
    twoDic.Add(new Tuple<int, bool>(3, true), "3 and true."); 
    twoDic.Add(new Tuple<int, bool>(4, true), "4 and true."); 
    twoDic.Add(new Tuple<int, bool>(3, false), "3 and false."); 

    // Will throw exception. Item with the same key already exists. 
    // twoDic.Add(new Tuple<int, bool>(3, true), "3 and true."); 

    Console.WriteLine(twoDic[new Tuple<int, bool>(3,false)]); 
    Console.WriteLine(twoDic[new Tuple<int, bool>(4,true)]); 
    // Outputs "3 and false." and "4 and true." 
    } 
} 
3

Вы можете сделать это в C# 7.0 теперь с новыми кортежей:

// Declare 
var test = new Dictionary<(int, bool), int>(); 

// Add 
test.Add((1, false), 5); 

// Get 
int a = test[(1, false)]; 
Смежные вопросы