2012-06-10 308 views
53

我想存儲在字典中的單詞方式如下:C#中的雙向/雙向字典?

我可以搭話三字代碼:dict["SomeWord"] - >123和字碼得到一句話:dict[123] - >"SomeWord"

是真的嗎?當然,有一種方法可以做到兩個字典:Dictionary<string,int>Dictionary<int,string>,但有沒有另外一種方法?

+0

沒有標準(如.NET 4)的數據類型,其提供O(1)訪問兩種方式... AFAIK :) – 2012-06-10 04:29:47

+0

而且不是雙向映射(關鍵字?)強加額外限制,除非多雙向地圖... – 2012-06-10 04:37:20

+1

http://stackoverflow.com/questions/1227683/bi-directional-dictionary,http://stackoverflow.com/questions/268321/bidirectional-1-to-1- dictionary-in-c-sharp – 2012-06-10 04:37:49

回答

71

我寫了一個快速幾個類,讓你做你想做的。您可能需要擴展更多功能,但這是一個很好的起點。

中使用的代碼看起來是這樣的:

var map = new Map<int, string>(); 

map.Add(42, "Hello"); 

Console.WriteLine(map.Forward[42]); 
// Outputs "Hello" 

Console.WriteLine(map.Reverse["Hello"]); 
//Outputs 42 

這裏的定義是:

public class Map<T1, T2> 
{ 
    private Dictionary<T1, T2> _forward = new Dictionary<T1, T2>(); 
    private Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>(); 

    public Map() 
    { 
     this.Forward = new Indexer<T1, T2>(_forward); 
     this.Reverse = new Indexer<T2, T1>(_reverse); 
    } 

    public class Indexer<T3, T4> 
    { 
     private Dictionary<T3, T4> _dictionary; 
     public Indexer(Dictionary<T3, T4> dictionary) 
     { 
      _dictionary = dictionary; 
     } 
     public T4 this[T3 index] 
     { 
      get { return _dictionary[index]; } 
      set { _dictionary[index] = value; } 
     } 
    } 

    public void Add(T1 t1, T2 t2) 
    { 
     _forward.Add(t1, t2); 
     _reverse.Add(t2, t1); 
    } 

    public Indexer<T1, T2> Forward { get; private set; } 
    public Indexer<T2, T1> Reverse { get; private set; } 
} 
+0

不錯,.NET仍然沒有他們的「地圖」解決方案? – Pedro77

+2

@ Pedro77 - 它現在。 ;-) – Enigmativity

+1

所以,你不喜歡我的編輯。你能分享一下我們這個新的.NET類嗎? – Pedro77

7

你可以使用兩個字典,如你所說,或者如果這兩個鍵和值是相同類型的,你可以只使用一個:

dict["SomeWord"]= "123"dict["123"]="SomeWord"所有使用它的查找。

+3

是的,這個方法在問題中得到了肯定:) – 2012-06-10 04:32:13

+2

這忽略了「keys」和「values」中存在相同值的可能性。那麼這個解決方案會衝突。 – user1028741

+0

@ user1028741同意,雖然從例如它似乎是指「不同類型的」非「相同類型的」 – Hutch

1

雖然它使用枚舉,但您可以使用此擴展方法,因此可能不適用於大型數據集。如果你擔心效率,那麼你需要兩本字典。如果你想兩個庫包裝成一個類,請參閱接受的答案對這個問題:Bidirectional 1 to 1 Dictionary in C#

public static class IDictionaryExtensions 
{ 
    public static TKey FindKeyByValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TValue value) 
    { 
     if (dictionary == null) 
      throw new ArgumentNullException("dictionary"); 

     foreach (KeyValuePair<TKey, TValue> pair in dictionary) 
      if (value.Equals(pair.Value)) return pair.Key; 

     throw new Exception("the value is not found in the dictionary"); 
    } 
} 
+6

雖然這是一個雙向的字典,取該值是一個爲O​​(n)操作,其中它應該是一個O(1)操作。這對於小數據集可能無關緊要,但在處理大數據集時可能會導致性能問題。對空間性能的最佳答案是使用反向數據的兩個字典。 – Tom

+0

@TomA我完全贊同湯姆,你需要一個真正的雙向字典的唯一實例是當你有100K,1M +的條目,更少的掃描它實際上是一個NOOP。 –

+0

