5 // Jb Evain <jbevain@novell.com>
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections;
31 using System.Collections.Generic;
33 using System.Runtime.Serialization;
34 using System.Runtime.InteropServices;
35 using System.Security;
36 using System.Security.Permissions;
38 // HashSet is basically implemented as a reduction of Dictionary<K, V>
40 namespace System.Collections.Generic {
42 [Serializable, HostProtection (SecurityAction.LinkDemand, MayLeakOnAbort = true)]
43 public class HashSet<T> : ICollection<T>, ISerializable, IDeserializationCallback {
45 const int INITIAL_SIZE = 10;
46 const float DEFAULT_LOAD_FACTOR = (90f / 100);
47 const int NO_SLOT = -1;
48 const int HASH_FLAG = -2147483648;
55 // The hash table contains indices into the "links" array
61 // The number of slots in "links" and "slots" that
62 // are in use (i.e. filled with data) or have been used and marked as
66 // The index of the first slot in the "empty slots chain".
67 // "Remove ()" prepends the cleared slots to the empty chain.
68 // "Add ()" fills the first slot in the empty slots chain with the
69 // added item (or increases "touched" if the chain itself is empty).
72 // The number of items in this set.
75 // The number of items the set can hold without
76 // resizing the hash table and the slots arrays.
79 IEqualityComparer<T> comparer;
82 // The number of changes made to this set. Used by enumerators
83 // to detect changes and invalidate themselves.
92 Init (INITIAL_SIZE, null);
95 public HashSet (IEqualityComparer<T> comparer)
97 Init (INITIAL_SIZE, comparer);
100 public HashSet (IEnumerable<T> collection) : this (collection, null)
104 public HashSet (IEnumerable<T> collection, IEqualityComparer<T> comparer)
106 if (collection == null)
107 throw new ArgumentNullException ("collection");
110 var col = collection as ICollection<T>;
112 capacity = col.Count;
114 Init (capacity, comparer);
115 foreach (var item in collection)
119 protected HashSet (SerializationInfo info, StreamingContext context)
124 void Init (int capacity, IEqualityComparer<T> comparer)
127 throw new ArgumentOutOfRangeException ("capacity");
129 this.comparer = comparer ?? EqualityComparer<T>.Default;
131 capacity = INITIAL_SIZE;
133 /* Modify capacity so 'capacity' elements can be added without resizing */
134 capacity = (int) (capacity / DEFAULT_LOAD_FACTOR) + 1;
136 InitArrays (capacity);
140 void InitArrays (int size)
142 table = new int [size];
144 links = new Link [size];
145 empty_slot = NO_SLOT;
147 slots = new T [size];
150 threshold = (int) (table.Length * DEFAULT_LOAD_FACTOR);
151 if (threshold == 0 && table.Length > 0)
155 bool SlotsContainsAt (int index, int hash, T item)
157 int current = table [index] - 1;
158 while (current != NO_SLOT) {
159 Link link = links [current];
160 if (link.HashCode == hash && comparer.Equals (item, slots [current]))
169 public void CopyTo (T [] array)
171 CopyTo (array, 0, count);
174 public void CopyTo (T [] array, int index)
176 CopyTo (array, index, count);
179 public void CopyTo (T [] array, int index, int count)
182 throw new ArgumentNullException ("array");
184 throw new ArgumentOutOfRangeException ("index");
185 if (index > array.Length)
186 throw new ArgumentException ("index larger than largest valid index of array");
187 if (array.Length - index < count)
188 throw new ArgumentException ("Destination array cannot hold the requested elements!");
190 for (int i = 0, items = 0; i < touched && items < count; i++) {
191 if (GetLinkHashCode (i) != 0)
192 array [index++] = slots [i];
198 int newSize = PrimeHelper.ToPrime ((table.Length << 1) | 1);
200 // allocate new hash table and link slots array
201 var newTable = new int [newSize];
202 var newLinks = new Link [newSize];
204 for (int i = 0; i < table.Length; i++) {
205 int current = table [i] - 1;
206 while (current != NO_SLOT) {
207 int hashCode = newLinks [current].HashCode = GetItemHashCode (slots [current]);
208 int index = (hashCode & int.MaxValue) % newSize;
209 newLinks [current].Next = newTable [index] - 1;
210 newTable [index] = current + 1;
211 current = links [current].Next;
218 // allocate new data slots, copy data
219 var newSlots = new T [newSize];
220 Array.Copy (slots, 0, newSlots, 0, touched);
223 threshold = (int) (newSize * DEFAULT_LOAD_FACTOR);
226 int GetLinkHashCode (int index)
228 return links [index].HashCode & HASH_FLAG;
231 int GetItemHashCode (T item)
233 return comparer.GetHashCode (item) | HASH_FLAG;
236 public bool Add (T item)
238 int hashCode = GetItemHashCode (item);
239 int index = (hashCode & int.MaxValue) % table.Length;
241 if (SlotsContainsAt (index, hashCode, item))
244 if (++count > threshold) {
246 index = (hashCode & int.MaxValue) % table.Length;
249 // find an empty slot
250 int current = empty_slot;
251 if (current == NO_SLOT)
254 empty_slot = links [current].Next;
256 // store the hash code of the added item,
257 // prepend the added item to its linked list,
258 // update the hash table
259 links [current].HashCode = hashCode;
260 links [current].Next = table [index] - 1;
261 table [index] = current + 1;
264 slots [current] = item;
271 public IEqualityComparer<T> Comparer {
272 get { return comparer; }
279 Array.Clear (table, 0, table.Length);
280 Array.Clear (slots, 0, slots.Length);
281 Array.Clear (links, 0, links.Length);
283 // empty the "empty slots chain"
284 empty_slot = NO_SLOT;
290 public bool Contains (T item)
292 int hashCode = GetItemHashCode (item);
293 int index = (hashCode & int.MaxValue) % table.Length;
295 return SlotsContainsAt (index, hashCode, item);
298 public bool Remove (T item)
300 // get first item of linked list corresponding to given key
301 int hashCode = GetItemHashCode (item);
302 int index = (hashCode & int.MaxValue) % table.Length;
303 int current = table [index] - 1;
305 // if there is no linked list, return false
306 if (current == NO_SLOT)
309 // walk linked list until right slot (and its predecessor) is
310 // found or end is reached
313 Link link = links [current];
314 if (link.HashCode == hashCode && comparer.Equals (slots [current], item))
319 } while (current != NO_SLOT);
321 // if we reached the end of the chain, return false
322 if (current == NO_SLOT)
327 // remove slot from linked list
328 // is slot at beginning of linked list?
330 table [index] = links [current].Next + 1;
332 links [prev].Next = links [current].Next;
334 // mark slot as empty and prepend it to "empty slots chain"
335 links [current].Next = empty_slot;
336 empty_slot = current;
339 links [current].HashCode = 0;
340 slots [current] = default (T);
347 public int RemoveWhere (Predicate<T> predicate)
349 if (predicate == null)
350 throw new ArgumentNullException ("predicate");
354 var copy = new T [count];
357 foreach (var item in copy) {
358 if (predicate (item)) {
367 public void TrimExcess ()
374 public void IntersectWith (IEnumerable<T> other)
377 throw new ArgumentNullException ("other");
379 var copy = new T [count];
382 foreach (var item in copy)
383 if (!other.Contains (item))
386 foreach (var item in other)
387 if (!Contains (item))
391 public void ExceptWith (IEnumerable<T> other)
394 throw new ArgumentNullException ("other");
396 foreach (var item in other)
400 public bool Overlaps (IEnumerable<T> other)
403 throw new ArgumentNullException ("other");
405 foreach (var item in other)
412 public bool SetEquals (IEnumerable<T> other)
415 throw new ArgumentNullException ("other");
417 if (count != other.Count ())
420 foreach (var item in this)
421 if (!other.Contains (item))
427 public void SymmetricExceptWith (IEnumerable<T> other)
430 throw new ArgumentNullException ("other");
432 foreach (var item in other) {
440 public void UnionWith (IEnumerable<T> other)
443 throw new ArgumentNullException ("other");
445 foreach (var item in other)
449 bool CheckIsSubsetOf (IEnumerable<T> other)
452 throw new ArgumentNullException ("other");
454 foreach (var item in this)
455 if (!other.Contains (item))
461 public bool IsSubsetOf (IEnumerable<T> other)
464 throw new ArgumentNullException ("other");
469 if (count > other.Count ())
472 return CheckIsSubsetOf (other);
475 public bool IsProperSubsetOf (IEnumerable<T> other)
478 throw new ArgumentNullException ("other");
483 if (count >= other.Count ())
486 return CheckIsSubsetOf (other);
489 bool CheckIsSupersetOf (IEnumerable<T> other)
492 throw new ArgumentNullException ("other");
494 foreach (var item in other)
495 if (!Contains (item))
501 public bool IsSupersetOf (IEnumerable<T> other)
504 throw new ArgumentNullException ("other");
506 if (count < other.Count ())
509 return CheckIsSupersetOf (other);
512 public bool IsProperSupersetOf (IEnumerable<T> other)
515 throw new ArgumentNullException ("other");
517 if (count <= other.Count ())
520 return CheckIsSupersetOf (other);
524 public static IEqualityComparer<HashSet<T>> CreateSetComparer ()
526 throw new NotImplementedException ();
530 [SecurityPermission (SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
531 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
533 throw new NotImplementedException ();
537 public virtual void OnDeserialization (object sender)
542 throw new NotImplementedException ();
545 IEnumerator<T> IEnumerable<T>.GetEnumerator ()
547 return new Enumerator (this);
550 bool ICollection<T>.IsReadOnly {
551 get { return false; }
554 void ICollection<T>.CopyTo (T [] array, int index)
556 CopyTo (array, index);
559 void ICollection<T>.Add (T item)
562 throw new ArgumentException ();
565 IEnumerator IEnumerable.GetEnumerator ()
567 return new Enumerator (this);
570 public Enumerator GetEnumerator ()
572 return new Enumerator (this);
576 public struct Enumerator : IEnumerator<T>, IDisposable {
582 internal Enumerator (HashSet<T> hashset)
584 this.hashset = hashset;
585 this.stamp = hashset.generation;
590 public bool MoveNext ()
594 while (current < hashset.touched)
595 if (hashset.GetLinkHashCode (++current) != 0)
605 return hashset.slots [current];
609 object IEnumerator.Current {
610 get { return this.Current; }
613 void IEnumerator.Reset ()
618 public void Dispose ()
626 throw new ObjectDisposedException (null);
627 if (hashset.generation != stamp)
628 throw new InvalidOperationException ("HashSet have been modified while it was iterated over");
635 if (current == NO_SLOT || current >= hashset.touched)
636 throw new InvalidOperationException ("Current is not valid");
640 // borrowed from System.Collections.HashTable
641 static class PrimeHelper {
643 static readonly int [] primes_table = {
680 static bool TestPrime (int x)
683 int top = (int) Math.Sqrt (x);
685 for (int n = 3; n < top; n += 2) {
693 // There is only one even prime - 2.
697 static int CalcPrime (int x)
699 for (int i = (x & (~1)) - 1; i < Int32.MaxValue; i += 2)
706 public static int ToPrime (int x)
708 for (int i = 0; i < primes_table.Length; i++)
709 if (x <= primes_table [i])
710 return primes_table [i];
712 return CalcPrime (x);