Click here to Skip to main content
15,891,828 members
Articles / Programming Languages / C#

MultiList: .NET Generic List with Multiple Sort Orders

Rate me:
Please Sign up or sign in to vote.
3.55/5 (5 votes)
1 Feb 2008CPOL6 min read 44.3K   200   16  
The MultiList class extends the functionality of the generic list. The MultiList class manages multiple sort orders on lists. It is best suited to object lists where searching is required on more than one criteria.
// 
// MultiList library - Copyright 2008 by Michael Moorman / AsAbove.net
// This work is released to the public domain, and may be used for any purpose, without any conditions.
// 
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using AsAbove.Collections;
using AsAbove.Collections.Exception;

namespace Test
{
	[TestFixture]
	public class UnitTest
	{
		private NameComparer nameComparer;
		private AgeComparer ageComparer;
		private CareerComparer careerComparer;
		private MultiList<Person> _ml;

		private enum bogusEnumerator
		{
			should,
			cause,
			exception
		}

		[TestFixtureSetUp]
		public void Init ()
		{
			nameComparer = new NameComparer ();
			ageComparer = new AgeComparer ();
			careerComparer = new CareerComparer ();
			_ml = new MultiList<Person> (typeof (PersonSortCriteria), nameComparer, ageComparer, careerComparer);
		}

		[Test]
		public void A01_AddAndFind ()
		{
			Person TomPerson = new Person ("Tom", 56, Person.Profession.Welder);
			_ml.Add (new Person ("Jim",	42, Person.Profession.Teacher));
			_ml.Add (new Person ("Susan", 29, Person.Profession.Lawyer));
			_ml.Add (new Person ("Arthur", 22, Person.Profession.Barber));
			_ml.Add (TomPerson);
			_ml.Add (new Person ("Betty", 50, Person.Profession.Teacher));

			_ml.Add (new Person ("Joe", 18, Person.Profession.Lawyer));
			_ml.Add (new Person ("Sandra", 25, Person.Profession.Carpenter));
			_ml.Add (new Person ("Andy", 56, Person.Profession.Barber));
			_ml.Add (new Person ("Ty", 19, Person.Profession.Carpenter));
			_ml.Add (new Person ("Bea", 33, Person.Profession.Nurse));

			_ml.Add (new Person ("Andy", 20, Person.Profession.Carpenter));
			_ml.Add (new Person ("Andy", 21, Person.Profession.Teacher));

			int position;
			bool found;

			// search and find position
			position = _ml.FindIndex<string> (PersonSortCriteria.Name, nameComparer, "Tom");
			Assert.AreNotEqual (position, MultiList<Person>.NotFound);
			Console.WriteLine ("Found 'Tom' in Name sort at position " + position);

			position = _ml.FindIndex<string> (PersonSortCriteria.Name, nameComparer, "Andy");
			Assert.AreNotEqual (position, MultiList<Person>.NotFound);
			Console.WriteLine ("Found 'Andy' in Name sort at position " + position);
			// there are two Andy persons, so this index should differ.
			int lastPosition = _ml.FindLastIndex<string> (PersonSortCriteria.Name, nameComparer, "Andy");
			Assert.AreNotEqual (position, lastPosition);

			// search and match by Tom's name using both find variants 
			Person match;
			found = _ml.TryFind<string> (PersonSortCriteria.Name, nameComparer, "Tom", out match);
			Assert.IsTrue (found);
			Assert.AreEqual (TomPerson, match);
			match = _ml.Find<string> (PersonSortCriteria.Name, nameComparer, "Tom");
			Assert.AreEqual (TomPerson, match);


			// search and match by Tom's age using both find variants
			found = _ml.TryFind<int> (PersonSortCriteria.Name, ageComparer, 56, out match);
			Assert.IsTrue (found);
			Assert.AreEqual (TomPerson, match);
			match = _ml.Find<string> (PersonSortCriteria.Name, nameComparer, "Tom");
			Assert.AreEqual (TomPerson, match);

			// search and match by Tom's career using both find variants
			found = _ml.TryFind<Person.Profession> (PersonSortCriteria.Career, careerComparer, Person.Profession.Welder, out match);
			Assert.IsTrue (found);
			Assert.AreEqual (TomPerson, match);
			match = _ml.Find<Person.Profession> (PersonSortCriteria.Career, careerComparer, Person.Profession.Welder);
			Assert.AreEqual (TomPerson, match);

			// search and find age position, that we know does *NOT* exist
			position = _ml.FindIndex<int> (PersonSortCriteria.Age, ageComparer, 44);
			Assert.AreEqual (position, MultiList<Person>.NotFound);

			// search and find age position, that we know exists
			position = _ml.FindIndex<int> (PersonSortCriteria.Age, ageComparer, 25);
			Assert.AreNotEqual (position, MultiList<Person>.NotFound);

			// search career position
			position = _ml.FindIndex<Person.Profession> (PersonSortCriteria.Career, careerComparer, Person.Profession.Barber);
			Assert.AreNotEqual (position, MultiList<Person>.NotFound);
			Console.WriteLine ("Found 'Barber' in Profession sort at position " + position);

			// search career position
			position = _ml.FindIndex<Person.Profession> (PersonSortCriteria.Career, careerComparer, Person.Profession.Carpenter);
			Assert.AreNotEqual (position, MultiList<Person>.NotFound);
			Console.WriteLine ("Found 'Carpenter' in Profession sort at position " + position);

			Dump ("\r\nAfter A01_AddAndFind...");
		}