我喜歡這種解決方案適合我的情況(小字典大小),因爲我仍然可以使用集合初始值設定項。地圖在接受的答案我不認爲可以用於收集初始值設定項。 – CVertex

0

它使用反向查找索引器。
反向查找是O(n),但它也不會超過1項字典實例使用兩個字典

public sealed class DictionaryDoubleKeyed : Dictionary<UInt32, string> 
{ // used UInt32 as the key as it has a perfect hash 
    // if most of the lookup is by word then swap 
    public void Add(UInt32 ID, string Word) 
    { 
     if (this.ContainsValue(Word)) throw new ArgumentException(); 
     base.Add(ID, Word); 
    } 
    public UInt32 this[string Word] 
    { // this will be O(n) 
     get 
     { 
      return this.FirstOrDefault(x => x.Value == Word).Key; 
     } 
    } 
} 
+0

例如:'this [string Word]'中可能的NRE。其他問題是變量名稱不符合常規做法,評論與代碼不一致('UInt16' vs'UInt32' - 這就是爲什麼:不使用註釋!),解決方案不是通用的,... – BartoszKP

0

以下包封類利用LINQ(IEnumerable的擴展)。

public class TwoWayDictionary<TKey, TValue> 
{ 
    readonly IDictionary<TKey, TValue> dict; 
    readonly Func<TKey, TValue> GetValueWhereKey; 
    readonly Func<TValue, TKey> GetKeyWhereValue; 
    readonly bool _mustValueBeUnique = true; 

    public TwoWayDictionary() 
    { 
     this.dict = new Dictionary<TKey, TValue>(); 
     this.GetValueWhereKey = (strValue) => dict.Where(kvp => Object.Equals(kvp.Key, strValue)).Select(kvp => kvp.Value).FirstOrDefault(); 
     this.GetKeyWhereValue = (intValue) => dict.Where(kvp => Object.Equals(kvp.Value, intValue)).Select(kvp => kvp.Key).FirstOrDefault(); 
    } 

    public TwoWayDictionary(KeyValuePair<TKey, TValue>[] kvps) 
     : this() 
    { 
     this.AddRange(kvps); 
    } 

    public void AddRange(KeyValuePair<TKey, TValue>[] kvps) 
    { 
     kvps.ToList().ForEach(kvp => {   
      if (!_mustValueBeUnique || !this.dict.Any(item => Object.Equals(item.Value, kvp.Value))) 
      { 
       dict.Add(kvp.Key, kvp.Value); 
      } else { 
       throw new InvalidOperationException("Value must be unique"); 
      } 
     }); 
    } 

    public TValue this[TKey key] 
    { 
     get { return GetValueWhereKey(key); } 
    } 

    public TKey this[TValue value] 
    { 
     get { return GetKeyWhereValue(value); } 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var dict = new TwoWayDictionary<string, int>(new KeyValuePair<string, int>[] { 
      new KeyValuePair<string, int>(".jpeg",100), 
      new KeyValuePair<string, int>(".jpg",101), 
      new KeyValuePair<string, int>(".txt",102), 
      new KeyValuePair<string, int>(".zip",103) 
     }); 


     var r1 = dict[100]; 
     var r2 = dict[".jpg"]; 

    } 

} 
2

Bictionary

下面是我在回答每一個喜歡攙和。它實現IEnumerable,因此它可以使用集合初始值設定項,如您在示例中所見。

