279. Double keyed dictionary strikes back

בהמשך לפעם שעברה,

קיימת עוד שיטה לממש Dictionary רב מפתחות, ע”י שימוש בDictionary מקונן.

במקום להשתמש בDictionary כKey, ניתן להשתמש בDictionary שממפה את המפתחות שלו לDictionary אחר:

1
2
Dictionary<string, Dictionary<int, string>> stateAndIdToName =
new Dictionary<string, Dictionary<int, string>>();

למי שאי פעם נתקל בשוויון (יותר נכון איזומורפיזם):

$ {\left({A^B}\right)}^C \cong A^{B \times C} $

(מתורת הקבוצות, אבל אפשר להבין אותו גם מספרית)

השוויון הזה בדיוק אומר ששני המבנים האלה זהים במובן מסוים:

כלומר אם יש לי Dictionary שממפה זוג $ (x,y) $ לערך $ z $, אני יכול ליצור Dictionary מתאים שממפה את $ x $ לDictionary שממפה את $ y $ ל$ z $.

אם נכתוב מחלקה משלנו העוטפת שימוש זה (כמו פעם קודמת), זה יראה בערך ככה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class DoubleKeyDictionary<TKey1, TKey2, TValue> :
Dictionary<TKey1, IDictionary<TKey2, TValue>>
{
private IDictionary<TKey2, TValue> GetOrCreateInnerDictionary(TKey1 key1)
{
IDictionary<TKey2, TValue> innerDictionary;
if (!this.TryGetValue(key1, out innerDictionary))
{
innerDictionary = new Dictionary<TKey2, TValue>();
this[key1] = innerDictionary;
}
return innerDictionary;
}
public void Add(TKey1 key1, TKey2 key2, TValue value)
{
IDictionary<TKey2, TValue> innerDictionary =
GetOrCreateInnerDictionary(key1);
innerDictionary.Add(key2, value);
}
public TValue this[TKey1 key1, TKey2 key2]
{
get
{
return this[key1][key2];
}
set
{
IDictionary<TKey2, TValue> innerDictionary =
GetOrCreateInnerDictionary(key1);
innerDictionary[key2] = value;
}
}
public bool ContainsKey(TKey1 key1, TKey2 key2)
{
IDictionary<TKey2, TValue> innerDictionary;
return this.TryGetValue(key1, out innerDictionary) &&
innerDictionary.ContainsKey(key2);
}
public bool TryGetValue(TKey1 key1, TKey2 key2, out TValue result)
{
result = default(TValue);
IDictionary<TKey2, TValue> innerDictionary;
return this.TryGetValue(key1, out innerDictionary) &&
innerDictionary.TryGetValue(key2, out result);
}
}

המימוש קצת פחות אלגנטי מפעם קודמת, אבל יש פה יתרון עצום: אם אנחנו מעוניינים למצוא את כל הערכים שהם בעלי מפתח ראשון מסוים, נוכל לעשות זאת בקלות:

למשל נניח מעניין אותנו המיפוי של ת.ז. לשמות של כל האנשים בישראל, אז נוכל לקבל מידע זה כך:

1
IDictionary<int, string> israeliIdToName = stateAndIdToName["Israel"];

עם המימוש הקודם נצטרך לעבוד יותר קשה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ICollection<Tuple<string, int>> keys = stateAndIdToName.Keys;
IEnumerable<int> israeliIds =
from key in keys
where key.Item1 == "Isreal"
select key.Item2;
Dictionary<int, string> isrealiIdToName =
new Dictionary<int, string>();
foreach (int israeliId in israeliIds)
{
isrealiIdToName[israeliId] =
stateAndIdToName["Israel", israeliId];
}

שימו לב ששוב הירושה נותנת לנו בעיקר API יותר נוח, אבל ניתן ליצור Extension Methods מתאימים שיעשו API נוח אחר (אמנם בלי שם נוח וCollection Initializer, אבל עם מתודות נוחות מספיק)

שתף