		[Test]
		public void A02_FindIndexes ()
		{
			Person match = _ml.Find<string> (PersonSortCriteria.Name, nameComparer, "Andy");

			// search for "Andy" which should have three entries
			_ml.ActiveList = PersonSortCriteria.Name;
			IndexRange range = _ml.FindIndexes (match);
			Assert.AreEqual (3, range.Count);
			Console.WriteLine ("Found Andy at offsets {0} through {1}", range.Start, range.End);

			// try the other search methods - and verify the correct number of returned objects
			Person[] persons = _ml.FindMultiple (match);
			Assert.AreEqual (3, persons.Length);
			persons = _ml.FindMultiple (PersonSortCriteria.Name, match);
			Assert.AreEqual (3, persons.Length);
			persons = _ml.FindMultiple<string> (PersonSortCriteria.Name, nameComparer, "Andy");
			Assert.AreEqual (3, persons.Length);
			persons = _ml.FindMultiple<string> (nameComparer, "Andy");
			Assert.AreEqual (3, persons.Length);

			// should be three carpenters
			_ml.ActiveList = PersonSortCriteria.Career;
			range = _ml.FindIndexes<Person.Profession> (careerComparer, Person.Profession.Carpenter);
			Assert.AreEqual (3, range.Count);
			Console.WriteLine ("Found Carpenters at offsets {0} through {1}", range.Start, range.End);

			// should be two 56 year olds
			_ml.ActiveList = PersonSortCriteria.Age;
			range = _ml.FindIndexes<int> (ageComparer, 56);
			Assert.AreEqual (2, range.Count);
			Console.WriteLine ("Found 56 y.o. at offsets {0} through {1}", range.Start, range.End);

		}

		[Test]
		public void A03_Remove ()
		{
			Person match;
			bool found = _ml.TryFind<string> (PersonSortCriteria.Name, nameComparer, "Tom", out match);
			Assert.IsTrue (found);
			_ml.Remove (match);

			found = _ml.TryFind<int> (PersonSortCriteria.Age, ageComparer, 50, out match);
			Assert.IsTrue (found);
			_ml.Remove (match);

			Dump ("\r\nAfter A02_Remove removing name 'Tom' and age 50...");
		}

		[Test]
		public void A04_Remove ()
		{
			Person match;
			int count = 0;
			// in this test, we will iteratively remove all carpenters. We know that there are three
			// of them, so the list should go three times.
			while (true == _ml.TryFind<Person.Profession> (PersonSortCriteria.Career, careerComparer, Person.Profession.Carpenter, out match))
			{
				++count;
				_ml.Remove (match);
			}
			Assert.AreEqual (3, count);

			Dump ("\r\nAfter A03_Remove removing 'Carpenter'...");
		}

