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;
37 using System.Diagnostics;
39 // HashSet is basically implemented as a reduction of Dictionary<K, V>
41 namespace System.Collections.Generic {
43 [Serializable, HostProtection (SecurityAction.LinkDemand, MayLeakOnAbort = true)]
44 [DebuggerDisplay ("Count={Count}")]
45 [DebuggerTypeProxy (typeof (CollectionDebuggerView<,>))]
46 public class HashSet<T> : ICollection<T>, ISerializable, IDeserializationCallback
47 #if NET_4_0 || MOONLIGHT || MOBILE
51 const int INITIAL_SIZE = 10;
52 const float DEFAULT_LOAD_FACTOR = (90f / 100);
53 const int NO_SLOT = -1;
54 const int HASH_FLAG = -2147483648;
61 // The hash table contains indices into the "links" array
67 // The number of slots in "links" and "slots" that
68 // are in use (i.e. filled with data) or have been used and marked as
72 // The index of the first slot in the "empty slots chain".
73 // "Remove ()" prepends the cleared slots to the empty chain.
74 // "Add ()" fills the first slot in the empty slots chain with the
75 // added item (or increases "touched" if the chain itself is empty).
78 // The number of items in this set.
81 // The number of items the set can hold without
82 // resizing the hash table and the slots arrays.
85 IEqualityComparer<T> comparer;
88 // The number of changes made to this set. Used by enumerators
89 // to detect changes and invalidate themselves.
98 Init (INITIAL_SIZE, null);
101 public HashSet (IEqualityComparer<T> comparer)
103 Init (INITIAL_SIZE, comparer);
106 public HashSet (IEnumerable<T> collection) : this (collection, null)
110 public HashSet (IEnumerable<T> collection, IEqualityComparer<T> comparer)
112 if (collection == null)
113 throw new ArgumentNullException ("collection");
116 var col = collection as ICollection<T>;
118 capacity = col.Count;
120 Init (capacity, comparer);
121 foreach (var item in collection)
125 protected HashSet (SerializationInfo info, StreamingContext context)
130 void Init (int capacity, IEqualityComparer<T> comparer)
133 throw new ArgumentOutOfRangeException ("capacity");
135 this.comparer = comparer ?? EqualityComparer<T>.Default;
137 capacity = INITIAL_SIZE;
139 /* Modify capacity so 'capacity' elements can be added without resizing */
140 capacity = (int) (capacity / DEFAULT_LOAD_FACTOR) + 1;
142 InitArrays (capacity);
146 void InitArrays (int size)
148 table = new int [size];
150 links = new Link [size];
151 empty_slot = NO_SLOT;
153 slots = new T [size];
156 threshold = (int) (table.Length * DEFAULT_LOAD_FACTOR);
157 if (threshold == 0 && table.Length > 0)
161 bool SlotsContainsAt (int index, int hash, T item)
163 int current = table [index] - 1;
164 while (current != NO_SLOT) {
165 Link link = links [current];
166 if (link.HashCode == hash && ((hash == HASH_FLAG && (item == null || null == slots [current])) ? (item == null && null == slots [current]) : comparer.Equals (item, slots [current])))
175 public void CopyTo (T [] array)
177 CopyTo (array, 0, count);
180 public void CopyTo (T [] array, int arrayIndex)
182 CopyTo (array, arrayIndex, count);
185 public void CopyTo (T [] array, int arrayIndex, int count)
188 throw new ArgumentNullException ("array");
190 throw new ArgumentOutOfRangeException ("arrayIndex");
191 if (arrayIndex > array.Length)
192 throw new ArgumentException ("index larger than largest valid index of array");
193 if (array.Length - arrayIndex < count)
194 throw new ArgumentException ("Destination array cannot hold the requested elements!");
196 for (int i = 0, items = 0; i < touched && items < count; i++) {
197 if (GetLinkHashCode (i) != 0)
198 array [arrayIndex++] = slots [i];
204 int newSize = PrimeHelper.ToPrime ((table.Length << 1) | 1);
206 // allocate new hash table and link slots array
207 var newTable = new int [newSize];
208 var newLinks = new Link [newSize];
210 for (int i = 0; i < table.Length; i++) {
211 int current = table [i] - 1;
212 while (current != NO_SLOT) {
213 int hashCode = newLinks [current].HashCode = GetItemHashCode (slots [current]);
214 int index = (hashCode & int.MaxValue) % newSize;
215 newLinks [current].Next = newTable [index] - 1;
216 newTable [index] = current + 1;
217 current = links [current].Next;
224 // allocate new data slots, copy data
225 var newSlots = new T [newSize];
226 Array.Copy (slots, 0, newSlots, 0, touched);
229 threshold = (int) (newSize * DEFAULT_LOAD_FACTOR);
232 int GetLinkHashCode (int index)
234 return links [index].HashCode & HASH_FLAG;
237 int GetItemHashCode (T item)
241 return comparer.GetHashCode (item) | HASH_FLAG;
244 public bool Add (T item)
246 int hashCode = GetItemHashCode (item);
247 int index = (hashCode & int.MaxValue) % table.Length;
249 if (SlotsContainsAt (index, hashCode, item))
252 if (++count > threshold) {
254 index = (hashCode & int.MaxValue) % table.Length;
257 // find an empty slot
258 int current = empty_slot;
259 if (current == NO_SLOT)
262 empty_slot = links [current].Next;
264 // store the hash code of the added item,
265 // prepend the added item to its linked list,
266 // update the hash table
267 links [current].HashCode = hashCode;
268 links [current].Next = table [index] - 1;
269 table [index] = current + 1;
272 slots [current] = item;
279 public IEqualityComparer<T> Comparer {
280 get { return comparer; }
287 Array.Clear (table, 0, table.Length);
288 Array.Clear (slots, 0, slots.Length);
289 Array.Clear (links, 0, links.Length);
291 // empty the "empty slots chain"
292 empty_slot = NO_SLOT;
298 public bool Contains (T item)
300 int hashCode = GetItemHashCode (item);
301 int index = (hashCode & int.MaxValue) % table.Length;
303 return SlotsContainsAt (index, hashCode, item);
306 public bool Remove (T item)
308 // get first item of linked list corresponding to given key
309 int hashCode = GetItemHashCode (item);
310 int index = (hashCode & int.MaxValue) % table.Length;
311 int current = table [index] - 1;
313 // if there is no linked list, return false
314 if (current == NO_SLOT)
317 // walk linked list until right slot (and its predecessor) is
318 // found or end is reached
321 Link link = links [current];
322 if (link.HashCode == hashCode && ((hashCode == HASH_FLAG && (item == null || null == slots [current])) ? (item == null && null == slots [current]) : comparer.Equals (slots [current], item)))
327 } while (current != NO_SLOT);
329 // if we reached the end of the chain, return false
330 if (current == NO_SLOT)
335 // remove slot from linked list
336 // is slot at beginning of linked list?
338 table [index] = links [current].Next + 1;
340 links [prev].Next = links [current].Next;
342 // mark slot as empty and prepend it to "empty slots chain"
343 links [current].Next = empty_slot;
344 empty_slot = current;
347 links [current].HashCode = 0;
348 slots [current] = default (T);
355 public int RemoveWhere (Predicate<T> match)
358 throw new ArgumentNullException ("match");
360 var candidates = new List<T> ();
362 foreach (var item in this)
364 candidates.Add (item);
366 foreach (var item in candidates)
369 return candidates.Count;
372 public void TrimExcess ()
379 public void IntersectWith (IEnumerable<T> other)
382 throw new ArgumentNullException ("other");
384 var other_set = ToSet (other);
386 RemoveWhere (item => !other_set.Contains (item));
389 public void ExceptWith (IEnumerable<T> other)
392 throw new ArgumentNullException ("other");
394 foreach (var item in other)
398 public bool Overlaps (IEnumerable<T> other)
401 throw new ArgumentNullException ("other");
403 foreach (var item in other)
410 public bool SetEquals (IEnumerable<T> other)
413 throw new ArgumentNullException ("other");
415 var other_set = ToSet (other);
417 if (count != other_set.Count)
420 foreach (var item in this)
421 if (!other_set.Contains (item))
427 public void SymmetricExceptWith (IEnumerable<T> other)
430 throw new ArgumentNullException ("other");
432 foreach (var item in ToSet (other))
437 HashSet<T> ToSet (IEnumerable<T> enumerable)
439 var set = enumerable as HashSet<T>;
440 if (set == null || !Comparer.Equals (set.Comparer))
441 set = new HashSet<T> (enumerable);
446 public void UnionWith (IEnumerable<T> other)
449 throw new ArgumentNullException ("other");
451 foreach (var item in other)
455 bool CheckIsSubsetOf (HashSet<T> other)
458 throw new ArgumentNullException ("other");
460 foreach (var item in this)
461 if (!other.Contains (item))
467 public bool IsSubsetOf (IEnumerable<T> other)
470 throw new ArgumentNullException ("other");
475 var other_set = ToSet (other);
477 if (count > other_set.Count)
480 return CheckIsSubsetOf (other_set);
483 public bool IsProperSubsetOf (IEnumerable<T> other)
486 throw new ArgumentNullException ("other");
491 var other_set = ToSet (other);
493 if (count >= other_set.Count)
496 return CheckIsSubsetOf (other_set);
499 bool CheckIsSupersetOf (HashSet<T> other)
502 throw new ArgumentNullException ("other");
504 foreach (var item in other)
505 if (!Contains (item))
511 public bool IsSupersetOf (IEnumerable<T> other)
514 throw new ArgumentNullException ("other");
516 var other_set = ToSet (other);
518 if (count < other_set.Count)
521 return CheckIsSupersetOf (other_set);
524 public bool IsProperSupersetOf (IEnumerable<T> other)
527 throw new ArgumentNullException ("other");
529 var other_set = ToSet (other);
531 if (count <= other_set.Count)
534 return CheckIsSupersetOf (other_set);
537 class HashSetEqualityComparer : IEqualityComparer<HashSet<T>>
539 public bool Equals (HashSet<T> lhs, HashSet<T> rhs)
544 if (lhs == null || rhs == null || lhs.Count != rhs.Count)
547 foreach (var item in lhs)
548 if (!rhs.Contains (item))
554 public int GetHashCode (HashSet<T> hashset)
559 IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
561 foreach (var item in hashset)
562 hash ^= comparer.GetHashCode (item);
568 static readonly HashSetEqualityComparer setComparer = new HashSetEqualityComparer ();
570 public static IEqualityComparer<HashSet<T>> CreateSetComparer ()
575 [SecurityPermission (SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
576 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
579 throw new ArgumentNullException("info");
581 info.AddValue("Version", generation);
582 info.AddValue("Comparer", comparer, typeof(IEqualityComparer<T>));
583 info.AddValue("Capacity", (table == null) ? 0 : table.Length);
585 T[] tableArray = new T[table.Length];
587 info.AddValue("Elements", tableArray, typeof(T[]));
591 public virtual void OnDeserialization (object sender)
595 generation = (int) si.GetValue("Version", typeof(int));
596 comparer = (IEqualityComparer<T>) si.GetValue("Comparer",
597 typeof(IEqualityComparer<T>));
598 int capacity = (int) si.GetValue("Capacity", typeof(int));
600 empty_slot = NO_SLOT;
602 table = new int[capacity];
603 slots = new T[capacity];
605 T[] tableArray = (T[]) si.GetValue("Elements", typeof(T[]));
606 if (tableArray == null)
607 throw new SerializationException("Missing Elements");
609 for (int iElement = 0; iElement < tableArray.Length; iElement++) {
610 Add(tableArray[iElement]);
620 IEnumerator<T> IEnumerable<T>.GetEnumerator ()
622 return new Enumerator (this);
625 bool ICollection<T>.IsReadOnly {
626 get { return false; }
629 void ICollection<T>.Add (T item)
634 IEnumerator IEnumerable.GetEnumerator ()
636 return new Enumerator (this);
639 public Enumerator GetEnumerator ()
641 return new Enumerator (this);
645 public struct Enumerator : IEnumerator<T>, IDisposable {
653 internal Enumerator (HashSet<T> hashset)
656 this.hashset = hashset;
657 this.stamp = hashset.generation;
660 public bool MoveNext ()
667 while (next < hashset.touched) {
669 if (hashset.GetLinkHashCode (cur) != 0) {
670 current = hashset.slots [cur];
680 get { return current; }
683 object IEnumerator.Current {
687 throw new InvalidOperationException ("Current is not valid");
692 void IEnumerator.Reset ()
698 public void Dispose ()
706 throw new ObjectDisposedException (null);
707 if (hashset.generation != stamp)
708 throw new InvalidOperationException ("HashSet have been modified while it was iterated over");
712 // borrowed from System.Collections.HashTable
713 static class PrimeHelper {
715 static readonly int [] primes_table = {
752 static bool TestPrime (int x)
755 int top = (int) Math.Sqrt (x);
757 for (int n = 3; n < top; n += 2) {
765 // There is only one even prime - 2.
769 static int CalcPrime (int x)
771 for (int i = (x & (~1)) - 1; i < Int32.MaxValue; i += 2)
778 public static int ToPrime (int x)
780 for (int i = 0; i < primes_table.Length; i++)
781 if (x <= primes_table [i])
782 return primes_table [i];
784 return CalcPrime (x);