Click here to Skip to main content
15,896,318 members
Articles / Programming Languages / Visual Basic 10

Having fun with custom collections!

Rate me:
Please Sign up or sign in to vote.
4.91/5 (71 votes)
14 Oct 2011CPOL44 min read 187.6K   2.9K   121  
Creating custom collections from IEnumerable(T) to IDictionary(T) and everything in between!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace TheCult
{
	/// <summary>
	/// A Class representing a cult. Each person that enters gets a mark and becomes a cultist.
	/// </summary>
	public class Cult : ICollection<Person>, IListSource
	{

		private List<Cultist> _innerList;
		private int _mark;

		/// <summary>
		/// Instantiates a new cult with no members.
		/// </summary>
		public Cult()
		{
			_innerList = new List<Cultist>();
		}

		/// <summary>
		/// Instantiates a new cult with members.
		/// </summary>
		/// <param name="people">The people that will join the cult.</param>
		public Cult(IEnumerable<Person> people)
			: this()
		{
			this.AddRange(people);
		}

		protected List<Cultist> InnerList
		{
			get { return _innerList; }
		}

		/// <summary>
		/// Calculates if a new captain should be assigned to keep the members in line.
		/// </summary>
		/// <returns>True if a new captain should be assigned.</returns>
		protected bool NewCaptainNeeded()
		{
			int captains = (this.InnerList.Where(c => c.Rank == CultRanks.Captain).Count());
			int noOfCultists = this.InnerList.Count;
			int neededCaptains = 0;
			// For every 10th member after 10 there should be a captain.
			if ((noOfCultists - 10) - (captains * 10) >= 10 || noOfCultists % 10 == 0m)
			{
				while (noOfCultists > 0)
				{
					noOfCultists -= 10;
					neededCaptains += 1;
				}
				// Needed captains - 1 because the tenth member becomes
				// a general instead of captain.
				return neededCaptains - 1 > captains;
			}
			return false;
		}

		/// <summary>
		/// Assigns a new captain if it is absolutely necessary to keep the members in line.
		/// </summary>
		protected void AssignNextCaptainIfNecessary()
		{
			if (NewCaptainNeeded())
			{
				AssignNextCaptain();
			}
		}

		/// <summary>
		/// Assigns the first person who is elligible for becoming a captain.
		/// </summary>
		protected void AssignNextCaptain()
		{
			// The person who is in the cult the longest, but who is still a soldier gets the rank captain.
			this.InnerList.Where(c => c.Rank == CultRanks.Soldier).First().Rank = CultRanks.Captain;
		}

		/// <summary>
		/// Checks the highest mark of any member that is currently in the cult and creates a new mark based on that.
		/// </summary>
		/// <returns>A new, unique mark that is 1 higher than the current highest mark.</returns>
		protected virtual int GetMark()
		{

			if (this.InnerList.Count > 0)
			{
				_mark += 1;
			}
			else
			{
				// If no cultists yet exist get a random number between 500 and 1000 because we can.
				Random rnd = new Random();
				_mark = rnd.Next(500, 1000);
			}

			return _mark;
		}

		/// <summary>
		/// Checks if a person is eligible to join the cult.
		/// </summary>
		/// <param name="person">The person to check.</param>
		/// <returns>True if a person can join the cult.</returns>
		protected virtual bool CanJoin(Person person)
		{
			// A person is only able to join if no person with the same name is already in the cult.
			return !this.Contains(person, new PersonNameEqualityComparer());
		}

		/// <summary>
		/// Makes a group of people join the cult and become cultists. The new cultists will have the lowest ranks in the cult.
		/// </summary>
		/// <param name="people">The people who will join the cult and become cultists.</param>
		public void AddRange(IEnumerable<Person> people)
		{
			foreach (Person p in people)
			{
				this.Add(p);
			}
		}

		/// <summary>
		/// Makes a person join the cult and become a cultist. The new cultist will have the lowest rank in the cult.
		/// </summary>
		/// <param name="item">The person who will join the cult and become a cultist.</param>
		public void Add(Person item)
		{
			if (item == null)
				throw new ArgumentNullException("Item cannot be nothing.");

			// Check if the person is allowed to enter the cult.

			if (CanJoin(item))
			{
				// Create a new cultist with the current person.
				Cultist cultist = Cultist.CreateCultist(item, GetMark());
				// Add the new recruit to the cult!
				this.InnerList.Add(cultist);

				// Check how many members the cult currently has and
				// set the new recruits rank accordingly.
				int count = this.InnerList.Count;
				switch (count)
				{

					case 1:
						// If the inner list has no items yet then the cultist that is
						// to be added becomes the first cultist and thus gets leader status.
						cultist.Rank = CultRanks.Leader;

						break;
					case 10:
						// If the cult has 10 members then it is needed to get a second in command.
						// The second person to join the cult becomes general.
						this.InnerList[1].Rank = CultRanks.General;
						// The to be added cultist becomes a soldier.
						cultist.Rank = CultRanks.Soldier;

						break;
					default:
						// Nothing special.
						// The to be added cultist becomes a soldier.
						cultist.Rank = CultRanks.Soldier;

						break;
				}

				// If there are 20 cult members or any number dividable by 10 after that
				// then it is necessary to get a new person that can keep the peace.
				AssignNextCaptainIfNecessary();
			}
		}

		/// <summary>
		/// Removes all cultists from the cult.
		/// </summary>
		public void Clear()
		{
			// Clears the list of cultists (perhaps the police raided the place?).
			this.InnerList.Clear();
		}

		/// <summary>
		/// Determines whether a person is a member of the cult.
		/// </summary>
		/// <param name="item">The person to check if he is in the cult.</param>
		/// <returns>True if the person is secretly a cultist.</returns>
		public bool Contains(Person item)
		{
			return (this.InnerList.Where(cultist => cultist.FullName == item.FullName).FirstOrDefault() != null);
		}

		/// <summary>
		/// Copies the entire cult following to a compatible one-dimensional array, starting at the specified index of the target array.
		/// </summary>
		/// <param name="array">The one-dimensional Array that is the destination of the elements copied from the cult. The Array must have zero-based indexing.</param>
		/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
		/// <exception cref="System.ArgumentNullException">Array is null.</exception>
		/// <exception cref="System.ArgumentOutOfRangeException">arrayIndex is less than 0.</exception>
		/// <exception cref="System.ArgumentException">The number of elements in the source System.Collections.Generic.List(Of T) is greater than the available space from arrayIndex to the end of the destination array."</exception>
		public void CopyTo(Person[] array, int arrayIndex)
		{
			List<Person> tempList = new List<Person>();
			tempList.AddRange(this.InnerList);
			tempList.CopyTo(array, arrayIndex);
		}

		/// <summary>
		/// Gets the number of cultists that are a member of the cult.
		/// </summary>
		/// <returns>The number of cultists actually contained in the cult.</returns>
		public int Count
		{
			get { return this.InnerList.Count; }
		}

		// This is private, kind of 'not implemented', but also not quite.
		/// <summary>
		/// Gets a value indicating whether the cult is read-only.
		/// </summary>
		/// <returns>True if the cult is read-only; otherwise, false.</returns>
		bool ICollection<Person>.IsReadOnly
		{
			get { return false; }
		}

		/// <summary>
		/// Removes a person from the cult if this person is a member. New ranks will be assigned as necessary.
		/// </summary>
		/// <param name="item">Person to remove from the cult.</param>
		/// <returns>True if person is successfully removed; otherwise, false. This method also returns false if person was not found in the cult.</returns>
		public bool Remove(Person item)
		{
			// First retrieve the cultist based on the persons name.
			Cultist cultist = this.InnerList.Where(c => c.FullName == item.FullName).FirstOrDefault();
			// Check if a cultist with the given name exists.
			if (cultist != null)
			{
				if (this.InnerList.Remove(cultist))
				{
					// If the cultist was removed the ranks have to be recalculated.
					// We do not know who was removed from the cult. Maybe it was the leader
					// or general, so we need some promotions.
					RecalculateRanksOnRemove(cultist);
					cultist.Rank = CultRanks.None;
					return true;
				}

			}
			return false;
		}

		/// <summary>
		/// Recalculates ranks of the members if a member was removed from the cult.
		/// </summary>
		/// <param name="cultist">The cultist that was removed.</param>
		private void RecalculateRanksOnRemove(Cultist cultist)
		{
			if (this.InnerList.Count > 0)
			{
				// Set the new ranks based on the rank of the current cultist.
				switch (cultist.Rank)
				{

					case CultRanks.Leader:
						// If the cultist was the leader then the new leader will be assigned.
						// The former general will be leader so a new general will also be assigned.
						// This could mean that a new captain is needed to.
						this.InnerList[0].Rank = CultRanks.Leader;
						if (this.InnerList.Count > 1)
						{
							this.InnerList[1].Rank = CultRanks.General;
							AssignNextCaptainIfNecessary();
						}

						break;
					case CultRanks.General:
						// If a general was removed then assign a new general and check if there are still enough captains.
						if (this.InnerList.Count > 1)
						{
							this.InnerList[1].Rank = CultRanks.General;
							AssignNextCaptainIfNecessary();
						}

						break;
					case CultRanks.Captain:
						// If a captain was removed check if new captains are necessary.
						AssignNextCaptainIfNecessary();

						break;
					default:
						break;
					// Do Nothing.

				}
			}
		}

		/// <summary>
		/// Returns an Enumerator that iterates through the cultists.
		/// </summary>
		/// <returns>An Enumerator for the cult.</returns>
		public System.Collections.Generic.IEnumerator<Person> GetEnumerator()
		{
			return new GenericEnumerator.GenericEnumerator<Cultist>(this.InnerList);
		}

		/// <summary>
		/// Returns an Enumerator that iterates through the cultists.
		/// </summary>
		/// <returns>An Enumerator for the cult.</returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		/// <summary>
		/// Gets a List(Of Cultist) rather than a List(Of People) that are contained in the cult.
		/// </summary>
		/// <returns>A list of cultists.</returns>
		public List<Cultist> GetCultists()
		{
			return new List<Cultist>(this.InnerList);
		}

		bool IListSource.ContainsListCollection
		{
			get { return true; }
		}

		System.Collections.IList IListSource.GetList()
		{
			return this.InnerList;
		}

	}
}

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
CEO JUUN Software
Netherlands Netherlands
Sander Rossel is a Microsoft certified professional developer with experience and expertise in .NET and .NET Core (C#, ASP.NET, and Entity Framework), SQL Server, Azure, Azure DevOps, JavaScript, MongoDB, and other technologies.

He is the owner of JUUN Software, a company specializing in custom software. JUUN Software uses modern, but proven technologies, such as .NET Core, Azure and Azure DevOps.

You can't miss his books on Amazon and his free e-books on Syncfusion!

He wrote a JavaScript LINQ library, arrgh.js (works in IE8+, Edge, Firefox, Chrome, and probably everything else).

Check out his prize-winning articles on CodeProject as well!

Comments and Discussions