Click here to Skip to main content
15,885,767 members
Articles / Programming Languages / C#

The List Trifecta, Part 1

Rate me:
Please Sign up or sign in to vote.
4.97/5 (21 votes)
20 May 2016LGPL321 min read 37.1K   161   40  
The A-list is an all-purpose list, a data structure that can support most standard list operation in O(log n) time and does lots of other stuff, too
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Loyc.Collections.Impl;
using System.Diagnostics;

namespace Loyc.Collections
{
	using System;

	/// <summary>
	/// An sorted dictionary that allows multiple values to be associated with a
	/// single key. Note: both keys and values must be comparable.
	/// </summary>
	/// <remarks>
	/// Often when people want to be able to associate multiple values with a 
	/// single key, they use a Dictionary with values of type <see cref="List{T}"/>.
	/// This approach is very inefficient (in terms of memory use) if most keys are 
	/// only associated with one or two values; this class solves the problem using
	/// a single sorted B+ tree for all keys and all values. It requires, however,
	/// that both the keys and values are totally ordered (i.e. are sortable).
	/// <para/>
	/// By default, keys and values are sorted using <see cref="Comparer{T}.Default"/>.
	/// This will work provided that the keys and values both implement the
	/// <see cref="IComparable{T}"/> interface. If they don't, you can pass custom 
	/// comparison functions to the constructor instead (one comparison function for
	/// keys, and a second one for values).
	/// <para/>
	/// Since it is derived from <see cref="BList{T}"/>, this class enjoys the space 
	/// efficiency of a B+ tree and capabilities of a <see cref="AListBase{K,V}"/>, 
	/// although it tends to be slower than <see cref="Dictionary{K,V}"/>.
	/// </remarks>
	[Serializable]
	public class BMultiMap<K, V> : BList<KeyValuePair<K, V>>
	{
		#region Constructors

		const int DefaultMaxLeafNodeSize = AListLeaf<KeyValuePair<K, V>, KeyValuePair<K, V>>.DefaultMaxNodeSize;
		const int DefaultMaxInnerNodeSize = AListInnerBase<KeyValuePair<K, V>, KeyValuePair<K, V>>.DefaultMaxNodeSize;

		public BMultiMap()
			: this(DefaultMaxLeafNodeSize, DefaultMaxInnerNodeSize) { }
		public BMultiMap(int maxLeafSize)
			: this(maxLeafSize, DefaultMaxInnerNodeSize) { }
		public BMultiMap(int maxLeafSize, int maxInnerSize)
			: base(DefaultPairComparison, maxLeafSize, maxInnerSize)
		{
			_compareKeys = DefaultKComparison;
			_compareValues = DefaultVComparison;
		}
		
		public BMultiMap(Func<K, K, int> compareKeys)
			: this(compareKeys, DefaultVComparison) { }
		public BMultiMap(Func<K, K, int> compareKeys, Func<V, V, int> compareValues)
			: this(compareKeys, compareValues, DefaultMaxLeafNodeSize, DefaultMaxInnerNodeSize) { }
		public BMultiMap(Func<K, K, int> compareKeys, Func<V, V, int> compareValues, int maxLeafSize)
			: this(compareKeys, compareValues, maxLeafSize, DefaultMaxInnerNodeSize) { }
		public BMultiMap(Func<K, K, int> compareKeys, Func<V, V, int> compareValues, int maxLeafSize, int maxInnerSize)
			: base(DefaultPairComparison, maxLeafSize, maxInnerSize)
		{
			_compareKeys = compareKeys;
			_compareValues = compareValues;
			_compareItems = CompareKeyAndValue;
		}

		#endregion
		
		#region Member variables and comparison functions

