|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MagicStove
{
/// <summary>
/// Represents a collection of ingredients where the (type of) an ingredient is the key
/// and the amount of that ingredient the value.
/// </summary>
public class Ingredients : IDictionary<IIngredient, int>
{
private const String ErrorMessage = "Adding or removing the specified amount of ingredients will result in a negative. Ingredients not added.";
// The innerDictionary has a ChangeableDictionaryValue(Of Integer) as value
// because the value of a KeyValuePair cannot be changed once it is assigned.
private Dictionary<IIngredient, ChangeableDictionaryValue<int>> _innerDictionary;
/// <summary>
/// Instantiates a new cult with no members.
/// </summary>
public Ingredients()
: base()
{
_innerDictionary = new Dictionary<IIngredient, ChangeableDictionaryValue<int>>();
}
/// <summary>
/// Returns a KeyValuePair of the _innerDictionary given a key.
/// The KeyValuePair is selected by type of the key rather than its spot in memory.
/// </summary>
/// <param name="key">An ingredient to look for.</param>
/// <returns>A KeyValuePair where the type of the key is the same as the type of the key parameter.</returns>
private KeyValuePair<IIngredient, ChangeableDictionaryValue<int>> GetPairByKeyType(IIngredient key)
{
KeyValuePair<IIngredient, ChangeableDictionaryValue<int>> pair = _innerDictionary.Where(p => p.Key.GetType() == key.GetType()).FirstOrDefault();
if (pair.Key == null)
{
throw new KeyNotFoundException();
}
else
{
return pair;
}
}
/// <summary>
/// Adds the specified amount of the ingredient.
/// </summary>
/// <param name="key">The ingredient to add.</param>
/// <param name="value">The amount to add of the specified ingredient.</param>
public virtual void Add(IIngredient key, int value)
{
// Cast to call the explicitly implemented Add Method.
(this as ICollection<KeyValuePair<IIngredient, int>>).Add(new KeyValuePair<IIngredient, int>(key, value));
}
/// <summary>
/// Adds the KeyValuePair to the _innerDictionary.
/// </summary>
/// <param name="item">The KeyValuePair to add.</param>
void ICollection<KeyValuePair<IIngredient, int>>.Add(System.Collections.Generic.KeyValuePair<IIngredient, int> item)
{
// If the key already exists then do not add a new KeyValuePair.
// Add the specified amount to the already existing KeyValuePair instead.
if (this.ContainsKey(item.Key))
{
if (this[item.Key] + item.Value < 0)
{
throw new InvalidOperationException(ErrorMessage);
}
else
{
this[item.Key] += item.Value;
}
}
else
{
if (item.Value < 0)
{
throw new InvalidOperationException(ErrorMessage);
}
else
{
_innerDictionary.Add(item.Key, new ChangeableDictionaryValue<int>(item.Value));
}
}
}
/// <summary>
/// Removes an ingredient.
/// </summary>
/// <param name="key">The ingredient to remove.</param>
/// <returns>True if the key was found and successfully removed.</returns>
public virtual bool Remove(IIngredient key)
{
if (this.ContainsKey(key))
{
return _innerDictionary.Remove(GetPairByKeyType(key).Key);
}
else
{
return false;
}
}
/// <summary>
/// Removes the specified value from the ingredient of the key.
/// Removes by checking the type of the key rather than pointer.
/// </summary>
/// <param name="item">The KeyValuePair to remove.</param>
/// <returns>True if the key was found and successfully removed.</returns>
bool ICollection<KeyValuePair<IIngredient, int>>.Remove(System.Collections.Generic.KeyValuePair<IIngredient, int> item)
{
this.Add(item.Key, -item.Value);
return true;
}
/// <summary>
/// Clears the dictionary of ingredients.
/// </summary>
public void Clear()
{
_innerDictionary.Clear();
}
/// <summary>
/// Checks if the specified amount of the key ingredient is present in the dictionary.
/// </summary>
/// <param name="ingredient">The key ingredient to check for presence.</param>
/// <param name="amount">The amount of the ingredient to check for presence.</param>
/// <returns>True if the specified amount of the given key ingredient is present in the dictionary.</returns>
public bool Contains(IIngredient ingredient, int amount)
{
return this.Contains(new KeyValuePair<IIngredient, int>(ingredient, amount));
}
/// <summary>
/// Checks if the specified KeyValuePair is present in the dictionary.
/// Does not check pointer, but if the amount of the specified key type is present.
/// </summary>
/// <param name="item">The KeyValuePair containing the key ingredient and amount to check.</param>
/// <returns>True if the specified amount of the given key ingredient is present in the dictionary.</returns>
bool ICollection<KeyValuePair<IIngredient, int>>.Contains(System.Collections.Generic.KeyValuePair<IIngredient, int> item)
{
if (this.ContainsKey(item.Key))
{
return GetPairByKeyType(item.Key).Value.Value >= item.Value;
}
else
{
return false;
}
}
/// <summary>
/// Checks if an ingredient with the same type as the specified key is already present in the dictionary.
/// </summary>
/// <param name="key">The key ingredient to check for presence.</param>
/// <returns>True if a type of the specified ingredient is present in the dictionary.</returns>
public bool ContainsKey(IIngredient key)
{
KeyValuePair<IIngredient, ChangeableDictionaryValue<int>> pair = _innerDictionary.Where(p => p.Key.GetType() == key.GetType()).FirstOrDefault();
if (pair.Key == null)
{
return false;
}
else
{
return true;
}
}
/// <summary>
/// Gets the amount of an ingredient if the ingredient exists.
/// </summary>
/// <param name="key">The ingredient to get the amount of.</param>
/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter.</param>
/// <returns>True if the key exists and the value was found.</returns>
public bool TryGetValue(IIngredient key, out int value)
{
if (this.ContainsKey(key))
{
value = GetPairByKeyType(key).Value.Value;
return true;
}
else
{
value = default(int);
return false;
}
}
/// <summary>
/// Gets or sets the amount associated with the specified ingredient.
/// </summary>
/// <param name="key">An ingredient to get or set the amount of.</param>
/// <value>The amount of the ingredient to set.</value>
/// <returns>The amount of the specified ingredient.</returns>
public virtual int this[IIngredient key]
{
get { return GetPairByKeyType(key).Value.Value; }
set
{
if (value < 0)
{
throw new InvalidOperationException(ErrorMessage);
}
else
{
GetPairByKeyType(key).Value.Value = value;
}
}
}
/// <summary>
/// Gets a collection containing all ingredients in the dictionary.
/// </summary>
/// <returns>A collection of ingredients.</returns>
public System.Collections.Generic.ICollection<IIngredient> Keys
{
get { return _innerDictionary.Keys; }
}
/// <summary>
/// Gets a collection containing all amounts of ingredients in the dictionary.
/// </summary>
/// <returns>A collection of integers.</returns>
public System.Collections.Generic.ICollection<int> Values
{
get
{
return InnerDictToIntValue().Values;
}
}
/// <summary>
/// Gets the amount of unique ingredients in the dictionary.
/// </summary>
/// <returns>The amount of unique ingredients in the dictionary.</returns>
public int Count
{
get { return _innerDictionary.Count; }
}
/// <summary>
/// Returns a value that specifies wether the dictionary is read only.
/// </summary>
/// <returns>True if the dictionary is read only.</returns>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Converts the _innerDictionary to a new dictionary which values cannot be changed.
/// </summary>
/// <returns>A new dictionary containing all keys and values of the _innerDictionary.</returns>
private Dictionary<IIngredient, int> InnerDictToIntValue()
{
Dictionary<IIngredient, int> dict = new Dictionary<IIngredient, int>();
// Put all KeyValuePairs of the _innerDictionary in the new dictionary using a one-line LINQ query.
_innerDictionary.ToList().ForEach(pair => dict.Add(pair.Key, pair.Value.Value));
return dict;
}
/// <summary>
/// Copies the elements of the Dictionary to an Array, starting at a particular Array index.
/// </summary>
/// <param name="array">The one-dimensional Array that is the destination of the elements copied from the Dictionary. The Array must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
void ICollection<KeyValuePair<IIngredient, int>>.CopyTo(KeyValuePair<IIngredient, int>[] array, int arrayIndex)
{
// We do not want to implement this Method ourselves. The _innerDictionary already has it implemented.
// This is a private Method of the _innerDictionary though, so we first have to cast it to an IDictionary.
// Remember that the _innerDictionary has a ChangeableValue while we Implement an IDictionary with an Integer as value.
// First convert the _innerDictionary to a Dictionary with an Integer as value.
((IDictionary<IIngredient, int>)InnerDictToIntValue()).CopyTo(array, arrayIndex);
}
/// <summary>
/// Returns an Enumerator that iterates through the ingredients and their amounts.
/// </summary>
/// <returns>An Enumerator for ingredients.</returns>
public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<IIngredient, int>> GetEnumerator()
{
// First convert the _innerDictionary to a Dictionary having an Integer as value.
return InnerDictToIntValue().GetEnumerator();
}
/// <summary>
/// Returns an Enumerator that iterates through the ingredients and their amounts.
/// </summary>
/// <returns>An Enumerator for ingredients.</returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}
|
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.
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!