Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Collections/3.3/BidirectionalLookup.cs @ 9000

Last change on this file since 9000 was 9000, checked in by abeham, 11 years ago

#1991:

  • Added BidirectionalDictionary for storing 1:1 mapping and BidirectionalLookup for storing M:N mappings
    • I used HashSets in the BidirectionalLookup instead of Lists, because of the difficulty of removing items when custom equality comparers are used.
  • Added unit tests
File size: 7.7 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System.Collections.Generic;
23using System.Linq;
24using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
25
26namespace HeuristicLab.Collections {
27  [StorableClass]
28  public class BidirectionalLookup<TFirst, TSecond> {
29    [Storable]
30    private readonly Dictionary<TFirst, HashSet<TSecond>> firstToSecond;
31    [Storable]
32    private readonly Dictionary<TSecond, HashSet<TFirst>> secondToFirst;
33
34    [StorableConstructor]
35    protected BidirectionalLookup(bool deserializing) : base() { }
36    public BidirectionalLookup() {
37      firstToSecond = new Dictionary<TFirst, HashSet<TSecond>>();
38      secondToFirst = new Dictionary<TSecond, HashSet<TFirst>>();
39    }
40    public BidirectionalLookup(IEqualityComparer<TFirst> firstComparer) {
41      firstToSecond = new Dictionary<TFirst, HashSet<TSecond>>(firstComparer);
42      secondToFirst = new Dictionary<TSecond, HashSet<TFirst>>();
43    }
44    public BidirectionalLookup(IEqualityComparer<TSecond> secondComparer) {
45      firstToSecond = new Dictionary<TFirst, HashSet<TSecond>>();
46      secondToFirst = new Dictionary<TSecond, HashSet<TFirst>>(secondComparer);
47    }
48    public BidirectionalLookup(IEqualityComparer<TFirst> firstComparer, IEqualityComparer<TSecond> secondComparer) {
49      firstToSecond = new Dictionary<TFirst, HashSet<TSecond>>(firstComparer);
50      secondToFirst = new Dictionary<TSecond, HashSet<TFirst>>(secondComparer);
51    }
52
53    #region Properties
54    public int CountFirst {
55      get { return firstToSecond.Count; }
56    }
57
58    public int CountSecond {
59      get { return secondToFirst.Count; }
60    }
61
62    public IEnumerable<IEnumerable<TSecond>> FirstValues {
63      get { return firstToSecond.Values.AsEnumerable(); }
64    }
65
66    public IEnumerable<IEnumerable<TFirst>> SecondValues {
67      get { return secondToFirst.Values.AsEnumerable(); }
68    }
69
70    public IEnumerable<IGrouping<TFirst, TSecond>> FirstEnumerable {
71      get { return firstToSecond.Select(x => new StorableGrouping<TFirst, TSecond>(x.Key, x.Value, secondToFirst.Comparer)); }
72    }
73
74    public IEnumerable<IGrouping<TSecond, TFirst>> SecondEnumerable {
75      get { return secondToFirst.Select(x => new StorableGrouping<TSecond, TFirst>(x.Key, x.Value, firstToSecond.Comparer)); }
76    }
77    #endregion
78
79    #region Methods
80    public void Add(TFirst firstValue, TSecond secondValue) {
81      HashSet<TSecond> firstSet;
82      if (!firstToSecond.TryGetValue(firstValue, out firstSet)) {
83        firstSet = new HashSet<TSecond>(secondToFirst.Comparer);
84        firstToSecond[firstValue] = firstSet;
85      }
86      HashSet<TFirst> secondSet;
87      if (!secondToFirst.TryGetValue(secondValue, out secondSet)) {
88        secondSet = new HashSet<TFirst>(firstToSecond.Comparer);
89        secondToFirst[secondValue] = secondSet;
90      }
91      firstSet.Add(secondValue);
92      secondSet.Add(firstValue);
93    }
94
95    public void AddRangeFirst(TFirst firstValue, IEnumerable<TSecond> secondValues) {
96      HashSet<TSecond> firstSet;
97      if (!firstToSecond.TryGetValue(firstValue, out firstSet)) {
98        firstSet = new HashSet<TSecond>(secondToFirst.Comparer);
99        firstToSecond[firstValue] = firstSet;
100      }
101      foreach (var s in secondValues) {
102        HashSet<TFirst> secondSet;
103        if (!secondToFirst.TryGetValue(s, out secondSet)) {
104          secondSet = new HashSet<TFirst>(firstToSecond.Comparer);
105          secondToFirst[s] = secondSet;
106        }
107        firstSet.Add(s);
108        secondSet.Add(firstValue);
109      }
110    }
111
112    public void AddRangeSecond(TSecond secondValue, IEnumerable<TFirst> firstValues) {
113      HashSet<TFirst> secondSet;
114      if (!secondToFirst.TryGetValue(secondValue, out secondSet)) {
115        secondSet = new HashSet<TFirst>(firstToSecond.Comparer);
116        secondToFirst[secondValue] = secondSet;
117      }
118      foreach (var f in firstValues) {
119        HashSet<TSecond> firstSet;
120        if (!firstToSecond.TryGetValue(f, out firstSet)) {
121          firstSet = new HashSet<TSecond>(secondToFirst.Comparer);
122          firstToSecond[f] = firstSet;
123        }
124        firstSet.Add(secondValue);
125        secondSet.Add(f);
126      }
127    }
128
129    public bool ContainsFirst(TFirst firstValue) {
130      return firstToSecond.ContainsKey(firstValue);
131    }
132
133    public bool ContainsSecond(TSecond secondValue) {
134      return secondToFirst.ContainsKey(secondValue);
135    }
136
137    public IEnumerable<TSecond> GetByFirst(TFirst firstValue) {
138      return firstToSecond[firstValue];
139    }
140
141    public IEnumerable<TFirst> GetBySecond(TSecond secondValue) {
142      return secondToFirst[secondValue];
143    }
144
145    public void SetByFirst(TFirst firstValue, IEnumerable<TSecond> secondValues) {
146      RemoveByFirst(firstValue);
147      AddRangeFirst(firstValue, secondValues);
148    }
149
150    public void SetBySecond(TSecond secondValue, IEnumerable<TFirst> firstValues) {
151      RemoveBySecond(secondValue);
152      AddRangeSecond(secondValue, firstValues);
153    }
154
155    public void RemovePair(TFirst first, TSecond second) {
156      if (!ContainsFirst(first) || !ContainsSecond(second)) return;
157      firstToSecond[first].Remove(second);
158      if (!firstToSecond[first].Any()) firstToSecond.Remove(first);
159      secondToFirst[second].Remove(first);
160      if (!secondToFirst[second].Any()) secondToFirst.Remove(second);
161    }
162
163    public void RemoveByFirst(TFirst firstValue) {
164      if (!ContainsFirst(firstValue)) return;
165      var secondValues = firstToSecond[firstValue].ToArray();
166      firstToSecond.Remove(firstValue);
167      foreach (var s in secondValues) {
168        secondToFirst[s].Remove(firstValue);
169        if (!secondToFirst[s].Any()) secondToFirst.Remove(s);
170      }
171    }
172
173    public void RemoveBySecond(TSecond secondValue) {
174      if (!ContainsSecond(secondValue)) return;
175      var firstValues = secondToFirst[secondValue].ToArray();
176      secondToFirst.Remove(secondValue);
177      foreach (var f in firstValues) {
178        firstToSecond[f].Remove(secondValue);
179        if (!firstToSecond[f].Any()) firstToSecond.Remove(f);
180      }
181    }
182
183    public void Clear() {
184      firstToSecond.Clear();
185      secondToFirst.Clear();
186    }
187    #endregion
188
189    [StorableClass]
190    private class StorableGrouping<TKey, TValue> : IGrouping<TKey, TValue> {
191
192      [Storable]
193      private readonly TKey key;
194      [Storable]
195      private readonly HashSet<TValue> values;
196
197      public StorableGrouping(TKey key, IEnumerable<TValue> values, IEqualityComparer<TValue> comparer) {
198        this.key = key;
199        this.values = new HashSet<TValue>(values, comparer);
200      }
201
202      public TKey Key {
203        get { return key; }
204      }
205
206      public IEnumerator<TValue> GetEnumerator() {
207        return values.GetEnumerator();
208      }
209
210      System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
211        return GetEnumerator();
212      }
213    }
214  }
215}
Note: See TracBrowser for help on using the repository browser.