Browse Source

Add Hashtable

svn path=/trunk/mcs/; revision=209
Miguel de Icaza 24 years ago
parent
commit
732aad5b3c

+ 762 - 0
mcs/class/corlib/System.Collections/Hashtable.cs

@@ -0,0 +1,762 @@
+//
+// System.Collections.Hashtable
+//
+// Author:
+//   Sergey Chaban ([email protected])
+//
+
+
+
+using System;
+using System.Collections;
+
+
+// TODO: 1. Interfaces to implement: ISerializable and IDeserializationCallback;
+//          Synchronized wrapper (it's really easy but requires at least
+//          System.Threading.Monitor to be present, maybe just a stub for now).
+//       2. Meaningfull error messages for all exceptions.
+
+
+namespace System.Collections {
+
+	public class Hashtable : IDictionary, ICollection,
+	                         IEnumerable, ICloneable {
+
+
+		internal struct slot {
+			internal Object key;
+
+			internal Object value;
+
+			// Hashcode. Chains are also marked through this.
+			internal int hashMix;
+		}
+
+
+
+		//
+		// Private data
+		//
+
+		private readonly static int CHAIN_MARKER=~Int32.MaxValue;
+		private readonly static int ALLOC_GRAIN=0x2F;
+
+		// Used as indicator for the removed parts of a chain.
+		private readonly static Object REMOVED_MARKER=new Object();
+
+
+		private int inUse;
+		private int modificationCount;
+		private float loadFactor;
+		private slot[] table;
+		private int threshold;
+
+		private IHashCodeProvider m_hcp;
+		private IComparer m_comparer;
+
+		public static int[] primeTbl={};
+
+
+		// Class constructor
+
+		static Hashtable() {
+			// NOTE: Precalculate primes table.
+			// This precalculated table of primes is intended
+			// to speed-up allocations/resize for relatively
+			// small tables.
+			// I'm not sure whether it's a good idea or not.
+			// Also I am in doubt as for the quality of this
+			// particular implementation, probably the increment
+			// shouldn't be linear? Consider this as a hack
+			// or as a placeholder for future improvements.
+			int size=0x2000/ALLOC_GRAIN;
+			primeTbl=new int[size];
+			for (int x=53,i=0;i<size;x+=ALLOC_GRAIN,i++) {
+				primeTbl[i]=CalcPrime(x);
+			}
+		}
+
+
+		//
+		// Constructors
+		//
+
+		public Hashtable() : this(0,1.0f) {}
+
+
+		public Hashtable(int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) {
+			if (capacity<0)
+				throw new ArgumentOutOfRangeException("negative capacity");
+
+			if (loadFactor<0.1 || loadFactor>1)
+				throw new ArgumentOutOfRangeException("load factor");
+
+			if (capacity==0) ++capacity;
+			this.loadFactor=0.75f*loadFactor;
+			int size=(int)(capacity/this.loadFactor);
+			size=ToPrime(size);
+			this.SetTable(new slot[size]);
+
+			this.hcp=hcp;
+			this.comparer=comparer;
+
+			this.inUse=0;
+			this.modificationCount=0;
+
+		}
+
+		public Hashtable(int capacity, float loadFactor) :
+			this(capacity,loadFactor,null,null) {}
+
+		public Hashtable(int capacity) : this(capacity,1.0f) {}
+
+		public Hashtable(int capacity,
+		                 IHashCodeProvider hcp,
+		                 IComparer comparer
+		                ) : this(capacity,1.0f,hcp,comparer) {}
+
+
+		public Hashtable(IDictionary d, float loadFactor,
+		                 IHashCodeProvider hcp,IComparer comparer)
+		                 : this(d!=null?d.Count:0,
+		                        loadFactor,hcp,comparer) {
+			if (d==null)
+				throw new ArgumentNullException("dictionary");
+
+			IDictionaryEnumerator it=d.GetEnumerator();
+			while (it.MoveNext()) {
+				Add(it.Key,it.Value);
+			}
+			
+		}
+
+		public Hashtable(IDictionary d, float loadFactor)
+		       : this(d,loadFactor,null,null) {}
+
+
+		public Hashtable(IDictionary d) : this(d,1.0f) {}
+
+		public Hashtable(IDictionary d, IHashCodeProvider hcp,IComparer comparer)
+		                 : this(d,1.0f,hcp,comparer) {}
+
+		public Hashtable(IHashCodeProvider hcp,IComparer comparer)
+		                 : this(1,1.0f,hcp,comparer) {}
+
+
+
+		//
+		// Properties
+		//
+
+		protected IComparer comparer {
+			set {
+				m_comparer=value;
+			}
+			get {
+				return m_comparer;
+			}
+		}
+
+		protected IHashCodeProvider hcp {
+			set {
+				m_hcp=value;
+			}
+			get {
+				return m_hcp;
+			}
+		}
+
+		// ICollection
+
+		public virtual int Count {
+			get {
+				return inUse;
+			}
+		}
+
+		public virtual bool IsSynchronized {
+			get {
+				return false;
+			}
+		}
+
+		public virtual Object SyncRoot {
+			get {
+				return this;
+			}
+		}
+
+
+
+		// IDictionary
+
+		public virtual bool IsFixedSize {
+			get {
+				return false;
+			}
+		}
+
+
+		public virtual bool IsReadOnly {
+			get {
+				return false;
+			}
+		}
+
+		public virtual ICollection Keys {
+			get {
+				return new HashKeys(this);
+			}
+		}
+
+		public virtual ICollection Values {
+			get {
+				return new HashValues(this);
+			}
+		}
+
+
+
+		public virtual Object this[Object key] {
+			get {
+				return GetImpl(key);
+			}
+			set {
+				PutImpl(key,value,true);
+			}
+		}
+
+
+
+
+		//
+		// Interface methods
+		//
+
+
+		// IEnumerable
+
+		IEnumerator IEnumerable.GetEnumerator() {
+			return new Enumerator(this,EnumeratorMode.KEY_MODE);
+		}
+
+
+		// ICollection
+		public virtual void CopyTo(Array array, int arrayIndex) {
+			IDictionaryEnumerator it=GetEnumerator();
+			int i=arrayIndex;
+			while (it.MoveNext()) {
+				array.SetValue(it.Entry,i++);
+			}
+		}
+
+
+		// IDictionary
+
+		public virtual void Add(Object key, Object value) {
+			PutImpl(key,value,false);
+		}
+
+		public virtual void Clear() {
+			for (int i=0;i<table.Length;i++) {
+				table[i].key=null;
+				table[i].value=null;
+				table[i].hashMix=0;
+			}
+		}
+
+		public virtual bool Contains(Object key) {
+			return (Find(key)>=0);
+		}
+
+		public virtual IDictionaryEnumerator GetEnumerator() {
+			return new Enumerator(this,EnumeratorMode.KEY_MODE);
+		}
+
+		public virtual void Remove(Object key) {
+			int i=Find(key);
+			slot[] table=this.table;
+			if (i>=0) {
+				int h=table[i].hashMix;
+				h&=CHAIN_MARKER;
+				table[i].hashMix=h;
+				table[i].key=(h!=0)
+				              ? REMOVED_MARKER
+				              : null;
+				table[i].value=null;
+				--inUse;
+				++modificationCount;
+			}
+		}
+
+
+
+
+		public virtual bool ContainsKey(object key) {
+			return Contains(key);
+		}
+
+		public virtual bool ContainsValue(object value) {
+			int size=this.table.Length;
+			slot[] table=this.table;
+
+			for (int i=0;i<size;i++) {
+				slot entry=table[i];
+				if (entry.key!=null
+				    && entry.key!=REMOVED_MARKER
+				    && value.Equals(entry.value)) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+
+
+		// ICloneable
+
+		public virtual object Clone() {
+			Hashtable ht=new Hashtable(Count, hcp, comparer);
+			ht.modificationCount=this.modificationCount;
+			ht.inUse=this.inUse;
+			ht.AdjustThreshold();
+
+			// FIXME: maybe it's faster to simply
+			//        copy the back-end array?
+			IDictionaryEnumerator it=GetEnumerator();
+			while (it.MoveNext()) {
+				ht[it.Key]=it.Value;
+			}
+
+			return ht;
+		}
+
+
+
+		// TODO: public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {}
+		// TODO: public virtual void OnDeserialization(object sender);
+
+
+		public override string ToString() {
+			// FIXME: What's it supposed to do?
+			//        Maybe print out some internals here? Anyway.
+			return "mono::System.Collections.Hashtable";
+		}
+
+
+		/// <summary>
+		///  Returns a synchronized (thread-safe)
+		///  wrapper for the Hashtable.
+		/// </summary>
+		public static Hashtable Synchronized(Hashtable table) {
+			// TODO: implement
+			return null;
+		}
+
+
+
+		//
+		// Protected instance methods
+		//
+
+		/// <summary>Returns the hash code for the specified key.</summary>
+		protected virtual int GetHash(Object key) {
+			IHashCodeProvider hcp=this.hcp;
+			return (hcp!=null)
+			        ? hcp.GetHashCode()
+			        : key.GetHashCode();
+		}
+
+		/// <summary>
+		///  Compares a specific Object with a specific key
+		///  in the Hashtable.
+		/// </summary>
+		protected virtual bool KeyEquals(Object item,Object key) {
+			IComparer c=this.comparer;
+			if (c!=null)
+				return (c.Compare(item,key)==0);
+			else
+				return item.Equals(key);
+		}
+
+
+
+		//
+		// Private instance methods
+		//
+
+		private void AdjustThreshold() {
+			int size=table.Length;
+			threshold=(int)(size*loadFactor);
+			if (this.threshold>=size) threshold=size-1;
+		}
+
+		private void SetTable(slot[] table) {
+			if (table==null)
+				throw new ArgumentNullException("table");
+
+			this.table=table;
+			AdjustThreshold();
+		}
+
+		private Object GetImpl(Object key) {
+			int i=Find(key);
+
+			if (i>=0)
+				return table[i].value;
+			else
+				return null;
+		}
+
+
+		private int Find(Object key) {
+			if (key==null)
+				throw new ArgumentNullException("null key");
+
+			uint size=(uint)this.table.Length;
+			int h=this.GetHash(key) & Int32.MaxValue;
+			uint spot=(uint)h;
+			uint step=(uint)((h>>5)+1)%(size-1)+1;
+			slot[] table=this.table;
+
+			for (int i=0;i<size;i++) {
+				int indx=(int)(spot%size);
+				slot entry=table[indx];
+				Object k=entry.key;
+				if (k==null) return -1;
+				if ((entry.hashMix & Int32.MaxValue)==h
+				   && this.KeyEquals(key,k)) {
+					return indx;
+				}
+
+				if ((entry.hashMix & CHAIN_MARKER)==0)
+					return -1;
+
+				spot+=step;
+			}
+			return -1;
+		}
+
+
+		private void Rehash() {
+			int oldSize=this.table.Length;
+
+			// From the SDK docs:
+			//   Hashtable is automatically increased
+			//   to the smallest prime number that is larger
+			//   than twice the current number of Hashtable buckets
+			uint newSize=(uint)ToPrime((oldSize<<1)|1);
+
+
+			slot[] newTable=new slot[newSize];
+			slot[] table=this.table;
+
+			for (int i=0;i<oldSize;i++) {
+				slot s=table[i];
+				if (s.key!=null) {
+					int h=s.hashMix & Int32.MaxValue;
+					uint spot=(uint)h;
+					uint step=((uint)(h>>5)+1)%(newSize-1)+1;
+					for (uint j=spot%newSize;;spot+=step,j=spot%newSize) {
+						// No check for REMOVED_MARKER here,
+						// because the table is just allocated.
+						if (newTable[j].key==null) {
+							newTable[j].key=s.key;
+							newTable[j].value=s.value;
+							newTable[j].hashMix|=h;
+							break;
+						} else {
+							newTable[j].hashMix|=CHAIN_MARKER;
+						}
+					}
+				}
+			}
+
+			++this.modificationCount;
+
+			this.SetTable(newTable);
+		}
+
+
+		private void PutImpl(Object key,Object value,bool overwrite) {
+			if (key==null)
+				throw new ArgumentNullException("null key");
+
+			uint size=(uint)this.table.Length;
+			if (this.inUse>=this.threshold) {
+				this.Rehash();
+				size=(uint)this.table.Length;
+			}
+
+			int h=this.GetHash(key) & Int32.MaxValue;
+			uint spot=(uint)h;
+			uint step=(uint)((spot>>5)+1)%(size-1)+1;
+			slot[] table=this.table;
+			slot entry;
+
+			int freeIndx=-1;
+			for (int i=0;i<size;i++) {
+				int indx=(int)(spot%size);
+				entry=table[indx];
+
+				if (freeIndx==-1
+				    && entry.key==REMOVED_MARKER
+				    && (entry.hashMix & CHAIN_MARKER)!=0) freeIndx=indx;
+
+				if (entry.key==null ||
+				    (entry.key==REMOVED_MARKER
+				     && (entry.hashMix & CHAIN_MARKER)!=0)) {
+
+					if (freeIndx==-1) freeIndx=indx;
+					break;
+				}
+
+				if ((entry.hashMix & Int32.MaxValue)==h
+				   && KeyEquals(key,entry.key)) {
+					if (overwrite) {
+						table[indx].value=value;
+						++this.modificationCount;
+					} else {
+						// Handle Add():
+						// An entry with the same key already exists in the Hashtable.
+						throw new ArgumentException("Key duplication");
+					}
+					return;
+				}
+
+				if (freeIndx==-1) {
+					table[indx].hashMix|=CHAIN_MARKER;
+				}
+
+				spot+=step;
+
+			}
+
+			if (freeIndx!=-1) {
+				table[freeIndx].key=key;
+				table[freeIndx].value=value;
+				table[freeIndx].hashMix|=h;
+
+				++this.inUse;
+				++this.modificationCount;
+			}
+
+		}
+
+		private void  CopyToArray(Array arr,int i,
+		                          EnumeratorMode mode) {
+			IEnumerator it=new Enumerator(this,mode);
+			while (it.MoveNext()) {
+				arr.SetValue(it.Current,i++);
+			}
+		}
+
+
+
+		//
+		// Private static methods
+		//
+		private static bool TestPrime(int x) {
+			if ((x & 1)!=0) {
+				for (int n=3;n<(int)Math.Sqrt(x);n+=2) {
+					if (x%n==0) return false;
+				}
+				return true;
+			}
+			// There is only one even prime - 2.
+			return (x==2);
+		}
+
+		private static int CalcPrime(int x) {
+			for (int i=(x&(~1))-1;i<Int32.MaxValue;i+=2) {
+				if (TestPrime(i)) return i;
+			}
+			return x;
+		}
+
+		private static int ToPrime(int x) {
+			for (int i=x/ALLOC_GRAIN;i<primeTbl.Length;i++) {
+				if (x<=primeTbl[i]) return primeTbl[i];
+			}
+			return CalcPrime(x);
+		}
+
+
+
+
+		//
+		// Inner classes
+		//
+
+		public enum EnumeratorMode : int {KEY_MODE=0,VALUE_MODE};
+
+		protected sealed class Enumerator : IDictionaryEnumerator, IEnumerator {
+
+			private Hashtable host;
+			private int stamp;
+			private int pos;
+			private int size;
+			private EnumeratorMode mode;
+
+			private Object currentKey;
+			private Object currentValue;
+
+			private readonly static string xstr="Hashtable.Enumerator: snapshot out of sync.";
+
+			public Enumerator(Hashtable host,EnumeratorMode mode) {
+				this.host=host;
+				stamp=host.modificationCount;
+				size=host.table.Length;
+				this.mode=mode;
+				Reset();
+			}
+
+			public Enumerator(Hashtable host)
+			           : this(host,EnumeratorMode.KEY_MODE) {}
+
+
+			private void FailFast() {
+				if (host.modificationCount!=stamp) {
+					throw new InvalidOperationException(xstr);
+				}
+			}
+
+			public void Reset() {
+				FailFast();
+
+				pos=-1;
+				currentKey=null;
+				currentValue=null;
+			}
+
+			public bool MoveNext() {
+				FailFast();
+
+				if (pos<size) while (++pos<size) {
+					slot entry=host.table[pos];
+					if (entry.key!=null && entry.key!=REMOVED_MARKER) {
+						currentKey=entry.key;
+						currentValue=entry.value;
+						return true;
+					}
+				}
+				currentKey=null;
+				currentValue=null;
+				return false;
+			}
+
+			public DictionaryEntry Entry {
+				get {
+					FailFast();
+					return new DictionaryEntry(currentKey,currentValue);
+				}
+			}
+
+			public Object Key {
+				get {
+					FailFast();
+					return currentKey;
+				}
+			}
+
+			public Object Value {
+				get {
+					FailFast();
+					return currentValue;
+				}
+			}
+
+			public Object Current {
+				get {
+					FailFast();
+					return (mode==EnumeratorMode.KEY_MODE)
+					        ? currentKey
+					        : currentValue;
+				}
+			}
+		}
+
+
+
+		protected class HashKeys : ICollection, IEnumerable {
+
+			private Hashtable host;
+			private int count;
+
+			public HashKeys(Hashtable host) {
+				if (host==null)
+					throw new ArgumentNullException();
+
+				this.host=host;
+				this.count=host.Count;
+			}
+
+			// ICollection
+
+			public virtual int Count {
+				get {return count;}
+			}
+
+			public virtual bool IsSynchronized {
+				get {return host.IsSynchronized;}
+			}
+
+			public virtual Object SyncRoot {
+				get {return host.SyncRoot;}
+			}
+
+			public virtual void CopyTo(Array array, int arrayIndex) {
+				host.CopyToArray(array,arrayIndex,EnumeratorMode.KEY_MODE);
+			}
+
+			// IEnumerable
+
+			public virtual IEnumerator GetEnumerator() {
+				return new Hashtable.Enumerator(host,EnumeratorMode.KEY_MODE);
+			}
+		}
+
+
+		protected class HashValues : ICollection, IEnumerable {
+
+			private Hashtable host;
+			private int count;
+
+			public HashValues(Hashtable host) {
+				if (host==null)
+					throw new ArgumentNullException();
+
+				this.host=host;
+				this.count=host.Count;
+			}
+
+			// ICollection
+
+			public virtual int Count {
+				get {return count;}
+			}
+
+			public virtual bool IsSynchronized {
+				get {return host.IsSynchronized;}
+			}
+
+			public virtual Object SyncRoot {
+				get {return host.SyncRoot;}
+			}
+
+			public virtual void CopyTo(Array array, int arrayIndex) {
+				host.CopyToArray(array,arrayIndex,EnumeratorMode.VALUE_MODE);
+			}
+
+			// IEnumerable
+
+			public virtual IEnumerator GetEnumerator() {
+				return new Hashtable.Enumerator(host,EnumeratorMode.VALUE_MODE);
+			}
+		}
+
+
+	}
+}
+

+ 156 - 0
mcs/class/corlib/System.Collections/HashtableTest.cs

@@ -0,0 +1,156 @@
+// TODO: add tests for Comparer and HashCodeProvider
+
+
+// NOTE: add MCS prefix to Hashtable.cs to use this test.
+
+
+using System;
+using MCS.System.Collections;
+using System.Collections;
+
+using NUnit.Framework;
+
+
+
+namespace Testsuite.System.Collections {
+
+
+	/// <summary>Hashtable test.</summary>
+	public class HashtableTest {
+		public static ITest Suite {
+			get {
+				TestSuite suite= new TestSuite("All Hashtable Tests");
+				suite.AddTest(BasicOperationsTest.Suite);
+				return suite;
+			}
+		}
+	}
+
+
+
+
+	public class BasicOperationsTest : TestCase {
+
+		protected MCS.System.Collections.Hashtable ht;
+		private static Random rnd;
+
+		public BasicOperationsTest(String name) : base(name) {}
+
+		protected override void SetUp() {
+			ht=new MCS.System.Collections.Hashtable();
+			rnd=new Random();
+		}
+
+		public static ITest Suite {
+			get {
+				return new TestSuite(typeof(BasicOperationsTest));
+			}
+		}
+
+
+
+		private void SetDefaultData() {
+			ht.Clear();
+			ht.Add("k1","another");
+			ht.Add("k2","yet");
+			ht.Add("k3","hashtable");
+		}
+
+
+		public void TestAddRemoveClear() {
+			ht.Clear();
+			Assert(ht.Count==0);
+
+			SetDefaultData();
+			Assert(ht.Count==3);
+
+			bool thrown=false;
+			try {
+				ht.Add("k2","cool");
+			} catch (ArgumentException) {thrown=true;}
+			Assert("Must throw ArgumentException!",thrown);
+
+			ht["k2"]="cool";
+			Assert(ht.Count==3);
+			Assert(ht["k2"].Equals("cool"));
+
+		}
+
+		public void TestCopyTo() {
+			SetDefaultData();
+			Object[] entries=new Object[ht.Count];
+			ht.CopyTo(entries,0);
+			Assert("Not an entry.",entries[0] is DictionaryEntry);
+		}
+
+
+		public void TestUnderHeavyLoad() {
+			Console.WriteLine("Testing "+ht);
+			ht.Clear();
+			int max=100000;
+			String[] cache=new String[max*2];
+			int n=0;
+
+			for (int i=0;i<max;i++) {
+				int id=rnd.Next()&0xFFFF;
+				String key=""+id+"-key-"+id;
+				String val="value-"+id;
+				if (ht[key]==null) {
+					ht[key]=val;
+					cache[n]=key;
+					cache[n+max]=val;
+					n++;
+				}
+			}
+
+			Assert(ht.Count==n);
+
+			for (int i=0;i<n;i++) {
+				String key=cache[i];
+				String val=ht[key] as String;
+				String err="ht[\""+key+"\"]=\""+val+
+				      "\", expected \""+cache[i+max]+"\"";
+				Assert(err,val!=null && val.Equals(cache[i+max]));
+			}
+
+			int r1=(n/3);
+			int r2=r1+(n/5);
+
+			for (int i=r1;i<r2;i++) {
+				ht.Remove(cache[i]);
+			}
+
+
+			for (int i=0;i<n;i++) {
+				if (i>=r1 && i<r2) {
+					Assert(ht[cache[i]]==null);
+				} else {
+					String key=cache[i];
+					String val=ht[key] as String;
+					String err="ht[\""+key+"\"]=\""+val+
+					      "\", expected \""+cache[i+max]+"\"";
+					Assert(err,val!=null && val.Equals(cache[i+max]));
+				}
+			}
+
+			ICollection keys=ht.Keys;
+			int nKeys=0;
+			foreach (Object key in keys) {
+				Assert((key as String) != null);
+				nKeys++;
+			}
+			Assert(nKeys==ht.Count);
+
+
+			ICollection vals=ht.Values;
+			int nVals=0;
+			foreach (Object val in vals) {
+				Assert((val as String) != null);
+				nVals++;
+			}
+			Assert(nVals==ht.Count);
+
+		}
+
+	}
+}

+ 1 - 0
mcs/class/corlib/System.Collections/common.src

@@ -1,4 +1,5 @@
 ArrayList.cs
+Hashtable.cs
 ICollection.cs
 IComparer.cs
 IDictionary.cs