		protected readonly static Func<K, K, int> DefaultKComparison = Comparer<K>.Default.Compare;
		protected readonly static Func<V, V, int> DefaultVComparison = Comparer<V>.Default.Compare;
		protected readonly static Func<KeyValuePair<K, V>, KeyValuePair<K, V>, int> DefaultPairComparison = (a, b) =>
		{
			int c = DefaultKComparison(a.Key, b.Key);
			if (c != 0)
				return c;
			return DefaultVComparison(a.Value, b.Value);
		};

		protected readonly Func<K, K, int> _compareKeys;
		protected readonly Func<V, V, int> _compareValues;

		public int CompareKeyAndValue(KeyValuePair<K, V> a, KeyValuePair<K, V> b)
		{
			int c = _compareKeys(a.Key, b.Key);
			if (c != 0)
				return c;
			return _compareValues(a.Value, b.Value);
		}
		public int CompareKeysOnly(KeyValuePair<K, V> a, KeyValuePair<K, V> b)
		{
			return _compareKeys(a.Key, b.Key);
		}
		public int UpperBoundCompare(KeyValuePair<K, V> candidate, KeyValuePair<K, V> searchKey)
		{
			// When searchKey==candidate, act like searchKey>candidate.
			return -(_compareKeys(searchKey.Key, candidate.Key) | 1);
		}
		
		#endregion

		#region IDictionary-like Members

		/// <summary>Adds a key-value pair if there is not already a pair that 
		/// compares equal to the new one.</summary>
		/// <returns>True if the pair was added, or false if it already existed.</returns>
		public bool AddIfUnique(K key, V value)
		{
			return Do(AListOperation.AddIfNotPresent, new KeyValuePair<K, V>(key, value)) > 0;
		}

		/// <summary>Finds out whether the specified key is present.</summary>
		/// <param name="key">Key to search for</param>
		/// <returns>Returns true if the dictionary contains at least one key-
		/// value pair in which the key compares equal to the specified key.</returns>
		public bool ContainsKey(K key)
		{
			var op = new AListSingleOperation<KeyValuePair<K, V>, KeyValuePair<K, V>>();
			op.CompareToKey = op.CompareKeys = CompareKeysOnly;
			op.Key = new KeyValuePair<K,V>(key, default(V));
			OrganizedRetrieve(ref op);
			return op.Found;
		}

		/// <summary>Finds the lowest index of an item with the specified key.</summary>
		/// <param name="key">Key to search for</param>
		/// <returns>The index of the item that was found, or -1 if there is no such item.</returns>
		/// <remarks>This method is like <see cref="FindLowerBound"/> except that 
		/// it returns -1 if the key was not found.</remarks>
		public int FirstIndexOf(K key)
		{
			bool found;
			int index = FindLowerBound(key, out found);
			if (found)
				return index;
			else
				return -1;
		}

		/// <summary>Removes one pair from the collection that matches the specified key.</summary>
		/// <returns>True if a pair was removed, or false if the key was not found.</returns>
		public bool RemoveAny(K key)
		{
			var op = new AListSingleOperation<KeyValuePair<K, V>, KeyValuePair<K, V>>();
			op.Mode = AListOperation.Remove;
			op.CompareToKey = op.CompareKeys = CompareKeysOnly;
			op.Key = op.Item = new KeyValuePair<K, V>(key, default(V));
			return DoSingleOperation(ref op) < 0;
		}

		/// <summary>Removes up to a specified number of items from the collections 
		/// that have the specified key.</summary>
		/// <param name="key">The key to remove.</param>
		/// <param name="maxToRemove">Maximum number of items to remove.</param>
		/// <returns>The number of items removed.</returns>
		public int Remove(K key, int maxToRemove)
		{
			bool found;
			int lower = FindLowerBound(key, out found);
			if (!found)
				return 0;

			if (maxToRemove > 1)
			{
				int upper = FindUpperBound(key);
				int removeCount = Math.Min(maxToRemove, upper - lower);
				RemoveRange(lower, removeCount);
				return removeCount;
			}
			else if (maxToRemove == 1)
			{
				RemoveAt(lower);
				return 1;
			}
			return 0;
		}

		/// <summary>Removes all the items from the collection whose key compares 
		/// equal to the specified key.</summary>
		/// <param name="key">The key to remove.</param>
		/// <returns>The number of items removed.</returns>
		public int RemoveAll(K key)
		{
			return Remove(key, int.MaxValue);
		}

		/// <summary>Finds a value associated with the specified key.</summary>
		/// <param name="key">Key to find</param>
		/// <param name="value">Set to the first (lowest) value associated with the
		/// key, or default(V) if the key was not found.</param>
		/// <returns>True if the key was found, false if not.</returns>
		public bool TryGetValue(K key, out V value)
		{
			bool found;
			FindLowerBound(key, out value, out found);
			return found;
		}

		/// <summary>Gets a collection associated with the specified key.</summary>
		/// <param name="key"></param>
		/// <returns>A synthetic collection associated with the specified key.</returns>
		/// <remarks>This property always succeeds and does not actually search
		/// for the key you requested. It returns an object that represents the
		/// set of values associated with a key, but those values are not actually
		/// retrieved from the collection until you enumerate the collection.</remarks>
		/// <seealso cref="Values"/>
		public Values this[K key]
		{
			get { return new Values(this, key); }
		}

		/// <summary>Represents the set of values associated with a particular key 
		/// in a <see cref="BMultiMap{K,V}"/> collection.</summary>
		public struct Values : ISource<V>, ICollection<V>
		{
			readonly BMultiMap<K, V> _map;
			readonly K _key;
			
			internal Values(BMultiMap<K, V> map, K key)
			{
				_map = map;
				_key = key;
			}

			#region ISource<V> members

			public int Count
			{
				get {
					bool found;
					int lower = _map.FindLowerBound(_key, out found);
					if (!found)
						return 0;
					int upper = _map.FindUpperBound(_key);
					return upper - lower;
				}
			}

			public Iterator<V> GetIterator()
			{
				int index = _map.FirstIndexOf(_key);
				if (index <= -1)
					return EmptyIterator<V>.Value;

				var it = _map.GetIterator(index);
				var map = _map;
				var key = _key;
				return (ref bool ended) =>
				{
					var pair = it(ref ended);
					if (!ended)
					{
						if (map._compareKeys(pair.Key, key) != 0)
							ended = true;
					}
					return pair.Value;
				};
			}

			#endregion

			#region ICollection<V> Members

			/// <summary>Adds a new item associated with the key that this object 
			/// represents. Allows duplicate values.</summary>
			/// <param name="item">Value to add.</param>
			public void Add(V item)
			{
				_map.Add(new KeyValuePair<K,V>(_key, item));
			}
			public void Clear()
			{
				_map.RemoveAll(_key);
			}
			public bool Contains(V item)
			{
				return _map.Contains(new KeyValuePair<K, V>(_key, item));
			}
			public void CopyTo(V[] array, int arrayIndex)
			{
				LCInterfaces.CopyTo(this, array, arrayIndex);
			}
			public bool IsReadOnly
			{
				get { return _map.IsFrozen; }
			}
			public bool Remove(V item)
			{
				return _map.Remove(new KeyValuePair<K, V>(_key, item));
			}
			public IEnumerator<V> GetEnumerator()
			{
				return GetIterator().AsEnumerator();
			}
			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return GetEnumerator();
			}

			#endregion
		}

		#endregion

		#region FindLowerBound, FindUpperBound
		