使用限制:

  • 您正在使用不同的數據類型。 (即,T1T2

代碼:

using System; 
using System.Collections.Generic; 
using System.Linq; 

public class Program 
{ 
    public static void Main() 
    { 
     Bictionary<string, int> bictionary = 
      new Bictionary<string,int>() { 
       { "a",1 }, 
       { "b",2 }, 
       { "c",3 } 
      }; 

     // test forward lookup 
     Console.WriteLine(bictionary["b"]); 
     // test forward lookup error 
     //Console.WriteLine(bictionary["d"]); 
     // test reverse lookup 
     Console.WriteLine(bictionary[3]); 
     // test reverse lookup error (throws same error as forward lookup does) 
     Console.WriteLine(bictionary[4]); 
    } 
} 

public class Bictionary<T1, T2> : Dictionary<T1, T2> 
{ 
    public T1 this[T2 index] 
    { 
     get 
     { 
      if(!this.Any(x => x.Value.Equals(index))) 
       throw new System.Collections.Generic.KeyNotFoundException(); 
      return this.First(x => x.Value.Equals(index)).Key; 
     } 
    } 
} 

小提琴:

https://dotnetfiddle.net/mTNEuw

+0

非常優雅的解決方案!你能解釋一下它的膽量嗎?我是對的,即使所有的字符串都是唯一的,你也不能創建一個'Bictionary '? –

+0

@ Merlin2001,這是正確的。更確切地說,你不能用它進行前向查找。我將不得不考慮如何克服這一點。它會編譯,但是當'T1 == T2'時它總是首先找到反向索引器,所以前向查找失敗。此外,我無法重寫默認索引器,因爲查找調用會不明確。我添加了這個約束並刪除了前一個,因爲'T1'的值可以與'T2'的值重疊。 – toddmo

+3

背面存在相當嚴重的性能問題;字典被搜索兩次,O(n)表演命中;使用第二個字典會更快,並且會刪除類型約束。 –

2

通過添加初始化和Contains方法擴展Enigmativity代碼。

public class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>> 
{ 
    private readonly Dictionary<T1, T2> _forward = new Dictionary<T1, T2>(); 
    private readonly Dictionary<T2, T1> _reverse = new Dictionary<T2, T1>(); 

    public Map() 
    { 
     Forward = new Indexer<T1, T2>(_forward); 
     Reverse = new Indexer<T2, T1>(_reverse); 
    } 

    public Indexer<T1, T2> Forward { get; private set; } 
    public Indexer<T2, T1> Reverse { get; private set; } 

    public void Add(T1 t1, T2 t2) 
    { 
     _forward.Add(t1, t2); 
     _reverse.Add(t2, t1); 
    } 

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

    public IEnumerator<KeyValuePair<T1, T2>> GetEnumerator() 
    { 
     return _forward.GetEnumerator(); 
    } 

    public class Indexer<T3, T4> 
    { 
     private readonly Dictionary<T3, T4> _dictionary; 

     public Indexer(Dictionary<T3, T4> dictionary) 
     { 
      _dictionary = dictionary; 
     } 

     public T4 this[T3 index] 
     { 
      get { return _dictionary[index]; } 
      set { _dictionary[index] = value; } 
     } 

     public bool Contains(T3 key) 
     { 
      return _dictionary.ContainsKey(key); 
     } 
    } 
} 

下面是一個使用情況,檢查有效括號

public static class ValidParenthesisExt 
{ 
    private static readonly Map<char, char> 
     _parenthesis = new Map<char, char> 
     { 
      {'(', ')'}, 
      {'{', '}'}, 
      {'[', ']'} 
     }; 

    public static bool IsValidParenthesis(this string input) 
    { 
     var stack = new Stack<char>(); 
     foreach (var c in input) 
     { 
      if (_parenthesis.Forward.Contains(c)) 
       stack.Push(c); 
      else 
      { 
       if (stack.Count == 0) return false; 
       if (_parenthesis.Reverse[c] != stack.Pop()) 
        return false; 
      } 
     } 
     return stack.Count == 0; 
    } 
} 
0

這是一個老問題,但我想補充兩個擴展方法的情況下,任何人發現它是有用的。第二個不是很有用,但它提供了一個起點,如果需要支持一對一的字典。

public static Dictionary<VALUE,KEY> Inverse<KEY,VALUE>(this Dictionary<KEY,VALUE> dictionary) 
    { 
     if (dictionary==null || dictionary.Count == 0) { return null; } 

     var result = new Dictionary<VALUE, KEY>(dictionary.Count); 

     foreach(KeyValuePair<KEY,VALUE> entry in dictionary) 
     { 
      result.Add(entry.Value, entry.Key); 
     } 

     return result; 
    } 

    public static Dictionary<VALUE, KEY> SafeInverse<KEY, VALUE>(this Dictionary<KEY, VALUE> dictionary) 
    { 
     if (dictionary == null || dictionary.Count == 0) { return null; } 

     var result = new Dictionary<VALUE, KEY>(dictionary.Count); 

     foreach (KeyValuePair<KEY, VALUE> entry in dictionary) 
     { 
      if (result.ContainsKey(entry.Value)) { continue; } 

      result.Add(entry.Value, entry.Key); 
     } 

     return result; 
    }