Skip to content

Commit fd100c8

Browse files
ClémentClément
authored andcommitted
Working on dictionary implementation.
1 parent ed5461b commit fd100c8

4 files changed

Lines changed: 450 additions & 147 deletions

File tree

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
public class CDictionary<TKey, TValue>
5+
where TKey : IComparable<TKey>
6+
{
7+
// Two enumerated types that we will be using
8+
// for our implementation of Dictionary.
9+
public enum StatusType
10+
{
11+
Empty,
12+
Active,
13+
Deleted,
14+
};
15+
16+
public enum CollisionRes
17+
{
18+
Linear,
19+
Quad,
20+
Double,
21+
};
22+
23+
private class Cell
24+
{
25+
public StatusType Status { get; set; }
26+
public TValue Value { get; set; }
27+
public TKey Key { get; set; }
28+
29+
public Cell(
30+
TKey aKey = default(TKey),
31+
TValue aValue = default(TValue),
32+
StatusType aStatus = StatusType.Empty
33+
)
34+
{
35+
Key = aKey;
36+
Value = aValue;
37+
Status = aStatus;
38+
}
39+
40+
public override string ToString()
41+
{
42+
return Key + ":" + Value;
43+
}
44+
}
45+
46+
// The hash table is an array of Cells,
47+
// and a collision strategy.
48+
private Cell[] table;
49+
private readonly CollisionRes Strategy;
50+
51+
public override string ToString()
52+
{
53+
string returned = "";
54+
int i = 0;
55+
foreach (Cell pos in table)
56+
{
57+
returned += $"Position {i}: {pos}\n";
58+
i++;
59+
}
60+
return returned;
61+
}
62+
63+
public CDictionary(
64+
int size = 31,
65+
CollisionRes aCollisionStrategy = CollisionRes.Double
66+
)
67+
{
68+
table = new Cell[PrimeHelper.NextPrime(size)];
69+
Strategy = aCollisionStrategy;
70+
}
71+
72+
public void Clear()
73+
{
74+
for (int i = 0; i < table.Length; i++)
75+
if (table[i] != null)
76+
table[i].Status = StatusType.Deleted; // Reuse cells by setting them to Empty
77+
}
78+
79+
public void Add(TKey aKey, TValue aValue)
80+
{
81+
/*
82+
* First, we find an empty cell (e.g. cell is null, status empty or deleted)
83+
* - We computer a possible index:
84+
* - We first use GetHashCode() to generate a hash code,
85+
* - then shift it using collisionR.
86+
* - We check if the cell at this index is available,
87+
* - If it is not, we try with the next one,
88+
* - If all cells are occupied, we throw an error.
89+
*/
90+
int count = 0;
91+
int index = GetIndex(aKey, count);
92+
// Collision resolution
93+
while (
94+
table[index] != null
95+
&& !table[index].Status.Equals(StatusType.Deleted)
96+
)
97+
{
98+
count++;
99+
if (count == table.Length) // If table is full, throw an exception.
100+
throw new ApplicationException("Table is full.");
101+
index = GetIndex(aKey, count);
102+
}
103+
104+
if (table[index] == null) // table slot is empty (e.g. never been used)
105+
table[index] = new Cell(
106+
aKey,
107+
aValue,
108+
StatusType.Active
109+
);
110+
else if (
111+
table[index].Key.Equals(aKey) == true
112+
&& table[index].Status == StatusType.Active
113+
) // duplicate key found
114+
throw new ArgumentException(
115+
"Dictionary Error: Don't add duplicate keys: "
116+
+ aKey.ToString()
117+
);
118+
else if (table[index].Status == StatusType.Deleted) // previously used item, reuse the cell
119+
{
120+
table[index].Value = aValue;
121+
table[index].Key = aKey;
122+
table[index].Status = StatusType.Active;
123+
}
124+
else
125+
throw new ApplicationException(
126+
"Something went wrong in Add method."
127+
);
128+
}
129+
130+
/// <summary>
131+
/// Returns the data associated with the key
132+
/// </summary>
133+
/// <param name="aKey"></param>
134+
/// <returns>data item</returns>
135+
public TValue Find(TKey aKey)
136+
{
137+
// search until found or empty
138+
int count = 0;
139+
int index = GetIndex(aKey, count);
140+
while (
141+
table[index] != null
142+
&& table[index].Status != StatusType.Deleted
143+
&& !table[index].Key.Equals(aKey)
144+
)
145+
{
146+
count++;
147+
if (count == table.Length) // in case table is full, kicks out of inf loop
148+
throw new ApplicationException("Table is full");
149+
index = GetIndex(aKey, count);
150+
}
151+
152+
if (table[index] == null)
153+
throw new KeyNotFoundException(
154+
"The key " + aKey.ToString() + " was not found"
155+
);
156+
else if (
157+
table[index].Status == StatusType.Active
158+
&& table[index].Key.Equals(aKey) == true
159+
)
160+
return table[index].Value;
161+
else
162+
throw new ApplicationException(
163+
"Something went wrong in Find method."
164+
);
165+
}
166+
167+
// The following is used to compute the
168+
// integer hash code of the key, and shift it if needed
169+
// using countP.
170+
private int GetIndex(TKey aKey, int countP)
171+
{
172+
// countP captures the number of times we had to solve
173+
// a collision.
174+
return (
175+
Math.Abs(aKey.GetHashCode())
176+
+ collisionR(countP, aKey)
177+
) % table.Length;
178+
}
179+
180+
// This is the how collision are handled.
181+
// It depends on the strategy picked.
182+
// This overall strategy is called open addressing.
183+
// https://en.wikibooks.org/wiki/Data_Structures/Hash_Tables#Open_addressing
184+
private int collisionR(int i, TKey aKey)
185+
{
186+
if (i == 0)
187+
return 0;
188+
else
189+
{
190+
if (Strategy == CollisionRes.Linear)
191+
return i++;
192+
else if (Strategy == CollisionRes.Quad)
193+
return i * i;
194+
else if (Strategy == CollisionRes.Double)
195+
// This is double hashing.
196+
return i * (31 - (aKey.GetHashCode() % 31)); // i * hash2(aKey) where hash2 is 31 - (key % 31) and will always be > 0
197+
else
198+
throw new ApplicationException(
199+
"Unknown collision startegy."
200+
);
201+
}
202+
}
203+
204+
public void Remove(TKey aKey)
205+
{
206+
//int index = Search(aKey, IsDeletedOrFound);
207+
int count = 0;
208+
int index = GetIndex(aKey, count);
209+
while (
210+
table[index] != null
211+
&& (
212+
table[index].Status == StatusType.Deleted
213+
|| !table[index].Key.Equals(aKey)
214+
)
215+
)
216+
{
217+
count++;
218+
if (count == table.Length) // in case table is full, kicks out of inf loop
219+
throw new ApplicationException("Table is full");
220+
index = GetIndex(aKey, count);
221+
}
222+
// Search will keep looking until found or empty cell.
223+
if (table[index] == null)
224+
throw new KeyNotFoundException(
225+
"Cannot delete missing key: " + aKey.ToString()
226+
);
227+
else if (
228+
table[index].Status == StatusType.Active
229+
&& table[index].Key.Equals(aKey)
230+
)
231+
table[index].Status = StatusType.Deleted; // Found it! Mark the cell as deleted.
232+
else
233+
throw new ApplicationException(
234+
"Something went wrong in the Remove method."
235+
);
236+
}
237+
238+
// The following allows the use of [].
239+
public TValue this[TKey aKey]
240+
{
241+
get { return Find(aKey); }
242+
set
243+
{
244+
// find empty cell (e.g. cell is null, status empty or deleted)
245+
int count = 0;
246+
int index = GetIndex(aKey, count);
247+
while (
248+
table[index] != null
249+
&& !table[index].Status.Equals(StatusType.Deleted)
250+
)
251+
{
252+
count++;
253+
if (count == table.Length) // in case table is full, kicks out of inf loop
254+
throw new ApplicationException("Table is full");
255+
index = GetIndex(aKey, count);
256+
}
257+
// table slot is empty
258+
if (table[index] == null)
259+
table[index] = new Cell(
260+
aKey,
261+
value,
262+
StatusType.Active
263+
);
264+
// duplicate key found
265+
else if (
266+
table[index].Key.Equals(aKey) == true
267+
&& table[index].Status == StatusType.Active
268+
)
269+
table[index].Value = value;
270+
// previously used item, reuse it
271+
else if (table[index].Status == StatusType.Deleted)
272+
{
273+
table[index].Value = value;
274+
table[index].Key = aKey;
275+
table[index].Status = StatusType.Active;
276+
}
277+
else
278+
throw new ApplicationException(
279+
"Something went wrong in [] set."
280+
);
281+
}
282+
}
283+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Why prime numbers are needed is explained for example
3+
* at
4+
* https://cs.stackexchange.com/questions/11029
5+
*/
6+
7+
public static class PrimeHelper
8+
{
9+
public static bool IsPrime(int n)
10+
{
11+
// "A prime number is a natural number greater than 1 that is not a product of two smaller natural numbers."
12+
// https://en.wikipedia.org/wiki/Prime_number
13+
if (n < 2)
14+
return false;
15+
if (n == 2 || n == 3)
16+
return true;
17+
if (n % 2 == 0)
18+
return false;
19+
for (int i = 3; i * i <= n; i += 2)
20+
if (n % i == 0)
21+
return false;
22+
return true;
23+
}
24+
25+
public static int NextPrime(int n)
26+
{
27+
if (n < 2)
28+
{
29+
n = 2;
30+
}
31+
else
32+
{
33+
// Since 2 is the only even prime,
34+
// we make the n even if it is divisible
35+
// by 2.
36+
if (n % 2 == 0)
37+
n++;
38+
39+
while (!IsPrime(n))
40+
{
41+
n += 2;
42+
}
43+
}
44+
return n;
45+
}
46+
}

0 commit comments

Comments
 (0)