		/// <summary>Finds the lowest index of an item that is equal to or greater than the specified item.</summary>
		/// <param name="key">The key to find.</param>
		/// <param name="value">The first value associated with the specified key,
		/// if the key was found, or default(V) if not.</param>
		/// <param name="found">Set to true if the item was found, false if not.</param>
		/// <returns>The index of the item that was found, or of the next greater
		/// item, or Count if the given key is greater than the keys of all items 
		/// in the list.</returns>
		public int FindLowerBound(K key)
		{
			bool found;
			V value;
			return FindLowerBound(ref key, out value, out found);
		}
		/// <inheritdoc cref="FindLowerBound(K)"/>
		public int FindLowerBound(K key, out bool found)
		{
			V value;
			return FindLowerBound(ref key, out value, out found);
		}
		/// <inheritdoc cref="FindLowerBound(K)"/>
		public int FindLowerBound(K key, out V value, out bool found)
		{
			return FindLowerBound(ref key, out value, out found);
		}
		public int FindLowerBound(ref K key, out V value, out bool found)
		{
			var op = new AListSingleOperation<KeyValuePair<K, V>, KeyValuePair<K, V>>();
			op.CompareKeys = op.CompareToKey = CompareKeysOnly;
			op.Key = new KeyValuePair<K, V>(key, default(V));
			op.LowerBound = true;
			OrganizedRetrieve(ref op);
			if (found = op.Found)
				key = op.Item.Key;
			value = op.Item.Value;
			return (int)op.BaseIndex;
		}

		/// <summary>Finds the index of the first item in the list that is greater 
		/// than the specified item.</summary>
		/// <param name="item">The item to find. If passed by reference, when this 
		/// method returns, item is set to the next greater item than the item you 
		/// searched for, or left unchanged if there is no greater item.</param>
		/// <param name="index">The index of the next greater item that was found,
		/// or Count if the given item is greater than all items in the list.</param>
		/// <returns></returns>
		public int FindUpperBound(K key)
		{
			var op = new AListSingleOperation<KeyValuePair<K, V>, KeyValuePair<K, V>>();
			op.CompareKeys = op.CompareToKey = UpperBoundCompare;
			op.Key = new KeyValuePair<K, V>(key, default(V));
			OrganizedRetrieve(ref op);
			return (int)op.BaseIndex;
		}

		/// <summary>Does the same thing as <see cref="IndexOfExact"/>, but with 
		/// the same set of arguments as <see cref="FindLowerBound"/> including
		/// the value associated with the matching key.</summary>
		/// <returns>Lowest index of a matching item if found, or the same return 
		/// value as <see cref="FindLowerBound"/> if not found.</returns>
		public int FindLowerBoundExact(ref K key, out V value, out bool found)
		{
			K searchFor = key;
			int lowerBound = FindLowerBound(ref key, out value, out found);
			if (!found)
				return lowerBound;

			found = false;
			object searchFor2 = searchFor;
			int index = lowerBound;
			while (key == null ? searchFor != null : !key.Equals(searchFor2))
			{
				if (++index >= Count)
					return lowerBound;
				var kvp = this[index];
				if (_compareKeys(kvp.Key, searchFor) != 0)
					return lowerBound;
				key = kvp.Key;
				value = kvp.Value;
			}
			found = true;
			return index;
		}

		/// <summary>
		/// Specialized search function that finds the first index of an item whose 
		/// key compares equal to the specified key, not only according to the 
		/// comparison function for this collection, but also according to 
		/// <see cref="Object.Equals"/>. This function works properly even if 
		/// duplicate keys exist in addition that do NOT compare equal according 
		/// to <see cref="Object.Equals"/>.
		/// </summary>
		/// <remarks>
		/// This method is useful when the items in this collection are sorted by
		/// hashcode (which is usually a bad idea, but occasionally useful).
		/// </remarks>
		public int IndexOfExact(K key)
		{
			V value;
			bool found;
			int index = FindLowerBoundExact(ref key, out value, out found);
			if (found)
				return index;
			else
				return -1;
		}
 
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer None
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, the Enhanced C# programming language (in progress), the parser generator LLLPG, and LES, a syntax to help you start building programming languages, DSLs or build systems.

My overall focus is on the Language of your choice (Loyc) initiative, which is about investigating ways to improve interoperability between programming languages and putting more power in the hands of developers. I'm also seeking employment.

Comments and Discussions