		[Test]
		public void B01_EnumeratorTypeExpectedException ()
		{
			try
			{	// this should throw an exception because we're passing the wrong type
				// as the first argument of the constructor.
				MultiList<Person> p = new MultiList<Person> (typeof (Person), nameComparer, ageComparer, careerComparer);
				Assert.Fail ("Expected EnumeratorTypeExpectedException");
			}
			catch (EnumeratorTypeExpectedException)
			{}
			catch (Exception)
			{
				Assert.Fail ("Expected EnumeratorTypeExpectedException");
			}
		}

		[Test]
		public void B02_InsufficientIComparerCount ()
		{
			try
			{	// this should fail because we don't pass 3 comparers to the constructor.
				MultiList<Person> p = new MultiList<Person> (typeof (PersonSortCriteria), nameComparer, ageComparer);
				Assert.Fail ("Expected InsufficientIComparerCount");
			}
			catch (InsufficientIComparerCountException)
			{ }
			catch (Exception)
			{
				Assert.Fail ("Expected InsufficientIComparerCount");
			}
		}

		[Test]
		public void B03_InvalidEnumeratorException ()
		{
			try
			{	// this should fail because we pass the wrong enumerator type
				_ml.FindIndex<string> (bogusEnumerator.exception, nameComparer, "Tom");
				Assert.Fail ("Expected InvalidEnumeratorException");
			}
			catch (InvalidEnumeratorException)
			{ }
			catch (Exception)
			{
				Assert.Fail ("Expected InvalidEnumeratorException");
			}
		}

		[Test]
		public void B04_MultiListObjectNotFoundException ()
		{
			try
			{	// this should fail because no such Person object exists in the list
				_ml.Find<string> (PersonSortCriteria.Name, nameComparer, "Oprah");
				Assert.Fail ("Expected MultiListObjectNotFoundException");
			}
			catch (MultiListObjectNotFoundException)
			{ }
			catch (Exception)
			{
				Assert.Fail ("Expected MultiListObjectNotFoundException");
			}
		}

		private void Dump (string comment)
		{
			Console.WriteLine (comment);
			DumpList (PersonSortCriteria.Name);
			DumpList (PersonSortCriteria.Age);
			DumpList (PersonSortCriteria.Career);
		}

		private void DumpList (PersonSortCriteria attribute)
		{
			int count = _ml.Count;
			int index = 0;
			Console.WriteLine ("list sorted by " + attribute);
			_ml.ActiveList = attribute;
			foreach (Person p in _ml)
			{
				Console.WriteLine ("{0}.\t{1}, {2}, {3}", index++, p.Name, p.Age, p.Career);
			}

			// assert that the list count equals the number of iterations
			Assert.AreEqual (count, index);
			Console.WriteLine ("");
		}

		#region Sort criteria and Comparer class declarations
		// this enumerator is declared to define the sort orders that we want on the Person class.
		private enum PersonSortCriteria
		{
			Name,
			Age,
			Career
		};

		public class NameComparer : MultiList<Person>.ITypeComparer<string>, IComparer<Person>
		{
			public int Compare (Person p1, Person p2)
			{
				return p1.Name.CompareTo (p2.Name);
			}
			public int Compare (Person p, string name)
			{
				return p.Name.CompareTo (name);
			}
		}

		public class AgeComparer : MultiList<Person>.ITypeComparer<int>, IComparer<Person>
		{
			public int Compare (Person p1, Person p2)
			{
				return p1.Age - p2.Age;
			}
			public int Compare (Person p, int age)
			{
				return p.Age - age;
			}
		}

		public class CareerComparer : MultiList<Person>.ITypeComparer<Person.Profession>, IComparer<Person>
		{
			public int Compare (Person p1, Person p2)
			{
				return p1.Career.CompareTo (p2.Career);
			}
			public int Compare (Person p, Person.Profession career)
			{
				return p.Career.CompareTo (career);
			}
		}
		#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 Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions