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
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 index)
182 CopyTo (array, index, count);
185 public void CopyTo (T [] array, int index, int count)
188 throw new ArgumentNullException ("array");
190 throw new ArgumentOutOfRangeException ("index");
191 if (index > array.Length)
192 throw new ArgumentException ("index larger than largest valid index of array");
193 if (array.Length - index < 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 [index++] = 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> predicate)
357 if (predicate == null)
358 throw new ArgumentNullException ("predicate");
362 var candidates = new List<T> ();
364 foreach (var item in this)
365 if (predicate (item))
366 candidates.Add (item);
368 foreach (var item in candidates)
371 return candidates.Count;
374 public void TrimExcess ()
381 public void IntersectWith (IEnumerable<T> other)
384 throw new ArgumentNullException ("other");
386 var other_set = ToSet (other);
388 RemoveWhere (item => !other_set.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 var other_set = ToSet (other);
419 if (count != other_set.Count)
422 foreach (var item in this)
423 if (!other_set.Contains (item))
429 public void SymmetricExceptWith (IEnumerable<T> other)
432 throw new ArgumentNullException ("other");
434 foreach (var item in ToSet (other))
439 HashSet<T> ToSet (IEnumerable<T> enumerable)
441 var set = enumerable as HashSet<T>;
442 if (set == null || !Comparer.Equals (set.Comparer))
443 set = new HashSet<T> (enumerable);
448 public void UnionWith (IEnumerable<T> other)
451 throw new ArgumentNullException ("other");
453 foreach (var item in other)
457 bool CheckIsSubsetOf (HashSet<T> other)
460 throw new ArgumentNullException ("other");
462 foreach (var item in this)
463 if (!other.Contains (item))
469 public bool IsSubsetOf (IEnumerable<T> other)
472 throw new ArgumentNullException ("other");
477 var other_set = ToSet (other);
479 if (count > other_set.Count)
482 return CheckIsSubsetOf (other_set);
485 public bool IsProperSubsetOf (IEnumerable<T> other)
488 throw new ArgumentNullException ("other");
493 var other_set = ToSet (other);
495 if (count >= other_set.Count)
498 return CheckIsSubsetOf (other_set);
501 bool CheckIsSupersetOf (HashSet<T> other)
504 throw new ArgumentNullException ("other");
506 foreach (var item in other)
507 if (!Contains (item))
513 public bool IsSupersetOf (IEnumerable<T> other)
516 throw new ArgumentNullException ("other");
518 var other_set = ToSet (other);
520 if (count < other_set.Count)
523 return CheckIsSupersetOf (other_set);
526 public bool IsProperSupersetOf (IEnumerable<T> other)
529 throw new ArgumentNullException ("other");
531 var other_set = ToSet (other);
533 if (count <= other_set.Count)
536 return CheckIsSupersetOf (other_set);
540 public static IEqualityComparer<HashSet<T>> CreateSetComparer ()
542 throw new NotImplementedException ();
546 [SecurityPermission (SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
547 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
549 throw new NotImplementedException ();
553 public virtual void OnDeserialization (object sender)
558 throw new NotImplementedException ();
561 IEnumerator<T> IEnumerable<T>.GetEnumerator ()
563 return new Enumerator (this);
566 bool ICollection<T>.IsReadOnly {
567 get { return false; }
570 void ICollection<T>.CopyTo (T [] array, int index)
572 CopyTo (array, index);
575 void ICollection<T>.Add (T item)
580 IEnumerator IEnumerable.GetEnumerator ()
582 return new Enumerator (this);
585 public Enumerator GetEnumerator ()
587 return new Enumerator (this);
591 public struct Enumerator : IEnumerator<T>, IDisposable {
599 internal Enumerator (HashSet<T> hashset)
602 this.hashset = hashset;
603 this.stamp = hashset.generation;
606 public bool MoveNext ()
613 while (next < hashset.touched) {
615 if (hashset.GetLinkHashCode (cur) != 0) {
616 current = hashset.slots [cur];
626 get { return current; }
629 object IEnumerator.Current {
633 throw new InvalidOperationException ("Current is not valid");
638 void IEnumerator.Reset ()
644 public void Dispose ()
652 throw new ObjectDisposedException (null);
653 if (hashset.generation != stamp)
654 throw new InvalidOperationException ("HashSet have been modified while it was iterated over");
658 // borrowed from System.Collections.HashTable
659 static class PrimeHelper {
661 static readonly int [] primes_table = {
698 static bool TestPrime (int x)
701 int top = (int) Math.Sqrt (x);
703 for (int n = 3; n < top; n += 2) {
711 // There is only one even prime - 2.
715 static int CalcPrime (int x)
717 for (int i = (x & (~1)) - 1; i < Int32.MaxValue; i += 2)
724 public static int ToPrime (int x)
726 for (int i = 0; i < primes_table.Length; i++)
727 if (x <= primes_table [i])
728 return primes_table [i];
730 return CalcPrime (x);