<|start_filename|>tests/rm.ExtensionsTest/DateTimeExtensionTest.cs<|end_filename|>
﻿using System;
using System.Globalization;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class DateTimeExtensionTest
	{
		[Test]
		public void ToUtcFormatString01()
		{
			var date = new DateTime(1994, 11, 05, 13, 15, 30, DateTimeKind.Utc);
			Assert.AreEqual("1994-11-05T13:15:30.000Z", date.ToUtcFormatString());
		}

		[Test]
		public void ToSqlDateTimeMinUtc01()
		{
			var date = new DateTime().ToSqlDateTimeMinUtc();
			var expectedDate = new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Utc);
			Assert.AreEqual(expectedDate, date);
			Assert.AreEqual(expectedDate.Kind, date.Kind);
		}

		[Test]
		public void AsUtcKind01()
		{
			var date = DateTime.ParseExact(
				"4/1/2014 12:00:00 AM", "M/d/yyyy hh:mm:ss tt", CultureInfo.CurrentCulture);
			Assert.AreEqual(DateTimeKind.Unspecified, date.Kind);
			date = date.AsUtcKind();
			Assert.AreEqual(DateTimeKind.Utc, date.Kind);
			var expectedDate = new DateTime(2014, 4, 1, 0, 0, 0, DateTimeKind.Utc);
			Assert.AreEqual(expectedDate, date);
			Assert.AreEqual(expectedDate.Kind, date.Kind);
			Assert.AreEqual(expectedDate, date.ToUniversalTime());
			Assert.AreEqual(expectedDate.Kind, date.ToUniversalTime().Kind);
		}
	}
}


<|start_filename|>src/rm.Extensions/WrappedExtension.cs<|end_filename|>
﻿namespace rm.Extensions
{
	/// <summary>
	/// Wrapped extensions.
	/// </summary>
	public static class WrappedExtension
	{
		/// <summary>
		/// Returns a Wrapped{T} instance for <paramref name="t"/>.
		/// </summary>
		public static Wrapped<T> Wrap<T>(this T t)
		{
			return new Wrapped<T>(t);
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/DictionaryExtensionTest.cs<|end_filename|>
﻿using System;
using System.Linq;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class DictionaryExtensionTest
	{
		[Test]
		[TestCase(new[] { 1, 2 }, 3, 0)]
		[TestCase(new[] { 1, 2 }, 1, 1)]
		[TestCase(new[] { 0, 1, 2 }, 0, 0)]
		public void GetValueOrDefault01(int[] a, int key, int expected)
		{
			var dictionary = a.ToDictionary(x => x);
			Assert.AreEqual(expected, dictionary.GetValueOrDefault(key));
		}

		[Test]
		[TestCase(new[] { 1, 2 }, 3, null)]
		[TestCase(new[] { 1, 2 }, 1, "1")]
		[TestCase(new[] { 0, 1, 2 }, 0, "0")]
		public void GetValueOrDefault02(int[] a, int key, string expected)
		{
			var dictionary = a.ToDictionary(x => x, y => y.ToString());
			Assert.AreEqual(expected, dictionary.GetValueOrDefault(key));
		}

		[Test]
		public void AsReadOnly01()
		{
			var dictionary = new[] { 0, 1, 2 }.ToDictionary(x => x, y => y.ToString()).AsReadOnly();
			Assert.Throws<NotSupportedException>(() => dictionary[5] = "5");
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/StringBuilderExtensionTest.cs<|end_filename|>
﻿using System;
using System.Linq;
using System.Text;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class StringBuilderExtensionTest
	{
		private static readonly string CRLF = Environment.NewLine;
		private static readonly string LFCR = new string(Environment.NewLine.ToCharArray().Reverse().ToArray());

		[Test]
		[TestCase("this {0} {1} test.", "is", "a")]
		[TestCase("this test.")]
		[TestCase("this ->{0}<- test.", null, null)]
		public void AppendLine01(string format, params object[] args)
		{
			var result = new StringBuilder().AppendLine(format, args).ToString();
			Console.WriteLine(result);
			Assert.AreEqual(string.Format(format, args) + Environment.NewLine, result);
		}

		[Test]
		[TestCase("this", "siht")]
		[TestCase("this test", "tset siht")]
		[TestCase("0123456789", "9876543210")]
		[TestCase("t", "t")]
		[TestCase("", "")]
		[TestCase("this{CRLF}test", "tset{LFCR}siht")]
		[TestCase("Les Misérables", "selbarésiM seL")]
		[TestCase("Les Mise\u0301rables", "selbaŕesiM seL")]
		public void Reverse01(string input, string expected)
		{
			var actual = new StringBuilder(input.Replace("{CRLF}", CRLF)).Reverse().ToString();
			Assert.AreEqual(expected.Replace("{LFCR}", LFCR), actual);
		}
	}
}


<|start_filename|>src/rm.Extensions/HashQueue.cs<|end_filename|>
﻿using System;
using System.Collections;
using System.Collections.Generic;
using rm.Extensions.Deque;
using Ex = rm.Extensions.ExceptionHelper;

namespace rm.Extensions
{
	/// <summary>
	/// Defines hashqueue methods.
	/// </summary>
	public interface IHashQueue<T> : IEnumerable<T>
	{
		/// <summary>
		/// Enqueues <paramref name="x"/> into hashqueue.
		/// </summary>
		void Enqueue(T x);

		/// <summary>
		/// Dequeues head from hashqueue.
		/// </summary>
		T Dequeue();

		/// <summary>
		/// Deletes given <paramref name="x"/> from hashqueue.
		/// Returns true if successful.
		/// </summary>
		bool Delete(T x);

		/// <summary>
		/// Peeks head of hashqueue.
		/// </summary>
		T Peek();

		/// <summary>
		/// Peeks tail of hashqueue.
		/// </summary>
		T PeekTail();

		/// <summary>
		/// Returns true if <paramref name="x"/> is in hashqueue.
		/// </summary>
		bool Has(T x);

		/// <summary>
		/// Returns true if hashqueue is empty.
		/// </summary>
		bool IsEmpty();

		/// <summary>
		/// Returns count of hashqueue.
		/// </summary>
		int Count();

		/// <summary>
		/// Returns long count of hashqueue.
		/// </summary>
		long LongCount();

		/// <summary>
		/// Clears hashqueue.
		/// </summary>
		void Clear();
	}

	/// <summary>
	/// Hashqueue.
	/// </summary>
	/// <remarks>
	/// Uses deque and map as backing store.
	///
	/// All methods are O(1) time.
	/// </remarks>
	public class HashQueue<T> : IHashQueue<T>
	{
		#region members

		IDeque<T> dq;
		IDictionary<T, Deque<Node<T>>> map;

		#endregion

		#region ctors

		public HashQueue()
		{
			dq = new Deque<T>();
			map = new Dictionary<T, Deque<Node<T>>>();
		}

		#endregion

		#region IHashQueue<T> methods

		/// <summary>
		/// Enqueues <paramref name="x"/> into hashqueue.
		/// </summary>
		public void Enqueue(T x)
		{
			x.ThrowIfArgumentNull(nameof(x));
			if (!map.TryGetValue(x, out Deque<Node<T>> nodeq))
			{
				nodeq = new Deque<Node<T>>();
				map[x] = nodeq;
			}
			nodeq.Enqueue(dq.Enqueue(x));
		}

		/// <summary>
		/// Dequeues head from hashqueue.
		/// </summary>
		public T Dequeue()
		{
			Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
			var x = dq.Dequeue();
			DeleteFromMap(x);
			return x;
		}

		/// <summary>
		/// Deletes given <paramref name="x"/> from hashqueue.
		/// Returns true if successful.
		/// </summary>
		public bool Delete(T x)
		{
			Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
			x.ThrowIfArgumentNull(nameof(x));
			if (!Has(x))
			{
				return false;
			}
			dq.Delete(DeleteFromMap(x));
			return true;
		}

		/// <summary>
		/// Deletes <paramref name="x"/> from <see cref="map"/> and
		/// returns the node.
		/// </summary>
		private Node<T> DeleteFromMap(T x)
		{
			var nodeq = map[x];
			foreach (var node in nodeq.Nodes())
			{
				var xnode = node.Value;
				if (EqualityComparer<T>.Default.Equals(xnode.Value, x))
				{
					nodeq.Delete(node);
					if (nodeq.IsEmpty())
					{
						map.Remove(x);
					}
					return node.Value;
				}
			}
			throw new InvalidOperationException(
				$"Node not found for item {x} in HashQueue.");
		}

		/// <summary>
		/// Peeks head of hashqueue.
		/// </summary>
		public T Peek()
		{
			Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
			return dq.Peek();
		}

		/// <summary>
		/// Peeks tail of hashqueue.
		/// </summary>
		public T PeekTail()
		{
			Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
			return dq.PeekTail();
		}

		/// <summary>
		/// Returns true if <paramref name="x"/> is in hashqueue.
		/// </summary>
		public bool Has(T x)
		{
			x.ThrowIfArgumentNull(nameof(x));
			return map.ContainsKey(x);
		}

		/// <summary>
		/// Returns true if hashqueue is empty.
		/// </summary>
		public bool IsEmpty()
		{
			return dq.IsEmpty();
		}

		/// <summary>
		/// Returns count of hashqueue.
		/// </summary>
		public int Count()
		{
			return dq.Count();
		}

		/// <summary>
		/// Returns long count of hashqueue.
		/// </summary>
		public long LongCount()
		{
			return dq.LongCount();
		}

		/// <summary>
		/// Clears hashqueue.
		/// </summary>
		public void Clear()
		{
			dq.Clear();
			map.Clear();
		}

		#endregion

		#region IEnumerable<T> methods

		public IEnumerator<T> GetEnumerator()
		{
			return dq.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
	}
}


<|start_filename|>tests/rm.ExtensionsTest/WrappedExtensionTest.cs<|end_filename|>
﻿using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class WrappedExtensionTest
	{
		[Test]
		public void WrapInt01()
		{
			var wi = 1.Wrap();
			var wiReference = wi;
			Assert.AreEqual(1, wi.Value);
			Assert.IsTrue(ReferenceEquals(wi, wiReference));
			wi.Value = 5;
			Assert.AreEqual(5, wi.Value);
			Assert.IsTrue(ReferenceEquals(wi, wiReference));
			Change(wi);
			Assert.AreEqual(9, wi.Value);
			Assert.IsTrue(ReferenceEquals(wi, wiReference));
		}

		private void Change(Wrapped<int> wi)
		{
			wi.Value = 9;
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/Sample/ComparableClass2.cs<|end_filename|>
﻿using System;

namespace rm.ExtensionsTest.Sample
{
	public class ComparableClass2 : IComparable<ComparableClass2>, IEquatable<ComparableClass2>
	{
		public int Value { get; set; }

		public int CompareTo(ComparableClass2 other)
		{
			if (other == null)
			{
				return 1;
			}
			return Value.CompareTo(other.Value);
		}

		public override string ToString()
		{
			return Value.ToString();
		}

		public bool Equals(ComparableClass2 other)
		{
			if (other == null)
			{
				return false;
			}
			return Value.Equals(other.Value);
		}
	}
}


<|start_filename|>src/rm.Extensions/Heap.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace rm.Extensions
{
	/// <summary>
	/// Defines heap methods.
	/// </summary>
	public interface IHeap<T, TKey> : IEnumerable<T>
		where TKey : IComparable<TKey>
	{
		/// <summary>
		/// Inserts <paramref name="x"/> into heap.
		/// </summary>
		void Insert(T x);

		/// <summary>
		/// Appends <paramref name="x"/> to heap without maintaining the heap property.
		/// </summary>
		void Append(T x);

		/// <summary>
		/// Deletes top of heap.
		/// </summary>
		T Delete();

		/// <summary>
		/// Captures the current top to return it and replace it with <paramref name="x"/> and sift down.
		/// </summary>
		/// <remarks>Displace() avoids having to call Delete() and Insert() separately.</remarks>
		T Displace(T x);

		/// <summary>
		/// Peeks top of heap.
		/// </summary>
		T Peek();

		/// <summary>
		/// Returns count of heap.
		/// </summary>
		int Count();

		/// <summary>
		/// Returns true if heap is empty.
		/// </summary>
		bool IsEmpty();

		/// <summary>
		/// Returns true if heap is full.
		/// </summary>
		bool IsFull();
	}

	/// <summary>
	/// Defines heap methods.
	/// </summary>
	public interface IHeap<T> : IEnumerable<T>
		where T : IComparable<T>
	{
		/// <summary>
		/// Inserts <paramref name="x"/> into heap.
		/// </summary>
		void Insert(T x);

		/// <summary>
		/// Appends <paramref name="x"/> to heap without maintaining the heap property.
		/// </summary>
		void Append(T x);

		/// <summary>
		/// Deletes top of heap.
		/// </summary>
		T Delete();

		/// <summary>
		/// Captures the current top to return it and replace it with <paramref name="x"/> and sift down.
		/// </summary>
		/// <remarks>Displace() avoids having to call Delete() and Insert() separately.</remarks>
		T Displace(T x);

		/// <summary>
		/// Peeks top of heap.
		/// </summary>
		T Peek();

		/// <summary>
		/// Returns count of heap.
		/// </summary>
		int Count();

		/// <summary>
		/// Returns true if heap is empty.
		/// </summary>
		bool IsEmpty();

		/// <summary>
		/// Returns true if heap is full.
		/// </summary>
		bool IsFull();
	}

	/// <summary>
	/// Heap helper class.
	/// </summary>
	/// <remarks>Uses array as backing store.</remarks>
	internal class HeapHelper<T> : IEnumerable<T>
	{
		#region members

		protected int capacity;
		protected T[] heap;
		protected int count;
		private IHeapPropertyComparer<T> heapPropertyComparer;
		protected bool isHeapified = true;

		#endregion

		#region ctors

		/// <summary>
		/// HeapHelper ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="heapPropertyComparer">Heap property comparer for T.</param>
		internal protected HeapHelper(int capacity,
			IHeapPropertyComparer<T> heapPropertyComparer)
		{
			capacity.ThrowIfArgumentOutOfRange(nameof(capacity));
			heapPropertyComparer.ThrowIfArgumentNull(nameof(heapPropertyComparer));
			this.capacity = capacity;
			this.heapPropertyComparer = heapPropertyComparer;
			this.heap = new T[capacity];
		}

		#endregion

		#region methods

		#region IHeap<T> methods

		/// <summary>
		/// Inserts <paramref name="x"/> into heap.
		/// </summary>
		public void Insert(T x)
		{
			if (IsFull())
			{
				throw new InvalidOperationException("Heap is full.");
			}
			if (!isHeapified)
			{
				Heapify();
			}
			heap[count] = x;
			count++;
			SiftUp(count - 1);
		}

		/// <summary>
		/// Appends <paramref name="x"/> to heap without maintaining the heap property.
		/// </summary>
		public void Append(T x)
		{
			if (IsFull())
			{
				throw new InvalidOperationException("Heap is full.");
			}
			isHeapified = false;
			heap[count] = x;
			count++;
		}

		/// <summary>
		/// Deletes top of heap.
		/// </summary>
		public T Delete()
		{
			if (IsEmpty())
			{
				throw new InvalidOperationException("Heap is empty.");
			}
			if (!isHeapified)
			{
				Heapify();
			}
			T x = heap[0];
			Helper.Swap(heap, 0, count - 1);
			count--;
			SiftDown(0);
			return x;
		}

		/// <summary>
		/// Captures the current top to return it and replace it with <paramref name="x"/> and sift down.
		/// </summary>
		/// <remarks>Displace() avoids having to call Delete() and Insert() separately.</remarks>
		public T Displace(T x)
		{
			x.ThrowIfArgumentNull(nameof(x));
			if (IsEmpty())
			{
				throw new InvalidOperationException("Heap is empty.");
			}
			if (!isHeapified)
			{
				Heapify();
			}
			T top = heap[0];
			heap[0] = x;
			SiftDown(0);
			return top;
		}

		/// <summary>
		/// Peeks top of heap.
		/// </summary>
		public T Peek()
		{
			if (IsEmpty())
			{
				throw new InvalidOperationException("Heap is empty.");
			}
			if (!isHeapified)
			{
				Heapify();
			}
			return heap[0];
		}

		/// <summary>
		/// Returns count of heap.
		/// </summary>
		public int Count()
		{
			return count;
		}

		/// <summary>
		/// Returns true if heap is empty.
		/// </summary>
		public bool IsEmpty()
		{
			return count == 0;
		}

		/// <summary>
		/// Returns true if heap is full.
		/// </summary>
		public bool IsFull()
		{
			return count == capacity;
		}

		#endregion

		#region IEnumerable<T> methods

		public IEnumerator<T> GetEnumerator()
		{
			if (!isHeapified)
			{
				Heapify();
			}
			for (int i = 0; i < count; i++)
			{
				yield return heap[i];
			}
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		#region private methods

		/// <summary>
		/// Sifts down from start to end of heap.
		/// </summary>
		private void SiftDown(int start)
		{
			var i = start;
			while (i < count)
			{
				var left = i * 2 + 1;
				var right = left + 1;
				var m = i;
				if (left < count && !IsInHeapProperty(left, m))
				{
					m = left;
				}
				if (right < count && !IsInHeapProperty(right, m))
				{
					m = right;
				}
				if (i == m)
				{
					break;
				}
				Helper.Swap(heap, i, m);
				i = m;
			}
		}

		/// <summary>
		/// Sifts up from end to start of heap.
		/// </summary>
		private void SiftUp(int end)
		{
			var i = end;
			while (i > 0)
			{
				var parent = (i - 1) / 2;
				if (IsInHeapProperty(i, parent))
				{
					break;
				}
				Helper.Swap(heap, i, parent);
				i = parent;
			}
		}

		/// <summary>
		/// Heapifies using sift up.
		/// </summary>
		/// <remarks>O(nlogn) time.</remarks>
		private void HeapifySiftUp()
		{
			var start = 0;
			while (start < count)
			{
				SiftUp(start);
				start++;
			}
		}

		/// <summary>
		/// Heapifies using sift down.
		/// </summary>
		/// <remarks>O(n) time.</remarks>
		private void HeapifySiftDown()
		{
			var start = count / 2 - 1; // last parent
			while (start >= 0)
			{
				SiftDown(start);
				start--;
			}
		}

		/// <summary>
		/// Heapifies.
		/// </summary>
		private void Heapify()
		{
			// sift down is faster than using sift up
			HeapifySiftDown();
			isHeapified = true;
		}

		/// <summary>
		/// Returns true if child and parent are in heap property.
		/// </summary>
		private bool IsInHeapProperty(int item, int parent)
		{
			// pass through
			return heapPropertyComparer.IsInHeapProperty(heap[item], heap[parent]);
		}

		#endregion

		#endregion
	}

	#region HeapPropertyComparer classes

	/// <summary>
	/// Defines heap property methods.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public interface IHeapPropertyComparer<T>
	{
		/// <summary>
		/// Returns true if <paramref name="item"/> and <paramref name="parent"/>
		/// are in heap property.
		/// </summary>
		bool IsInHeapProperty(T item, T parent);
	}

	public class MinHeapPropertyComparer<T, TKey> : IHeapPropertyComparer<T>
		where TKey : IComparable<TKey>
	{
		private Func<T, TKey> keySelector;
		private IComparer<TKey> comparer;

		public MinHeapPropertyComparer(Func<T, TKey> keySelector, IComparer<TKey> comparer)
		{
			keySelector.ThrowIfArgumentNull(nameof(keySelector));
			comparer.ThrowIfArgumentNull(nameof(comparer));
			this.keySelector = keySelector;
			this.comparer = comparer;
		}

		#region IHeapPropertyComparer<T> methods

		public bool IsInHeapProperty(T item, T parent)
		{
			// parent <= child
			return comparer.Compare(keySelector(parent), keySelector(item)) <= 0;
		}

		#endregion
	}

	public class MaxHeapPropertyComparer<T, TKey> : IHeapPropertyComparer<T>
		where TKey : IComparable<TKey>
	{
		private Func<T, TKey> keySelector;
		private IComparer<TKey> comparer;

		public MaxHeapPropertyComparer(Func<T, TKey> keySelector, IComparer<TKey> comparer)
		{
			keySelector.ThrowIfArgumentNull(nameof(keySelector));
			comparer.ThrowIfArgumentNull(nameof(comparer));
			this.keySelector = keySelector;
			this.comparer = comparer;
		}

		#region IHeapPropertyComparer<T> methods

		public bool IsInHeapProperty(T item, T parent)
		{
			// parent >= child
			return comparer.Compare(keySelector(parent), keySelector(item)) >= 0;
		}

		#endregion
	}

	public class MinHeapPropertyComparer<T> : MinHeapPropertyComparer<T, T>
		where T : IComparable<T>
	{
		public MinHeapPropertyComparer(IComparer<T> comparer)
			: base(x => x, comparer)
		{ }
	}

	public class MaxHeapPropertyComparer<T> : MaxHeapPropertyComparer<T, T>
		where T : IComparable<T>
	{
		public MaxHeapPropertyComparer(IComparer<T> comparer)
			: base(x => x, comparer)
		{ }
	}

	#endregion

	/// <summary>
	/// HeapBase class.
	/// </summary>
	public class HeapBase<T, TKey> : IHeap<T, TKey>
		where TKey : IComparable<TKey>
	{
		private HeapHelper<T> heapHelper;

		/// <summary>
		/// HeapBase.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="heapPropertyComparer">Heap property comparer for T.</param>
		public HeapBase(int capacity, IHeapPropertyComparer<T> heapPropertyComparer)
		{
			heapHelper = new HeapHelper<T>(capacity, heapPropertyComparer);
		}

		#region IHeap<T, TKey> methods

		public void Append(T x)
		{
			heapHelper.Append(x);
		}

		public int Count()
		{
			return heapHelper.Count();
		}

		public T Delete()
		{
			return heapHelper.Delete();
		}

		public T Displace(T x)
		{
			return heapHelper.Displace(x);
		}

		public void Insert(T x)
		{
			heapHelper.Insert(x);
		}

		public bool IsEmpty()
		{
			return heapHelper.IsEmpty();
		}

		public bool IsFull()
		{
			return heapHelper.IsFull();
		}

		public T Peek()
		{
			return heapHelper.Peek();
		}

		#endregion

		#region IEnumerable<T> methods

		public IEnumerator<T> GetEnumerator()
		{
			return heapHelper.GetEnumerator();
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
	}

	/// <summary>
	/// Min heap.
	/// </summary>
	public class MinHeap<T, TKey> : HeapBase<T, TKey>
		where TKey : IComparable<TKey>
	{
		#region ctors

		/// <summary>
		/// MinHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="keySelector">T's key to heapify against.</param>
		public MinHeap(int capacity, Func<T, TKey> keySelector)
			: this(capacity, keySelector, Comparer<TKey>.Default)
		{ }

		/// <summary>
		/// MinHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="keySelector">T's key to heapify against.</param>
		/// <param name="comparer">Comparer for T's key.</param>
		public MinHeap(int capacity, Func<T, TKey> keySelector, IComparer<TKey> comparer)
			: base(capacity, new MinHeapPropertyComparer<T, TKey>(keySelector, comparer))
		{ }

		#endregion
	}

	/// <summary>
	/// Max heap.
	/// </summary>
	public class MaxHeap<T, TKey> : HeapBase<T, TKey>
		where TKey : IComparable<TKey>
	{
		#region ctors

		/// <summary>
		/// MaxHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="keySelector">T's key to heapify against.</param>
		public MaxHeap(int capacity, Func<T, TKey> keySelector)
			: this(capacity, keySelector, Comparer<TKey>.Default)
		{ }

		/// <summary>
		/// MaxHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="keySelector">T's key to heapify against.</param>
		/// <param name="comparer">Comparer for T's key.</param>
		public MaxHeap(int capacity, Func<T, TKey> keySelector, IComparer<TKey> comparer)
			: base(capacity, new MaxHeapPropertyComparer<T, TKey>(keySelector, comparer))
		{ }

		#endregion
	}

	/// <summary>
	/// HeapBase class.
	/// </summary>
	public class HeapBase<T> : IHeap<T>
		where T : IComparable<T>
	{
		private HeapHelper<T> heapHelper;

		/// <summary>
		/// HeapBase.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="heapPropertyComparer">Heap property comparer for T.</param>
		public HeapBase(int capacity, IHeapPropertyComparer<T> heapPropertyComparer)
		{
			heapHelper = new HeapHelper<T>(capacity, heapPropertyComparer);
		}

		#region IHeap<T> methods

		public void Append(T x)
		{
			heapHelper.Append(x);
		}

		public int Count()
		{
			return heapHelper.Count();
		}

		public T Delete()
		{
			return heapHelper.Delete();
		}

		public T Displace(T x)
		{
			return heapHelper.Displace(x);
		}

		public void Insert(T x)
		{
			heapHelper.Insert(x);
		}

		public bool IsEmpty()
		{
			return heapHelper.IsEmpty();
		}

		public bool IsFull()
		{
			return heapHelper.IsFull();
		}

		public T Peek()
		{
			return heapHelper.Peek();
		}

		#endregion

		#region IEnumerable<T> methods

		public IEnumerator<T> GetEnumerator()
		{
			return heapHelper.GetEnumerator();
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
	}

	/// <summary>
	/// Min heap.
	/// </summary>
	public class MinHeap<T> : HeapBase<T>
		where T : IComparable<T>
	{
		#region ctors

		/// <summary>
		/// MinHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		public MinHeap(int capacity)
			: this(capacity, Comparer<T>.Default)
		{ }

		/// <summary>
		/// MinHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="comparer">Comparer for T.</param>
		public MinHeap(int capacity, IComparer<T> comparer)
			: base(capacity, new MinHeapPropertyComparer<T>(comparer))
		{ }

		#endregion
	}

	/// <summary>
	/// Max heap.
	/// </summary>
	public class MaxHeap<T> : HeapBase<T>
		where T : IComparable<T>
	{
		#region ctors

		/// <summary>
		/// MaxHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		public MaxHeap(int capacity)
			: this(capacity, Comparer<T>.Default)
		{ }

		/// <summary>
		/// MaxHeap ctor.
		/// </summary>
		/// <param name="capacity">Capacity of heap.</param>
		/// <param name="comparer">Comparer for T.</param>
		public MaxHeap(int capacity, IComparer<T> comparer)
			: base(capacity, new MaxHeapPropertyComparer<T>(comparer))
		{ }

		#endregion
	}
}


<|start_filename|>tests/rm.ExtensionsTest/Sample/ComparableClass.cs<|end_filename|>
﻿using System;

namespace rm.ExtensionsTest.Sample
{
	public class ComparableClass : IComparable<ComparableClass>
	{
		public int CompareTo(ComparableClass other)
		{
			throw new NotImplementedException();
		}
	}
}


<|start_filename|>src/rm.Extensions/Properties/AssemblyInfo.cs<|end_filename|>
﻿using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("rm.ExtensionsTest")]


<|start_filename|>src/rm.Extensions/StringBuilderExtension.cs<|end_filename|>
﻿using System.Text;

namespace rm.Extensions
{
	/// <summary>
	/// StringBuilder extensions.
	/// </summary>
	public static class StringBuilderExtension
	{
		/// <summary>
		/// Appends formatted args followed by newline.
		/// </summary>
		public static StringBuilder AppendLine(this StringBuilder sb, string format, params object[] args)
		{
			sb.ThrowIfArgumentNull(nameof(sb));
			sb.AppendLine(string.Format(format, args));
			return sb;
		}

		/// <summary>
		/// Reverses this instance in-place.
		/// </summary>
		/// <remarks>
		/// Does not work for string with surrogate pairs as,
		/// "Les Misérables"
		/// See https://stackoverflow.com/questions/228038/best-way-to-reverse-a-string
		/// </remarks>
		public static StringBuilder Reverse(this StringBuilder sb)
		{
			sb.ThrowIfArgumentNull(nameof(sb));
			var start = 0;
			var end = sb.Length - 1;
			while (start < end)
			{
				var temp = sb[start];
				sb[start] = sb[end];
				sb[end] = temp;
				start++;
				end--;
			}
			return sb;
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/HeapTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class HeapTest
	{
		class Person
		{
			public int Age { get; set; }
		}

		// tests Siftdown() internally.
		[Test]
		public void Append01()
		{
			var person1 = new Person { Age = 1 };
			var person2 = new Person { Age = 1 };
			var minheap = new MinHeap<Person, int>(2, x => x.Age);
			minheap.Append(person1);
			minheap.Append(person2);
			foreach (var x in minheap) { break; }
			Assert.AreEqual(person1, minheap.Peek());
			Assert.AreNotEqual(person2, minheap.Peek());
		}

		// tests SiftUp() internally.
		[Test]
		public void Insert01()
		{
			var person1 = new Person { Age = 1 };
			var person2 = new Person { Age = 1 };
			var maxheap = new MaxHeap<Person, int>(2, x => x.Age);
			maxheap.Insert(person1);
			maxheap.Insert(person2);
			Assert.AreEqual(person1, maxheap.Peek());
			Assert.AreNotEqual(person2, maxheap.Peek());
		}

		[Test]
		public void Insert02()
		{
			var minheap = new MinHeap<int>(3);
			Assert.AreEqual(0, minheap.Count());
			minheap.Insert(0);
			Assert.AreEqual(1, minheap.Count());
			minheap.Insert(0);
			Assert.AreEqual(2, minheap.Count());
			minheap.Insert(2);
			Assert.AreEqual(3, minheap.Count());

			var maxheap = new MaxHeap<int>(3);
			Assert.AreEqual(0, maxheap.Count());
			maxheap.Insert(0);
			Assert.AreEqual(1, maxheap.Count());
			maxheap.Insert(0);
			Assert.AreEqual(2, maxheap.Count());
			maxheap.Insert(1);
			Assert.AreEqual(3, maxheap.Count());
		}

		[Test]
		public void Delete01()
		{
			var minheap = new MinHeap<int>(2);
			Assert.AreEqual(0, minheap.Count());
			minheap.Insert(1);
			minheap.Insert(0);
			Assert.Throws<InvalidOperationException>(() => { minheap.Insert(-1); });
			Assert.AreEqual(0, minheap.Delete());
			Assert.AreEqual(1, minheap.Count());

			var maxheap = new MaxHeap<int>(2);
			Assert.AreEqual(0, maxheap.Count());
			maxheap.Insert(0);
			maxheap.Insert(1);
			Assert.Throws<InvalidOperationException>(() => { maxheap.Insert(-1); });
			Assert.AreEqual(1, maxheap.Delete());
			Assert.AreEqual(1, maxheap.Count());
		}

		[Test]
		public void Displace01()
		{
			var minheap = new MinHeap<int>(2);
			Assert.AreEqual(0, minheap.Count());
			minheap.Insert(5);
			minheap.Insert(4);
			Assert.AreEqual(4, minheap.Displace(6));
			Assert.AreEqual(2, minheap.Count());
			Assert.AreEqual(5, minheap.Peek());

			var maxheap = new MaxHeap<int>(2);
			Assert.AreEqual(0, maxheap.Count());
			maxheap.Insert(3);
			maxheap.Insert(4);
			Assert.AreEqual(4, maxheap.Displace(2));
			Assert.AreEqual(2, maxheap.Count());
			Assert.AreEqual(3, maxheap.Peek());
		}

		[Test]
		public void Peek01()
		{
			var minheap = new MinHeap<int>(2);
			Assert.Throws<InvalidOperationException>(() => { minheap.Peek(); });
			minheap.Insert(0);
			Assert.AreEqual(0, minheap.Peek());

			var maxheap = new MaxHeap<int>(2);
			Assert.Throws<InvalidOperationException>(() => { maxheap.Peek(); });
			maxheap.Insert(0);
			Assert.AreEqual(0, maxheap.Peek());
		}

		[Test]
		public void Ineritance01()
		{
			IHeap<int> minheap1 = new MinHeap<int>(2);
			IHeap<int> maxheap1 = new MaxHeap<int>(2);
			IHeap<int, int> minheap2 = new MinHeap<int, int>(2, x => x);
			IHeap<int, int> maxheap2 = new MaxHeap<int, int>(2, x => x);
			// these should not compile
			//IHeap<int> maxheap3 = new MaxHeap<int, int>(2, x => x);
			//IHeap<int> minheap3 = new MinHeap<int, int>(2, x => x);
			//IHeap<int, int> maxheap3 = new MaxHeap<int>(2);
			//IHeap<int, int> minheap3 = new MinHeap<int>(2);
		}
	}
}


<|start_filename|>src/rm.Extensions/UriExtension.cs<|end_filename|>
﻿using System;
using System.Net;
using System.Security.Cryptography;

namespace rm.Extensions
{
	/// <summary>
	/// Hash algorithm type.
	/// </summary>
	public enum Hasher
	{
		sha1 = 1,
		md5
	}

	/// <summary>
	/// Uri extensions.
	/// </summary>
	public static class UriExtension
	{
		/// <summary>
		/// Calculates checksum / hash (sha1 or md5) for uri (url or local file).
		/// <para>
		/// hash: sha1 20 bytes, md5 hash 16 bytes
		/// </para>
		/// </summary>
		/// <param name="uri">uri (url or local file).</param>
		/// <param name="type">Hasher.sha1 or Hasher.md5.</param>
		/// <returns>hash</returns>
		/// <remarks>http://hash.online-convert.com/</remarks>
		public static string Checksum(this Uri uri, Hasher type = Hasher.sha1)
		{
			uri.ThrowIfArgumentNull(nameof(uri));
			switch (type)
			{
				case Hasher.sha1:
					using (var hasher = new SHA1CryptoServiceProvider())
					{
						return Checksum(hasher, uri);
					}
				case Hasher.md5:
					using (var hasher = MD5.Create())
					{
						return Checksum(hasher, uri);
					}
				default:
					throw new ArgumentOutOfRangeException("Unknown hash algorithm type.");
			}
		}

		private static string Checksum(HashAlgorithm hasher, Uri uri)
		{
			// webclient works for uris and local files
			using (var webclient = new WebClient())
			{
				using (var stream = webclient.OpenRead(uri))
				{
					stream.ThrowIfNull(nameof(stream));
					var hash = BitConverter.ToString(hasher.ComputeHash(stream)).Replace("-", "").ToLower();
					return hash;
				}
			}
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/ArrayExtensionTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class ArrayExtensionTest
	{
		[Test]
		public void EmptyTest01()
		{
			var empty = Array<int>.Empty;
			var expected = Array.Empty<int>();
			Assert.AreSame(expected, empty);
			Assert.AreSame(Array<int>.Empty, Array<int>.Empty);
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/StopwatchExtensionTest.cs<|end_filename|>
﻿using System.Diagnostics;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class StopwatchExtensionTest
	{
		[Test]
		public void Elaspsed_Compile01()
		{
			// flaky tests as dependent on time so just put calls for compile safety.
			var sw = Stopwatch.StartNew();
			//Assert.AreEqual(sw.ElapsedTicks, sw.ElapsedTicks());
			sw.ElapsedTicks();
			//Assert.AreEqual(sw.ElapsedMilliseconds, sw.ElapsedMilliseconds());
			sw.ElapsedMilliseconds();
			//Assert.AreEqual(sw.ElapsedMilliseconds() / 1000, sw.ElapsedSeconds());
			sw.ElapsedSeconds();
		}
	}
}


<|start_filename|>src/rm.Extensions/ThrowExtension.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using Ex = rm.Extensions.ExceptionHelper;

namespace rm.Extensions
{
	/// <summary>
	/// Throw extensions.
	/// </summary>
	public static class ThrowExtension
	{
		/// <summary>
		/// Throws exception if <paramref name="t"/> is null.
		/// </summary>
		/// <param name="t">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <returns>Input.</returns>
		public static T ThrowIfNull<T>(this T t, string exMessage = "")
		{
			Ex.ThrowIfNull(t == null, exMessage);
			return t;
		}

		/// <summary>
		/// Throws exception if <paramref name="t"/> argument is null.
		/// </summary>
		/// <param name="t">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <returns>Input.</returns>
		public static T ThrowIfArgumentNull<T>(this T t, string exMessage = "")
		{
			Ex.ThrowIfArgumentNull(t == null, exMessage);
			return t;
		}

		/// <summary>
		/// Throws exception if any of the <paramref name="objects"/> is null.
		/// </summary>
		/// <param name="objects">Input.</param>
		/// <returns>Input.</returns>
		public static IEnumerable<T> ThrowIfAnyNull<T>(this IEnumerable<T> objects)
		{
			foreach (var o in objects)
			{
				o.ThrowIfNull();
			}
			return objects;
		}

		/// <summary>
		/// Throws exception if any of the <paramref name="objects"/> arguments is null.
		/// </summary>
		/// <param name="objects">Input.</param>
		/// <returns>Input.</returns>
		public static IEnumerable<T> ThrowIfAnyArgumentNull<T>(this IEnumerable<T> objects)
		{
			foreach (var o in objects)
			{
				o.ThrowIfArgumentNull();
			}
			return objects;
		}

		/// <summary>
		/// Throws exception if the string is null or empty.
		/// </summary>
		/// <param name="s">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <returns>Input.</returns>
		public static string ThrowIfNullOrEmpty(this string s, string exMessage = "")
		{
			Ex.ThrowIfNull(s == null, exMessage);
			Ex.ThrowIfEmpty(s.Length == 0, exMessage);
			return s;
		}

		/// <summary>
		/// Throws exception if the string argument is null or empty.
		/// </summary>
		/// <param name="s">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <returns>Input.</returns>
		public static string ThrowIfNullOrEmptyArgument(this string s, string exMessage = "")
		{
			Ex.ThrowIfArgumentNull(s == null, exMessage);
			Ex.ThrowIfEmpty(s.Length == 0, exMessage);
			return s;
		}

		/// <summary>
		/// Throws exception if any of the strings is null or empty.
		/// </summary>
		/// <param name="strings">Input.</param>
		/// <returns>Input.</returns>
		public static IEnumerable<string> ThrowIfNullOrEmpty(this IEnumerable<string> strings)
		{
			foreach (var s in strings)
			{
				s.ThrowIfNullOrEmpty();
			}
			return strings;
		}

		/// <summary>
		/// Throws exception if any of the string arguments is null or empty.
		/// </summary>
		/// <param name="strings">Input.</param>
		/// <returns>Input.</returns>
		public static IEnumerable<string> ThrowIfNullOrEmptyArgument(this IEnumerable<string> strings)
		{
			foreach (var s in strings)
			{
				s.ThrowIfNullOrEmptyArgument();
			}
			return strings;
		}

		/// <summary>
		/// Throws exception if the string is null or whitespace.
		/// </summary>
		/// <param name="s">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <returns>Input.</returns>
		public static string ThrowIfNullOrWhiteSpace(this string s, string exMessage = "")
		{
			Ex.ThrowIfNull(s == null, exMessage);
			Ex.ThrowIfEmpty(s.Trim().Length == 0, exMessage);
			return s;
		}

		/// <summary>
		/// Throws exception if the string argument is null or whitespace.
		/// </summary>
		/// <param name="s">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <returns>Input.</returns>
		public static string ThrowIfNullOrWhiteSpaceArgument(this string s, string exMessage = "")
		{
			Ex.ThrowIfArgumentNull(s == null, exMessage);
			Ex.ThrowIfEmpty(s.Trim().Length == 0, exMessage);
			return s;
		}

		/// <summary>
		/// Throws exception if <paramref name="x"/> is out of range.
		/// </summary>
		/// <param name="x">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <param name="minRange">Min range value.</param>
		/// <param name="maxRange">Max range value.</param>
		/// <returns>Input.</returns>
		public static int ThrowIfArgumentOutOfRange(this int x, string exMessage = "",
			int minRange = 0, int maxRange = int.MaxValue)
		{
			Ex.ThrowIfArgumentOutOfRange(x < minRange || x > maxRange,
				exMessage);
			return x;
		}

		/// <summary>
		/// Throws exception if <paramref name="x"/> is out of range (uint).
		/// </summary>
		/// <param name="x">Input.</param>
		/// <param name="exMessage">Exception message.</param>
		/// <param name="minRange">Min range value.</param>
		/// <param name="maxRange">Max range value.</param>
		/// <returns>Input.</returns>
		public static uint ThrowIfArgumentOutOfRange(this uint x, string exMessage = "",
			uint minRange = 0, uint maxRange = uint.MaxValue)
		{
			Ex.ThrowIfArgumentOutOfRange(x < minRange || x > maxRange,
				exMessage);
			return x;
		}
	}
}


<|start_filename|>src/rm.Extensions/DictionaryExtension.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace rm.Extensions
{
	/// <summary>
	/// Dictionary extensions.
	/// </summary>
	public static class DictionaryExtension
	{
		/// <summary>
		/// Returns value for key if exists or default{<typeparamref name="TValue"/>}.
		/// </summary>
		public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
			TKey key)
		{
			return dictionary.GetValueOrDefault(key, default(TValue));
		}

		/// <summary>
		/// Returns value for key if exists or <paramref name="defaultValue"/>.
		/// </summary>
		public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
			TKey key, TValue defaultValue)
		{
			TValue value;
			if (dictionary.TryGetValue(key, out value))
			{
				return value;
			}
			return defaultValue;
		}

		/// <summary>
		/// Returns the <paramref name="dictionary"/> as read-only.
		/// </summary>
		public static IDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
		{
			return new ReadOnlyDictionary<TKey, TValue>(dictionary);
		}
	}
}


<|start_filename|>src/rm.Extensions/StringExtension.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;

namespace rm.Extensions
{
	/// <summary>
	/// String extensions.
	/// </summary>
	public static class StringExtension
	{
		/// <summary>
		/// Returns true if string is null or empty.
		/// </summary>
		public static bool IsNullOrEmpty(this string s)
		{
			return string.IsNullOrEmpty(s);
		}

		/// <summary>
		/// Returns true if string is null, empty or only whitespaces.
		/// </summary>
		public static bool IsNullOrWhiteSpace(this string s)
		{
			return string.IsNullOrWhiteSpace(s);
		}

		/// <summary>
		/// Returns specified value if string is null/empty/whitespace else same string.
		/// </summary>
		public static string Or(this string s, string or)
		{
			if (!s.IsNullOrWhiteSpace())
			{
				return s;
			}
			return or;
		}

		/// <summary>
		/// Returns empty if string is null/empty/whitespace else same string.
		/// </summary>
		public static string OrEmpty(this string s)
		{
			return s.Or("");
		}

		/// <summary>
		/// Returns null if string is null/empty else same string.
		/// </summary>
		public static string NullIfEmpty(this string s)
		{
			return !s.IsNullOrEmpty() ? s : null;
		}

		/// <summary>
		/// Returns null if string is null/empty/whitespace else same string.
		/// </summary>
		public static string NullIfWhiteSpace(this string s)
		{
			return !s.IsNullOrWhiteSpace() ? s : null;
		}

		/// <summary>
		/// Returns html-encoded string.
		/// </summary>
		public static string HtmlEncode(this string s)
		{
			return WebUtility.HtmlEncode(s);
		}

		/// <summary>
		/// Returns html-decoded string.
		/// </summary>
		public static string HtmlDecode(this string s)
		{
			return WebUtility.HtmlDecode(s);
		}

		/// <summary>
		/// Returns url-encoded string.
		/// </summary>
		public static string UrlEncode(this string s)
		{
			return WebUtility.UrlEncode(s);
		}

		/// <summary>
		/// Returns url-decoded string.
		/// </summary>
		public static string UrlDecode(this string s)
		{
			return WebUtility.UrlDecode(s);
		}

		/// <summary>
		/// Formats string as string.Format() but the parameter index is optional and parameter meta is allowed.
		/// </summary>
		/// <example>"{} is a {1}".format("this", "test")</example>
		public static string format(this string format, params object[] args)
		{
			var buffer = new StringBuilder(format);
			var i = 0;
			var param = 0;
			var paramMetaToIndexMap = new Dictionary<string, string>();
			while (i < buffer.Length)
			{
				// close curly before open curly
				if (buffer[i] == '}')
				{
					// skip escaped curly "}}", else break
					if (i + 1 < buffer.Length && buffer[i + 1] == '}')
					{
						i += 2;
						continue;
					}
					else
					{
						break;
					}
				}
				// stop at '{'
				if (buffer[i] != '{')
				{
					i++;
					continue;
				}
				// skip escaped curly "{{"
				if (i + 1 < buffer.Length && buffer[i + 1] == '{')
				{
					i += 2;
					continue;
				}
				var start = i;
				while (i < buffer.Length && buffer[i] != '}')
				{
					i++;
				}
				// open curly is not matched, break
				if (i == buffer.Length)
				{
					break;
				}
				var end = i;
				//// at this point buffer[start..end] has format field
				//// insert parameter index only if not present
				////     so insert for:
				////     se  s  e  s            e  s    e  s      e  s                e
				////     {}  {:2}  {,10:#,##0.00}  {test}  {test:2}  {test,10:#,##0.00}
				////     and do not insert for:
				////     s e  s   e  s             e
				////     {0}  {0:2}  {0,10:#,##0.00}
				// find the meta substring
				var metastart = start + 1;
				var metaend = metastart;
				while (buffer[metaend] != '}' && buffer[metaend] != ':' && buffer[metaend] != ',')
				{
					metaend++;
				}
				var paramMeta = buffer.ToString(metastart, metaend - metastart).Trim();
				// insert param only if meta is not int
				int ignore;
				if (!int.TryParse(paramMeta, out ignore))
				{
					string paramIndex;
					// do not insert "" into meta->index map
					if (paramMeta == "")
					{
						paramIndex = param.ToString();
						param++;
					}
					else
					{
						// remove meta
						buffer.Remove(metastart, paramMeta.Length);
						// insert param index into meta->index map if not exists
						if (!paramMetaToIndexMap.ContainsKey(paramMeta))
						{
							paramMetaToIndexMap[paramMeta] = param.ToString();
							param++;
						}
						// do not increment param as param index is reused
						paramIndex = paramMetaToIndexMap[paramMeta];
					}
					// insert index
					buffer.Insert(metastart, paramIndex);
					// adjust end as buffer is removed from and inserted into
					end += -paramMeta.Length + paramIndex.Length;
				}
				else
				{
					param++;
				}
				i = end + 1; // i++ does not work
			}
			var formatConverted = buffer.ToString();
			return string.Format(formatConverted, args);
		}

		/// <summary>
		/// Try-parses string to bool, else default value.
		/// </summary>
		public static bool ToBool(this string value, bool defaultValue)
		{
			bool result;
			if (bool.TryParse(value, out result))
			{
				return result;
			}
			return defaultValue;
		}

		/// <summary>
		/// Munges substitutions.
		/// </summary>
		private static List<KeyValuePair<string, string>> mungeUnmungeSubstitutions =
			GetMungeUnmungeSubstitutions();
		private static List<KeyValuePair<string, string>> GetMungeUnmungeSubstitutions()
		{
			var mungeSubstitutions = new List<KeyValuePair<string, string>>();
			new[]
			{
				new[] {"a", "@"},
				new[] {"b", "8"},
				new[] {"c", "("},
				new[] {"d", "6"},
				new[] {"e", "3"},
				new[] {"f", "#"},
				new[] {"g", "9"},
				new[] {"h", "#"},
				new[] {"i", "1"},
				new[] {"i", "!"},
				new[] {"k", "<"},
				new[] {"l", "1"},
				new[] {"l", "i"},
				new[] {"o", "0"},
				new[] {"q", "9"},
				new[] {"s", "$"},
				new[] {"s", "5"},
				new[] {"t", "+"},
				new[] {"v", ">"},
				new[] {"v", "<"},
				new[] {"w", "uu"},
				new[] {"w", "2u"},
				new[] {"x", "%"},
				new[] {"y", "?"},
			}.ToList().ForEach(x => mungeSubstitutions.Add(new KeyValuePair<string, string>(x[0], x[1])));
			return mungeSubstitutions;
		}

		/// <summary>
		/// key->value[] map.
		/// </summary>
		private static IDictionary<string, string[]> mungeMap =
			mungeUnmungeSubstitutions
				.GroupBy(x => x.Key)
				.ToDictionary(g => g.Key, g => g.Select(x => x.Value)
				.ToArray());

		/// <summary>
		/// value->key[] map.
		/// </summary>
		private static IDictionary<string, string[]> unmungeMap =
			mungeUnmungeSubstitutions
				.GroupBy(x => x.Value)
				.ToDictionary(g => g.Key, g => g.Select(x => x.Key)
				.ToArray());

		/// <summary>
		/// Munges or unmunges password as per substitution map.
		/// </summary>
		private static IList<string> MungeUnmunge(this string password,
			IDictionary<string, string[]> muMap, bool isMunging)
		{
			password.ThrowIfArgumentNull(nameof(password));
			var items = new List<string>();
			MungeUnmunge
			(
				password, muMap, isMunging, items,
				0, new StringBuilder(password.Length), new Dictionary<string, string>()
			);
			return items;
		}

		/// <summary>
		/// Munges or unmunges recursively.
		/// </summary>
		/// <param name="password">Password to munge/unmunge.</param>
		/// <param name="muMap">Substitution map.</param>
		/// <param name="isMunging">True if munging, false if unmunging.</param>
		/// <param name="items">List to hold the passwords.</param>
		/// <param name="index">Index (start) of currently processed part.</param>
		/// <param name="buffer">Buffer to hold the munge/unmunged password.</param>
		/// <param name="partReplaceMap">Map to hold part replacements.</param>
		private static void MungeUnmunge(string password,
			IDictionary<string, string[]> muMap, bool isMunging, List<string> items,
			int index, StringBuilder buffer, Dictionary<string, string> partReplaceMap)
		{
			if (index == password.Length)
			{
				items.Add(buffer.ToString());
				return;
			}
			Func<string, int, int, string> substring = (pwd, idx, len) =>
			{
				if (idx + len > pwd.Length)
				{
					return null;
				}
				return pwd.Substring(idx, len);
			};
			// look ahead 1, 2 chars only
			for (int length = 1; length <= 2; length++)
			{
				var part = substring(password, index, length);
				foreach (var muSubstitute in
					GetMungeUnmungeParts(part, muMap, isMunging, partReplaceMap))
				{
					buffer.Insert(index, muSubstitute);
					partReplaceMap[part] = muSubstitute;
					MungeUnmunge(password, muMap, isMunging, items, index + length, buffer, partReplaceMap);
					buffer.Length -= muSubstitute.Length;
					partReplaceMap.Remove(part);
				}
			}
		}

		/// <summary>
		/// Returns the munged/unmunged parts for part.
		/// </summary>
		/// <example>munge("o"), returns {"o", "0"}.</example>
		/// <example>Unmunge("0"), returns {"o"}.</example>
		private static IEnumerable<string> GetMungeUnmungeParts(string part,
			IDictionary<string, string[]> muMap, bool isMunging,
			Dictionary<string, string> partReplaceMap)
		{
			if (part == null)
			{
				yield break;
			}
			// yield same replacement for part as before
			if (partReplaceMap.ContainsKey(part))
			{
				yield return partReplaceMap[part];
			}
			else if (part.Length == 1)
			{
				// yield same part only if munging and length 1
				if (isMunging)
				{
					yield return part;
				}
				// yield if in muMap
				string[] muParts;
				if (muMap.TryGetValue(part, out muParts))
				{
					foreach (var muPart in muParts)
					{
						yield return muPart;
					}
				}
				else
				{
					// yield only if not munging and not in muMap
					if (!isMunging)
					{
						yield return part;
					}
				}
			}
			else if (part.Length == 2)
			{
				// yield only if in muMap
				string[] muParts;
				if (muMap.TryGetValue(part, out muParts))
				{
					foreach (var muPart in muParts)
					{
						yield return muPart;
					}
				}
			}
		}

		/// <summary>
		/// Munges a password (up to two chars).
		/// </summary>
		/// <remarks>http://en.wikipedia.org/wiki/Munged_password</remarks>
		public static IEnumerable<string> Munge(this string password)
		{
			var items = MungeUnmunge(password, mungeMap, true);
			// 1st item is same as password
			if (items.Any() && items[0] == password)
			{
				items.RemoveAt(0);
			}
			return items.AsEnumerable();
		}

		/// <summary>
		/// Unmunges a (munged) password (up to two chars).
		/// </summary>
		/// <remarks>http://en.wikipedia.org/wiki/Munged_password</remarks>
		public static IEnumerable<string> Unmunge(this string password)
		{
			var items = MungeUnmunge(password, unmungeMap, false);
			// remove 1st item if same as password
			if (items.Any() && items[0] == password)
			{
				items.RemoveAt(0);
			}
			return items.AsEnumerable();
		}

		/// <summary>
		/// Returns a new collection with characters scrabbled like the game.
		/// </summary>
		public static IEnumerable<string> Scrabble(this string word)
		{
			word.ThrowIfArgumentNull(nameof(word));
			return word.Scrabble<char>().Select(x => new string(x));
		}

		/// <summary>
		/// Returns a new collection with characters scrabbled like the game for particular r.
		/// </summary>
		public static IEnumerable<string> Scrabble(this string word, int limit)
		{
			word.ThrowIfArgumentNull(nameof(word));
			limit.ThrowIfArgumentOutOfRange(nameof(limit), maxRange: word.Length);
			return word.Scrabble<char>(limit).Select(x => new string(x));
		}

		/// <summary>
		/// Parses a string in UTC format as DateTime.
		/// </summary>
		public static DateTime ParseAsUtc(this string s)
		{
			return DateTimeOffset.Parse(s).UtcDateTime;
		}

		/// <summary>
		/// Converts a string to title case.
		/// </summary>
		/// <example>"war and peace" -> "War And Peace"</example>
		public static string ToTitleCase(this string s)
		{
			return Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(s);
		}

		/// <summary>
		/// Gets permutations of string for particular r.
		/// </summary>
		/// <remarks>nPr permutations</remarks>
		public static IEnumerable<string> Permutation(this string s, int r)
		{
			return s.Permutation<char>(r).Select(x => new string(x));
		}

		/// <summary>
		/// Gets permutations of string.
		/// </summary>
		/// <remarks>nPn permutations</remarks>
		public static IEnumerable<string> Permutation(this string s)
		{
			return s.Permutation(s.Length);
		}

		/// <summary>
		/// Gets combinations of string for particular r.
		/// </summary>
		/// <remarks>nCr combinations</remarks>
		public static IEnumerable<string> Combination(this string s, int r)
		{
			return s.Combination<char>(r).Select(x => new string(x));
		}

		/// <summary>
		/// Gets combinations of string.
		/// </summary>
		/// <remarks>nCn combinations</remarks>
		public static IEnumerable<string> Combination(this string s)
		{
			return s.Combination(s.Length);
		}

		/// <summary>
		/// Splits csv string using common delimiters.
		/// </summary>
		public static IEnumerable<string> SplitCsv(this string s)
		{
			return s.Split(new[] { ",", ";", "|" }, StringSplitOptions.RemoveEmptyEntries);
		}

		/// <summary>
		/// Returns substring till end of length <paramref name="n"/>.
		/// </summary>
		public static string SubstringTillEnd(this string source, int n)
		{
			source.ThrowIfArgumentNull(nameof(source));
			n.ThrowIfArgumentOutOfRange(nameof(n));
			if (n >= source.Length)
			{
				return source;
			}
			return source.Substring(source.Length - n);
		}

		/// <summary>
		/// Returns substring by specifying start index and end index.
		/// </summary>
		public static string SubstringByIndex(this string source, int startIndex, int endIndex)
		{
			source.ThrowIfArgumentNull(nameof(source));
			startIndex.ThrowIfArgumentOutOfRange(nameof(startIndex));
			endIndex.ThrowIfArgumentOutOfRange(nameof(endIndex), maxRange: source.Length);
			return source.Substring(startIndex, endIndex - startIndex);
		}
	}
}


<|start_filename|>src/rm.Extensions/TimeSpanExtension.cs<|end_filename|>
﻿using System;

namespace rm.Extensions
{
	/// <summary>
	/// TimeSpan extensions.
	/// </summary>
	public static class TimespanExtension
	{
		/// <summary>
		/// Rounds timespan.
		/// <para>
		/// Ex: ms, s, h, d, wk, mth, y.
		/// </para>
		/// </summary>
		public static string Round(this TimeSpan ts)
		{
			if (ts.Days >= 365)
			{
				return "{0}y".format(ts.Days / 365);
			}
			if (ts.Days >= 30)
			{
				return "{0}mth".format(ts.Days / 30);
			}
			if (ts.Days >= 7)
			{
				return "{0}wk".format(ts.Days / 7);
			}
			if (ts.Days > 0)
			{
				return "{0}d".format(ts.Days);
			}
			if (ts.Hours > 0)
			{
				return "{0}h".format(ts.Hours);
			}
			if (ts.Minutes > 0)
			{
				return "{0}m".format(ts.Minutes);
			}
			if (ts.Seconds > 0)
			{
				return "{0}s".format(ts.Seconds);
			}
			return "{0}ms".format(ts.Milliseconds);
		}

		/// <summary>
		/// Gets timespan with <paramref name="n"/> days.
		/// </summary>
		public static TimeSpan Days(this int n)
		{
			return TimeSpan.FromDays(n);
		}

		/// <summary>
		/// Gets timespan with <paramref name="n"/> hours.
		/// </summary>
		public static TimeSpan Hours(this int n)
		{
			return TimeSpan.FromHours(n);
		}

		/// <summary>
		/// Gets timespan with <paramref name="n"/> minutes.
		/// </summary>
		public static TimeSpan Minutes(this int n)
		{
			return TimeSpan.FromMinutes(n);
		}

		/// <summary>
		/// Gets timespan with <paramref name="n"/> seconds.
		/// </summary>
		public static TimeSpan Seconds(this int n)
		{
			return TimeSpan.FromSeconds(n);
		}

		/// <summary>
		/// Gets timespan with <paramref name="n"/> milliseconds.
		/// </summary>
		public static TimeSpan Milliseconds(this int n)
		{
			return TimeSpan.FromMilliseconds(n);
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/ListExtensionTest.cs<|end_filename|>
﻿using System;
using System.Linq;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class ListExtensionTest
	{
		[Test]
		public void RemoveLast01()
		{
			var list = new int[] { 1, 2 }.ToList();
			list.RemoveLast();
			Assert.AreEqual(1, list.Count);
			list.RemoveLast();
			Assert.AreEqual(0, list.Count);
			Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveLast());
		}

		[Test]
		public void RemoveLast02()
		{
			var list = new int[] { 1, 2 }.ToList();
			list.RemoveLast(2);
			Assert.AreEqual(0, list.Count);
			Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveLast());
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/EnumExtensionTest.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using rm.Extensions;
using Sc = System.ComponentModel;

namespace rm.ExtensionsTest
{
	public enum Temperature
	{
		C = 1,
		F
	}

	public enum Color
	{
		[Sc.Description("Red color")]
		Red = 1,
		Green,
		[Sc.Description("Blue color")]
		Blue
	}

	public enum Grade
	{
		Toddler = 1,
		[Sc.Description("Pre-K")]
		PreK,
		Kindergarten,
		[Sc.Description("1")]
		One,
		[Sc.Description("2")]
		Two,
		[Sc.Description("3")]
		Three,
		[Sc.Description("4")]
		Four,
		[Sc.Description("5")]
		Five,
		[Sc.Description("6")]
		Six,
		[Sc.Description("7")]
		Seven,
		[Sc.Description("8")]
		Eight,
		[Sc.Description("9")]
		Nine,
		[Sc.Description("10")]
		Ten,
		[Sc.Description("11")]
		Eleven,
		[Sc.Description("12")]
		Twelve,
		College
	}

	public enum EmptyEnum { }

	[TestFixture]
	public class EnumExtensionTest
	{
		[Test]
		public void Parse01()
		{
			Assert.AreEqual(Color.Red, "Red".Parse<Color>());
		}

		[Test]
		public void Parse02()
		{
			Assert.Throws<ArgumentException>(() => { "Red".Parse<Temperature>(); });
		}

		[Test]
		public void TryParse01()
		{
			Color color;
			Assert.IsTrue("Red".TryParse<Color>(out color));
			Assert.AreEqual(Color.Red, color);
		}

		[Test]
		public void TryParse02()
		{
			Temperature t;
			Assert.IsFalse("Red".TryParse<Temperature>(out t));
			Assert.AreNotEqual(Color.Red, t);
			Assert.AreEqual(0, (int)t);
		}

		[Test]
		public void GetDescription01()
		{
			Assert.AreEqual("Red color", Color.Red.GetDescription());
			Assert.AreEqual(Color.Green.ToString(), Color.Green.GetDescription());
		}

		[Test]
		public void UnsupportedEnumValueException01()
		{
			var enumValue = (Color)0;
			var TEnum = enumValue.GetType();
			var ex = Assert.Throws<UnsupportedEnumValueException<Color>>(() =>
			{
				enumValue.GetEnumName();
			});
			Assert.AreEqual($"Value {enumValue} of enum {TEnum.Name} is not supported.", ex.Message);
		}

		[Test]
		public void GetDescription02()
		{
			Assert.Throws<UnsupportedEnumValueException<Color>>(() =>
			{
				((Color)0).GetDescription();
			});
		}

		[Test]
		public void GetEnumValue01()
		{
			Assert.AreEqual(Color.Red, "Red".GetEnumValue<Color>());
			Assert.Throws<UnsupportedEnumValueException<Color>>(() =>
			{
				"UnsupportedEnumValue".GetEnumValue<Color>();
			});
		}

		[Test]
		public void GetEnumValues01()
		{
			var colors = EnumExtension.GetEnumValues<Color>();
			Assert.AreEqual(3, colors.Count());
			Assert.IsTrue(colors.Contains(Color.Red));
			Assert.IsTrue(colors.Contains(Color.Green));
			Assert.IsTrue(colors.Contains(Color.Blue));
		}

		[Test(Description = "GetEnumValue() v/s Enum.ToString() speed test.")]
		[Category("slow")]
		public void GetEnumValue02()
		{
			Action<Func<bool>, string> speedTest = (testPredicate, testName) =>
			{
				var iterations = 1000000;
				DateTime datetime;
				TimeSpan timespan;
				datetime = DateTime.Now;
				foreach (var item in Enumerable.Range(0, iterations))
				{
					if (testPredicate())
					{ }
				}
				timespan = DateTime.Now - datetime;
				Console.WriteLine("{0}: \t{1}", testName, timespan.TotalSeconds);
			};
			// 15x slow
			speedTest(() => { return Color.Red.ToString() == "Red"; }, "Enum.ToString()");
			// 10x slow
			speedTest(() => { return "Red".Parse<Color>() == Color.Red; }, "Enum.Parse()");
			// 1.5x slow
			speedTest(() => { return "Red".GetEnumValue<Color>() == Color.Red; }, "GetEnumValue()");
			// fastest
			speedTest(() => { return Color.Red.GetEnumName() == "Red"; }, "GetEnumName()");
		}

		[Test]
		public void GetEnumNames01()
		{
			var colors = EnumExtension.GetEnumNames<Color>();
			Assert.AreEqual(3, colors.Count());
			Assert.IsTrue(colors.Contains(Color.Red.ToString()));
			Assert.IsTrue(colors.Contains(Color.Green.ToString()));
			Assert.IsTrue(colors.Contains(Color.Blue.ToString()));
		}

		[Test]
		public void GetEnumNameToDescriptionMap01()
		{
			var colorsMap = EnumExtension.GetEnumNameToDescriptionMap<Color>();
			Assert.AreEqual(3, colorsMap.Count());
			Assert.IsTrue(colorsMap.Contains(
				new KeyValuePair<string, string>(Color.Red.ToString(), Color.Red.GetDescription())
				));
			Assert.IsTrue(colorsMap.Contains(
				new KeyValuePair<string, string>(Color.Green.ToString(), Color.Green.GetDescription())
				));
			Assert.IsTrue(colorsMap.Contains(
				new KeyValuePair<string, string>(Color.Blue.ToString(), Color.Blue.GetDescription())
				));
		}

		[Test]
		public void GetEnumNameFromDescription01()
		{
			Assert.AreEqual(Color.Red.ToString(), "Red color".GetEnumNameFromDescription<Color>());
			Assert.Throws<UnsupportedEnumValueException<Color>>(() =>
			{
				"UnsupportedEnumValue".GetEnumNameFromDescription<Color>();
			});
		}

		[Test]
		public void Sorting01()
		{
			var gradesUnsorted = new[] { "Pre-K", "1", "College", "2", "Toddler" };
			var grades = gradesUnsorted
				.Select(x => x.GetEnumValueFromDescription<Grade>()).ToArray();
			Array.Sort(grades);
			Console.WriteLine("orig: {0}", string.Join(", ", gradesUnsorted));
			var gradesSorted = grades.Select(x => x.GetDescription());
			var gradesSortedFlat = string.Join(", ", gradesSorted);
			Assert.AreEqual("Toddler, Pre-K, 1, 2, College", gradesSortedFlat);
			Console.WriteLine("sort: {0}", gradesSortedFlat);
		}

		[Test]
		public void IsDefined01()
		{
			Assert.IsFalse(0.IsDefined<Color>());
			Assert.IsFalse(100.IsDefined<Color>());
			Assert.IsTrue(((int)Color.Red).IsDefined<Color>());
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/LruCacheTest.cs<|end_filename|>
﻿using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class LruCacheTest
	{
		private const int capacity = 5;
		ILruCache<int, int> cache;

		[SetUp]
		public void Setup()
		{
			cache = new LruCache<int, int>(capacity);
		}

		[Test]
		public void Get_01()
		{
			cache.Insert(-1, -1);
			for (int i = 0; i < capacity; i++)
			{
				cache.Insert(i, i);
			}
			Assert.IsFalse(cache.HasKey(-1));
		}

		[Test]
		public void Get_02()
		{
			Assert.AreEqual(default(int), cache.Get(0));
		}

		[Test]
		public void Get_03()
		{
			Assert.DoesNotThrow(() => cache.Get(0));
		}

		[Test]
		public void Insert_01()
		{
			for (int i = 0; i < capacity; i++)
			{
				cache.Insert(i, i);
				Assert.AreEqual(i + 1, cache.Count());
			}
		}

		[Test]
		public void Lru_01()
		{
			int key = 0;
			for (int i = 0; i < capacity; i++, key++)
			{
				cache.Insert(key, key);
			}
			for (int i = 0; i < capacity; i++, key++)
			{
				cache.Insert(key, key);
			}
			for (int i = 0; i < capacity; i++)
			{
				Assert.IsFalse(cache.HasKey(i));
			}
			for (int i = 0; i < capacity; i++)
			{
				Assert.IsTrue(cache.HasKey(capacity + i));
			}
		}

		[Test]
		public void Lru_02()
		{
			int key = 0;
			for (int i = 0; i < capacity; i++, key++)
			{
				cache.Insert(key, key);
			}
			cache.Get(2);
			for (int i = 0; i < capacity - 1; i++, key++)
			{
				cache.Insert(key, key);
			}
			Assert.IsTrue(cache.HasKey(2));
			cache.Insert(key, key);
			Assert.IsFalse(cache.HasKey(2));
		}

		[Test]
		public void Remove_01()
		{
			for (int i = 0; i < capacity; i++)
			{
				cache.Insert(i, i);
			}
			for (int i = 0; i < capacity; i++)
			{
				cache.Remove(i);
				Assert.AreEqual(capacity - i - 1, cache.Count());
			}
		}

		[Test]
		public void HasKey_01()
		{
			for (int i = 0; i < capacity; i++)
			{
				cache.Insert(i, i);
			}
			cache.Remove(0);
			Assert.IsFalse(cache.HasKey(0));
		}

		[Test]
		public void IsEmpty_01()
		{
			Assert.IsTrue(cache.IsEmpty());
			cache.Insert(0, 0);
			Assert.IsFalse(cache.IsEmpty());
		}

		[Test]
		public void IsFull_01()
		{
			for (int i = 0; i < capacity; i++)
			{
				Assert.IsFalse(cache.IsFull());
				cache.Insert(i, i);
			}
			Assert.IsTrue(cache.IsFull());
		}

		[Test]
		public void IsFull_02()
		{
			for (int i = 0; i < capacity; i++)
			{
				cache.Insert(i, i);
			}
			Assert.IsTrue(cache.IsFull());
			cache.Remove(0);
			Assert.IsFalse(cache.IsFull());
		}

		[Test]
		public void Capacity_01()
		{
			Assert.AreEqual(capacity, cache.Capacity());
		}

		[Test]
		public void Count_01()
		{
			Assert.AreEqual(0, cache.Count());
			cache.Insert(0, 0);
			Assert.AreEqual(1, cache.Count());
		}

		[Test]
		public void Count_02()
		{
			for (int i = 0; i < capacity; i++)
			{
				cache.Insert(i, i);
			}
			Assert.AreEqual(capacity, cache.Count());
			cache.Insert(10, 10);
			Assert.AreEqual(capacity, cache.Count());
		}

		[Test]
		public void Clear_01()
		{
			for (int i = 0; i < capacity; i++)
			{
				cache.Insert(i, i);
			}
			cache.Clear();
			Assert.IsTrue(cache.IsEmpty());
		}

		[Test]
		public void Ctor_01()
		{
			Assert.DoesNotThrow(() =>
			{
				cache = new LruCache<int, int>(0);
				cache.Insert(0, 0);
				Assert.IsFalse(cache.HasKey(0));
			});
		}
	}
}


<|start_filename|>src/rm.Extensions/ExceptionHelper.cs<|end_filename|>
﻿using System;

namespace rm.Extensions
{
	/// <summary>
	/// Exception helper class.
	/// </summary>
	internal class ExceptionHelper
	{
		/// <summary>
		/// Throws NullReferenceException if true with message.
		/// </summary>
		internal static void ThrowIfNull(bool throwEx, string exMessage)
		{
			if (throwEx)
			{
				throw new NullReferenceException(exMessage);
			}
		}

		/// <summary>
		/// Throws ArgumentNullException if true with message.
		/// </summary>
		internal static void ThrowIfArgumentNull(bool throwEx, string exMessage)
		{
			if (throwEx)
			{
				throw new ArgumentNullException(exMessage);
			}
		}

		/// <summary>
		/// Throws EmptyException if true with message.
		/// </summary>
		internal static void ThrowIfEmpty(bool throwEx, string exMessage)
		{
			if (throwEx)
			{
				throw new EmptyException(exMessage);
			}
		}

		/// <summary>
		/// Throws ArgumentOutOfRangeException if true with message.
		/// </summary>
		internal static void ThrowIfArgumentOutOfRange(bool throwEx, string exMessage)
		{
			if (throwEx)
			{
				throw new ArgumentOutOfRangeException(exMessage);
			}
		}
	}
}


<|start_filename|>src/rm.Extensions/ListExtension.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace rm.Extensions
{
	/// <summary>
	/// List extensions.
	/// </summary>
	public static class ListExtension
	{
		/// <summary>
		/// Removes the last <paramref name="n"/> item(s) in the list.
		/// </summary>
		public static void RemoveLast<T>(this IList<T> source, int n = 1)
		{
			for (int i = 0; i < n; i++)
			{
				source.RemoveAt(source.Count - 1);
			}
		}
	}
}


<|start_filename|>src/rm.Extensions/StopwatchExtension.cs<|end_filename|>
﻿using System.Diagnostics;

namespace rm.Extensions
{
	/// <summary>
	/// Stopwatch extensions.
	/// </summary>
	public static class StopwatchExtension
	{
		public static long ElapsedTicks(this Stopwatch sw)
		{
			return sw.ElapsedTicks;
		}
		public static long ElapsedMilliseconds(this Stopwatch sw)
		{
			return sw.ElapsedMilliseconds;
		}
		public static long ElapsedSeconds(this Stopwatch sw)
		{
			return sw.ElapsedMilliseconds / 1000;
		}
	}
}


<|start_filename|>src/rm.Extensions/ArrayExtension.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Text;

namespace rm.Extensions
{
	/// <summary>
	/// Array{T} extensions.
	/// </summary>
	public static class Array<T>
	{
		/// <summary>
		/// Returns empty T array.
		/// </summary>
		public static T[] Empty => Array.Empty<T>();
	}
}


<|start_filename|>tests/rm.ExtensionsTest/Sample/SampleClass.cs<|end_filename|>
﻿namespace rm.ExtensionsTest.Sample
{
	public class SampleClass
	{
		public ComparableClass2 ComparableClass2 { get; set; }
	}
}


<|start_filename|>tests/rm.ExtensionsTest/CircularStackTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class CircularStackTest
	{
		[Test]
		public void Push01()
		{
			var capacity = 4;
			var cq = new CircularStack<int>(capacity);
			for (int i = 0; i < 6; i++)
			{ cq.Push(i); }
			Assert.AreEqual(capacity, cq.Count());
		}

		[Test]
		public void Pop01()
		{
			var capacity = 4;
			var cq = new CircularStack<int>(capacity);
			for (int i = 0; i < capacity; i++)
			{ cq.Push(i); }
			for (int i = 0; i < capacity; i++)
			{
				Assert.AreEqual(capacity - (i + 1), cq.Pop());
				Assert.AreEqual(capacity - (i + 1), cq.Count());
			}
			Assert.Throws<InvalidOperationException>(() => cq.Pop());
		}

		[Test]
		public void PushPop01()
		{
			var capacity = 4;
			var cq = new CircularStack<int>(capacity);
			cq.Push(0);
			cq.Push(1);
			cq.Push(2);
			cq.Push(3);
			cq.Pop();
			cq.Pop();
			Assert.AreEqual(1, cq.Peek());
			Assert.AreEqual(0, cq.PeekBottom());
			cq.Push(4);
			cq.Push(5);
			Assert.AreEqual(5, cq.Peek());
			Assert.AreEqual(0, cq.PeekBottom());
			cq.Push(6);
			cq.Push(7);
			Assert.AreEqual(7, cq.Peek());
			Assert.AreEqual(4, cq.PeekBottom());
		}

		[Test]
		public void PeekPeekBottom01()
		{
			var capacity = 4;
			var cq = new CircularStack<int>(capacity);
			for (int i = 0; i < 6; i++)
			{ cq.Push(i); }
			Assert.AreEqual(capacity, cq.Count());
			Assert.AreEqual(5, cq.Peek());
			Assert.AreEqual(2, cq.PeekBottom());
		}

		[Test]
		public void IsEmpty01()
		{
			var cq = new CircularStack<int>(1);
			Assert.IsTrue(cq.IsEmpty());
			cq.Push(1);
			Assert.IsFalse(cq.IsEmpty());
			cq.Pop();
			Assert.IsTrue(cq.IsEmpty());
		}

		[Test]
		public void Count01()
		{
			var cq = new CircularStack<int>(4);
			Assert.AreEqual(0, cq.Count());
			cq.Push(1);
			cq.Push(1);
			Assert.AreEqual(2, cq.Count());
			cq.Pop();
			Assert.AreEqual(1, cq.Count());
			cq.Pop();
			Assert.AreEqual(0, cq.Count());
		}

		[Test]
		public void Capacity01()
		{
			var capacity = 4;
			var cq = new CircularStack<int>(capacity);
			Assert.AreEqual(capacity, cq.Capacity());
			for (int i = 0; i < 6; i++)
			{ cq.Push(i); }
			Assert.AreEqual(capacity, cq.Capacity());
		}

		[Test]
		public void Clear01()
		{
			var capacity = 4;
			var cq = new CircularStack<int>(capacity);
			Assert.AreEqual(capacity, cq.Capacity());
			for (int i = 0; i < 6; i++)
			{ cq.Push(i); }
			Assert.AreEqual(capacity, cq.Count());
			cq.Clear();
			Assert.AreEqual(0, cq.Count());
			for (int i = 0; i < 6; i++)
			{ cq.Push(i); }
			Assert.AreEqual(capacity, cq.Count());
		}

		[Test]
		public void ArgumentOutOfRange01()
		{
			Assert.Throws<ArgumentOutOfRangeException>(() => new CircularStack<int>(0));
			Assert.Throws<ArgumentOutOfRangeException>(() => new CircularStack<int>(-1));
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/CircularQueueTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class CircularQueueTest
	{
		[Test]
		public void Enqueue01()
		{
			var capacity = 4;
			var cq = new CircularQueue<int>(capacity);
			for (int i = 0; i < 6; i++)
			{ cq.Enqueue(i); }
			Assert.AreEqual(capacity, cq.Count());
		}

		[Test]
		public void Dequeue01()
		{
			var capacity = 4;
			var cq = new CircularQueue<int>(capacity);
			for (int i = 0; i < capacity; i++)
			{ cq.Enqueue(i); }
			for (int i = 0; i < capacity; i++)
			{
				Assert.AreEqual(i, cq.Dequeue());
				Assert.AreEqual(capacity - (i + 1), cq.Count());
			}
			Assert.Throws<InvalidOperationException>(() => cq.Dequeue());
		}

		[Test]
		public void EnqueueDequeue01()
		{
			var capacity = 4;
			var cq = new CircularQueue<int>(capacity);
			cq.Enqueue(0);
			cq.Enqueue(1);
			cq.Enqueue(2);
			cq.Enqueue(3);
			cq.Dequeue();
			cq.Dequeue();
			Assert.AreEqual(2, cq.Peek());
			Assert.AreEqual(3, cq.PeekTail());
			cq.Enqueue(4);
			cq.Enqueue(5);
			Assert.AreEqual(2, cq.Peek());
			Assert.AreEqual(5, cq.PeekTail());
			cq.Enqueue(6);
			cq.Enqueue(7);
			Assert.AreEqual(4, cq.Peek());
			Assert.AreEqual(7, cq.PeekTail());
		}

		[Test]
		public void PeekPeekTail01()
		{
			var capacity = 4;
			var cq = new CircularQueue<int>(capacity);
			for (int i = 0; i < 6; i++)
			{ cq.Enqueue(i); }
			Assert.AreEqual(capacity, cq.Count());
			Assert.AreEqual(2, cq.Peek());
			Assert.AreEqual(5, cq.PeekTail());
		}

		[Test]
		public void IsEmpty01()
		{
			var cq = new CircularQueue<int>(1);
			Assert.IsTrue(cq.IsEmpty());
			cq.Enqueue(1);
			Assert.IsFalse(cq.IsEmpty());
			cq.Dequeue();
			Assert.IsTrue(cq.IsEmpty());
		}

		[Test]
		public void Count01()
		{
			var cq = new CircularQueue<int>(4);
			Assert.AreEqual(0, cq.Count());
			cq.Enqueue(1);
			cq.Enqueue(1);
			Assert.AreEqual(2, cq.Count());
			cq.Dequeue();
			Assert.AreEqual(1, cq.Count());
			cq.Dequeue();
			Assert.AreEqual(0, cq.Count());
		}

		[Test]
		public void Capacity01()
		{
			var capacity = 4;
			var cq = new CircularQueue<int>(capacity);
			Assert.AreEqual(capacity, cq.Capacity());
			for (int i = 0; i < 6; i++)
			{ cq.Enqueue(i); }
			Assert.AreEqual(capacity, cq.Capacity());
		}

		[Test]
		public void Clear01()
		{
			var capacity = 4;
			var cq = new CircularQueue<int>(capacity);
			Assert.AreEqual(capacity, cq.Capacity());
			for (int i = 0; i < 6; i++)
			{ cq.Enqueue(i); }
			Assert.AreEqual(capacity, cq.Count());
			cq.Clear();
			Assert.AreEqual(0, cq.Count());
			for (int i = 0; i < 6; i++)
			{ cq.Enqueue(i); }
			Assert.AreEqual(capacity, cq.Count());
		}

		[Test]
		public void ArgumentOutOfRange01()
		{
			Assert.Throws<ArgumentOutOfRangeException>(() => new CircularQueue<int>(0));
			Assert.Throws<ArgumentOutOfRangeException>(() => new CircularQueue<int>(-1));
		}
	}
}


<|start_filename|>src/rm.Extensions/NameValueCollectionExtension.cs<|end_filename|>
﻿using System.Collections.Specialized;
using System.Text;
using System.Web;

namespace rm.Extensions
{
	/// <summary>
	/// NameValueCollection extensions.
	/// </summary>
	public static class NameValueCollectionExtension
	{
		/// <summary>
		/// Gets query string for name value collection.
		/// </summary>
		public static string ToQueryString(this NameValueCollection collection,
			bool prefixQuestionMark = true)
		{
			collection.ThrowIfArgumentNull(nameof(collection));
			if (collection.Keys.Count == 0)
			{
				return "";
			}
			var buffer = new StringBuilder();
			if (prefixQuestionMark)
			{
				buffer.Append("?");
			}
			var append = false;
			for (int i = 0; i < collection.Keys.Count; i++)
			{
				var key = collection.Keys[i];
				var values = collection.GetValues(key);
				key.ThrowIfNull(nameof(key));
				values.ThrowIfNull(nameof(values));
				foreach (var value in values)
				{
					if (append)
					{
						buffer.Append("&");
					}
					append = true;
					buffer.AppendFormat("{0}={1}", key.UrlEncode(), value.UrlEncode());
				}
			}
			return buffer.ToString();
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/HelperTest.cs<|end_filename|>
﻿using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class HelperTest
	{
		[Test]
		public void Swap01()
		{
			var t1 = 1;
			var t2 = 2;
			Assert.AreNotEqual(t1, t2);
			Helper.Swap(ref t1, ref t2);
			Assert.AreNotEqual(1, t1);
			Assert.AreNotEqual(2, t2);
			Assert.AreEqual(2, t1);
			Assert.AreEqual(1, t2);
		}

		[Test]
		public void Swap02()
		{
			var c1 = new object(); var c1copy = c1;
			var c2 = new object(); var c2copy = c2;
			Assert.AreNotEqual(c1, c2);
			Helper.Swap(ref c1, ref c2);
			Assert.AreNotEqual(c1copy, c1);
			Assert.AreNotEqual(c2copy, c2);
			Assert.AreEqual(c2copy, c1);
			Assert.AreEqual(c1copy, c2);
		}

		[Test]
		public void Swap03()
		{
			object c1 = null; var c1copy = c1;
			var c2 = new object(); var c2copy = c2;
			Assert.AreNotEqual(c1, c2);
			Helper.Swap(ref c1, ref c2);
			Assert.AreNotEqual(c1copy, c1);
			Assert.AreNotEqual(c2copy, c2);
			Assert.AreEqual(c2copy, c1);
			Assert.AreEqual(c1copy, c2);
		}
	}
}


<|start_filename|>src/rm.Extensions/LruCache.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace rm.Extensions
{
	/// <summary>
	/// Defines LRU cache methods.
	/// </summary>
	interface ILruCache<TKey, TValue>
	{
		/// <summary>
		/// Gets value for <paramref name="key"/> from cache.
		/// </summary>
		/// <remarks>
		/// Makes item MRU.
		/// </remarks>
		TValue Get(TKey key);

		/// <summary>
		/// Inserts <paramref name="value"/> for <paramref name="key"/> in cache.
		/// </summary>
		/// <remarks>
		/// Makes item MRU (also if <paramref name="key"/> already exists).
		/// </remarks>
		void Insert(TKey key, TValue value);

		/// <summary>
		/// Removes <paramref name="key"/> from cache.
		/// </summary>
		bool Remove(TKey key);

		/// <summary>
		/// Returns true if <paramref name="key"/> exists in cache.
		/// </summary>
		bool HasKey(TKey key);

		/// <summary>
		/// Returns true if cache is empty.
		/// </summary>
		bool IsEmpty();

		/// <summary>
		/// Returns true if cache is full.
		/// </summary>
		bool IsFull();

		/// <summary>
		/// Returns capacity of cache.
		/// </summary>
		int Capacity();

		/// <summary>
		/// Returns count of cache.
		/// </summary>
		int Count();

		/// <summary>
		/// Clears cache.
		/// </summary>
		void Clear();
	}

	/// <summary>
	/// LRU cache.
	/// </summary>
	/// <remarks>
	/// Uses deque and map as backing store.
	///
	/// All methods are O(1) time.
	/// </remarks>
	public class LruCache<TKey, TValue> : ILruCache<TKey, TValue>
	{
		#region members

		private readonly int n;
		private readonly IDeque<TKey> dq;
		private readonly IDictionary<TKey, (Deque.Node<TKey>, TValue)> map;

		#endregion

		#region ctors

		public LruCache(int n)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n));
			this.n = n;
			dq = new Deque<TKey>();
			map = new Dictionary<TKey, (Deque.Node<TKey>, TValue)>(capacity: n);
		}

		#endregion

		#region ILruCache<TKey, TValue> methods

		/// <summary>
		/// Gets value for <paramref name="key"/> from cache.
		/// </summary>
		/// <remarks>
		/// Makes item MRU.
		/// </remarks>
		public TValue Get(TKey key)
		{
			if (!map.ContainsKey(key))
			{
				return default(TValue);
			}
			var (node, value) = map[key];
			dq.MakeTail(node);
			return value;
		}

		/// <summary>
		/// Inserts <paramref name="value"/> for <paramref name="key"/> in cache.
		/// </summary>
		/// <remarks>
		/// Makes item MRU (also if <paramref name="key"/> already exists).
		/// </remarks>
		public void Insert(TKey key, TValue value)
		{
			key.ThrowIfArgumentNull(nameof(key));
			if (Capacity() == 0)
			{
				return;
			}
			if (map.ContainsKey(key))
			{
				var (node, _) = map[key];
				dq.MakeTail(node);
				map[key] = (node, value);
				return;
			}
			if (IsFull())
			{
				// remove lru
				map.Remove(dq.Dequeue());
			}
			map[key] = (dq.Enqueue(key), value);
		}

		/// <summary>
		/// Removes <paramref name="key"/> from cache.
		/// </summary>
		public bool Remove(TKey key)
		{
			if (!map.ContainsKey(key))
			{
				return false;
			}
			var (node, _) = map[key];
			dq.Delete(node);
			map.Remove(key);
			return true;
		}

		/// <summary>
		/// Returns true if <paramref name="key"/> exists in cache.
		/// </summary>
		public bool HasKey(TKey key)
		{
			return map.ContainsKey(key);
		}

		/// <summary>
		/// Returns true if cache is empty.
		/// </summary>
		public bool IsEmpty()
		{
			return Count() == 0;
		}

		/// <summary>
		/// Returns true if cache is full.
		/// </summary>
		public bool IsFull()
		{
			return Count() == Capacity();
		}

		/// <summary>
		/// Returns capacity of cache.
		/// </summary>
		public int Capacity()
		{
			return n;
		}

		/// <summary>
		/// Returns count of cache.
		/// </summary>
		public int Count()
		{
			return map.Count;
		}

		/// <summary>
		/// Clears cache.
		/// </summary>
		public void Clear()
		{
			dq.Clear();
			map.Clear();
		}

		#endregion
	}
}


<|start_filename|>tests/rm.ExtensionsTest/ThrowExtensionTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class ThrowExtensionTest
	{
		[Test]
		[TestCase((object)null)]
		public void ThrowIfNull01(object o)
		{
			Assert.Throws<NullReferenceException>(() => { o.ThrowIfNull(); });
		}

		[Test]
		public void ThrowIfNull02()
		{
			Assert.DoesNotThrow(() => { new object().ThrowIfNull(); });
		}

		[Test]
		[TestCase((object)null, "ex message")]
		public void ThrowIfNull03(object o, string m)
		{
			try
			{
				o.ThrowIfNull(m);
				Assert.Fail();
			}
			catch (NullReferenceException ex)
			{
				Assert.IsTrue(ex.Message.Contains(m));
			}
			catch (Exception)
			{
				Assert.Fail();
			}
		}

		[Test]
		public void ThrowIfNull04()
		{
			Assert.Throws<NullReferenceException>(() =>
			{
				new[] { new object(), null }.ThrowIfAnyNull();
			});
		}

		[Test]
		public void ThrowIfNull05()
		{
			Assert.DoesNotThrow(() =>
			{
				new[] { new object(), new object() }.ThrowIfAnyNull();
			});
		}

		[Test]
		[TestCase((object)null)]
		public void ThrowIfArgumentNull01(object o)
		{
			Assert.Throws<ArgumentNullException>(() => { o.ThrowIfArgumentNull(); });
		}

		[Test]
		public void ThrowIfArgumentNull02()
		{
			Assert.DoesNotThrow(() => { new object().ThrowIfArgumentNull(); });
		}

		[TestCase((object)null, "ex message")]
		public void ThrowIfArgumentNull03(object o, string m)
		{
			try
			{
				o.ThrowIfArgumentNull(m);
				Assert.Fail();
			}
			catch (ArgumentNullException ex)
			{
				Assert.IsTrue(ex.Message.Contains(m));
			}
			catch (Exception)
			{
				Assert.Fail();
			}
		}

		[Test]
		public void ThrowIfArgumentNull04()
		{
			Assert.Throws<ArgumentNullException>(() =>
			{
				new[] { new object(), null }.ThrowIfAnyArgumentNull();
			});
		}

		[Test]
		public void ThrowIfArgumentNull05()
		{
			Assert.DoesNotThrow(() =>
			{
				new[] { new object(), new object() }.ThrowIfAnyArgumentNull();
			});
		}

		[Test]
		[TestCase((string)null)]
		public void ThrowIfNullOrEmpty01a(string s)
		{
			Assert.Throws<NullReferenceException>(() => { s.ThrowIfNullOrEmpty(); });
		}

		[Test]
		[TestCase("")]
		public void ThrowIfNullOrEmpty01b(string s)
		{
			Assert.Throws<EmptyException>(() => { s.ThrowIfNullOrEmpty(); });
		}

		[Test]
		[TestCase("s")]
		[TestCase("  ")]
		public void ThrowIfNullOrEmpty02(string s)
		{
			Assert.DoesNotThrow(() => { s.ThrowIfNullOrEmpty(); });
		}

		[Test]
		public void ThrowIfNullOrEmpty03()
		{
			Assert.Throws<NullReferenceException>(() =>
			{
				new[] { "s1", null }.ThrowIfNullOrEmpty();
			});
		}

		[Test]
		[TestCase((string)null)]
		public void ThrowIfNullOrEmptyArgument01a(string s)
		{
			Assert.Throws<ArgumentNullException>(() => { s.ThrowIfNullOrEmptyArgument(); });
		}

		[Test]
		[TestCase("")]
		public void ThrowIfNullOrEmptyArgument01b(string s)
		{
			Assert.Throws<EmptyException>(() => { s.ThrowIfNullOrEmptyArgument(); });
		}

		[Test]
		[TestCase("s")]
		[TestCase("  ")]
		public void ThrowIfNullOrEmptyArgument02(string s)
		{
			Assert.DoesNotThrow(() => { s.ThrowIfNullOrEmptyArgument(); });
		}

		[Test]
		public void ThrowIfNullOrEmptyArgument03()
		{
			Assert.Throws<ArgumentNullException>(() =>
			{
				new[] { "s1", null }.ThrowIfNullOrEmptyArgument();
			});
		}

		[Test]
		[TestCase((string)null)]
		public void ThrowIfNullOrWhiteSpace01a(string s)
		{
			Assert.Throws<NullReferenceException>(() => { s.ThrowIfNullOrWhiteSpace(); });
		}

		[Test]
		[TestCase("")]
		[TestCase("  ")]
		public void ThrowIfNullOrWhiteSpace01b(string s)
		{
			Assert.Throws<EmptyException>(() => { s.ThrowIfNullOrWhiteSpace(); });
		}

		[Test]
		[TestCase("s")]
		public void ThrowIfNullOrWhiteSpace02(string s)
		{
			Assert.DoesNotThrow(() => { s.ThrowIfNullOrWhiteSpace(); });
		}

		[Test]
		[TestCase((string)null)]
		public void ThrowIfNullOrWhiteSpaceArgument01a(string s)
		{
			Assert.Throws<ArgumentNullException>(() => { s.ThrowIfNullOrWhiteSpaceArgument(); });
		}

		[Test]
		[TestCase("")]
		[TestCase("  ")]
		public void ThrowIfNullOrWhiteSpaceArgument01b(string s)
		{
			Assert.Throws<EmptyException>(() => { s.ThrowIfNullOrWhiteSpaceArgument(); });
		}

		[Test]
		[TestCase("s")]
		public void ThrowIfNullOrWhiteSpaceArgument02(string s)
		{
			Assert.DoesNotThrow(() => { s.ThrowIfNullOrWhiteSpaceArgument(); });
		}

		[Test]
		public void IsNotVoidReturn()
		{
			Assert.IsNotNull(new object().ThrowIfNull());
			Assert.IsNotNull(new object().ThrowIfArgumentNull());
			Assert.IsNotNull(new[] { new object() }.ThrowIfAnyNull());
			Assert.IsNotNull(new[] { new object() }.ThrowIfAnyArgumentNull());
			Assert.IsNotNull("s".ThrowIfNullOrEmpty());
			Assert.IsNotNull("s".ThrowIfNullOrEmptyArgument());
			Assert.IsNotNull(new[] { "s", "t" }.ThrowIfNullOrEmpty());
			Assert.IsNotNull(new[] { "s", "t" }.ThrowIfNullOrEmptyArgument());
			Assert.IsNotNull("s".ThrowIfNullOrWhiteSpace());
			Assert.IsNotNull("s".ThrowIfNullOrWhiteSpaceArgument());
			Assert.IsNotNull(0.ThrowIfArgumentOutOfRange());
			Assert.IsNotNull(((uint)0).ThrowIfArgumentOutOfRange());
		}
	}
}


<|start_filename|>src/rm.Extensions/GraphExtension.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace rm.Extensions
{
	/// <summary>
	/// Graph extensions.
	/// </summary>
	public static class GraphExtension
	{
		/// <summary>
		/// Returns true if graph is cyclic.
		/// </summary>
		public static bool IsCyclic(this IGraph graph)
		{
			graph.ThrowIfArgumentNull(nameof(graph));
			graph.Nodes.ThrowIfArgumentNull(nameof(graph.Nodes));
			var acyclicNodes = new HashSet<string>();
			var path = new HashSet<string>();
			foreach (var node in graph.Nodes)
			{
				if (IsCyclic(node, path, acyclicNodes))
				{
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Returns true if graph node is cyclic.
		/// </summary>
		/// <param name="node">Graph node.</param>
		/// <param name="path">Path from a graph node to this <paramref name="node"/>.</param>
		/// <param name="acyclicNodes">Nodes from which a cycle does not exist.</param>
		/// <returns>Returns true if graph node is cyclic.</returns>
		private static bool IsCyclic(IGraphNode node, ISet<string> path, ISet<string> acyclicNodes)
		{
			node.ThrowIfArgumentNull(nameof(node));
			node.Id.ThrowIfArgumentNull(nameof(node.Id));
			node.Neighbors.ThrowIfArgumentNull(nameof(node.Neighbors));
			if (acyclicNodes.Contains(node.Id))
			{
				return false;
			}
			if (path.Contains(node.Id))
			{
				return true;
			}
			path.Add(node.Id);
			foreach (var neighbor in node.Neighbors)
			{
				if (IsCyclic(neighbor, path, acyclicNodes))
				{
					return true;
				}
			}
			path.Remove(node.Id);
			acyclicNodes.Add(node.Id);
			return false;
		}
	}

	#region Graph interfaces

	/// <summary>
	/// Defines Graph.
	/// </summary>
	public interface IGraph
	{
		/// <summary>
		/// Nodes in the graph.
		/// </summary>
		IEnumerable<IGraphNode> Nodes { get; }
	}

	/// <summary>
	/// Defines Graph node.
	/// </summary>
	public interface IGraphNode
	{
		/// <summary>
		/// Node's Id.
		/// </summary>
		string Id { get; }

		/// <summary>
		/// Node's neighbors.
		/// </summary>
		IEnumerable<IGraphNode> Neighbors { get; }
	}

	#endregion
}


<|start_filename|>src/rm.Extensions/DateTimeExtension.cs<|end_filename|>
﻿using System;
using System.Data.SqlTypes;

namespace rm.Extensions
{
	/// <summary>
	/// DateTime extensions.
	/// </summary>
	public static class DateTimeExtension
	{
		/// <summary>
		/// UTC date format string.
		/// </summary>
		public static readonly string UtcDateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'";

		/// <summary>
		/// Min value for Sql datetime to save in sql db.
		/// </summary>
		public static readonly DateTime SqlDateTimeMinUtc = SqlDateTime.MinValue.Value.AsUtcKind();

		/// <summary>
		/// Gets the UTC datetime format for the date.
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		public static string ToUtcFormatString(this DateTime date)
		{
			return date.ToUniversalTime().ToString(UtcDateFormat);
		}

		/// <summary>
		/// Gets the min value for Sql datetime.
		/// </summary>
		public static DateTime ToSqlDateTimeMinUtc(this DateTime date)
		{
			return SqlDateTimeMinUtc;
		}

		/// <summary>
		/// Specifies datetime's kind as UTC.
		/// </summary>
		/// <param name="datetime"></param>
		/// <returns></returns>
		/// <remarks>
		/// Date read from db or parsed from string has its Kind as Unspecified.
		/// Specifying its kind as UTC is needed if date is expected to be UTC.
		/// ToUniversalTime() assumes that the kind is local while converting it and is undesirable.
		/// </remarks>
		public static DateTime AsUtcKind(this DateTime datetime)
		{
			return DateTime.SpecifyKind(datetime, DateTimeKind.Utc);
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/Sample/ComparableClass2Comparer.cs<|end_filename|>
﻿using System.Collections.Generic;

namespace rm.ExtensionsTest.Sample
{
	public class ComparableClass2Comparer : IComparer<ComparableClass2>
	{
		public int Compare(ComparableClass2 x, ComparableClass2 y)
		{
			return x.Value.CompareTo(y.Value);
		}
	}
}


<|start_filename|>src/rm.Extensions/IntExtension.cs<|end_filename|>
﻿using System;
using System.Numerics;

namespace rm.Extensions
{
	/// <summary>
	/// Int extensions.
	/// </summary>
	public static class IntExtension
	{
		/// <summary>
		/// Gets n!.
		/// </summary>
		public static BigInteger Factorial(this int n)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n));
			BigInteger product = 1;
			for (int i = 1; i <= n; i++)
			{
				product *= i;
			}
			return product;
		}

		/// <summary>
		/// Gets nPr.
		/// </summary>
		public static BigInteger Permutation(this int n, int r)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n));
			r.ThrowIfArgumentOutOfRange(nameof(r), maxRange: n);
			BigInteger result = (n.Factorial() / (n - r).Factorial());
			return result;
		}

		/// <summary>
		/// Gets nCr.
		/// </summary>
		public static BigInteger Combination(this int n, int r)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n));
			r.ThrowIfArgumentOutOfRange(nameof(r), maxRange: n);
			BigInteger result = (n.Factorial() / ((n - r).Factorial() * r.Factorial()));
			return result;
		}

		/// <summary>
		/// Gets scrabble count for n.
		/// </summary>
		/// <remarks>nP1 + nP2 + ... + nPn</remarks>
		public static BigInteger ScrabbleCount(this int n)
		{
			return ScrabbleCount(n, n);
		}

		/// <summary>
		/// Gets scrabble count for n with limit.
		/// </summary>
		/// <remarks>nP1 + nP2 + ... + nPlimit, where limit is up to n</remarks>
		public static BigInteger ScrabbleCount(this int n, int limit)
		{
			BigInteger sum = 0;
			for (int i = 1; i <= limit; i++)
			{
				sum += n.Permutation(i);
			}
			return sum;
		}

		/// <summary>
		/// Rounds int as "k" for kilo, "m" for mega, "g" for giga.
		/// </summary>
		public static string Round(this int n, uint digits = 0)
		{
			string s;
			var nabs = Math.Abs(n);
			if (nabs < 1000)
			{
				s = n + "";
			}
			else if (nabs < 1000000)
			{
				s = ((decimal)n / 1000).TruncateTo(digits) + "k";
			}
			else if (nabs < 1000000000)
			{
				s = ((decimal)n / 1000000).TruncateTo(digits) + "m";
			}
			else
			{
				s = ((decimal)n / 1000000000).TruncateTo(digits) + "g";
			}
			return s;
		}

		/// <summary>
		/// Rounds value down to an integer multiple of a given bin size.
		/// </summary>
		/// <remarks>
		/// See https://docs.microsoft.com/en-us/azure/kusto/query/binfunction
		/// </remarks>
		public static int Bin(this int n, int binSize)
		{
			var result = (n / binSize) * binSize;
			if (n < 0)
			{
				result -= binSize;
			}
			return result;
		}
	}
}


<|start_filename|>src/rm.Extensions/Wrapped.cs<|end_filename|>
﻿namespace rm.Extensions
{
	/// <summary>
	/// Wrapped type.
	/// </summary>
	/// <remarks>Useful to avoid pass by reference parameters.</remarks>
	public class Wrapped<T>
	{
		public T Value { get; set; }

		public Wrapped(T value)
		{
			Value = value;
		}

		///// <summary>
		///// Converts T to Wrapped{T}.
		///// </summary>
		///// <remarks>
		///// This does NOT work as a new instance is returned.
		///// And cannot overload the assignment "=" operator in C#.
		///// 
		///// It WOULD allow this:
		/////     // same as wrappedT.Value = value;
		/////     wrappedT = value;
		///// </remarks>
		//public static implicit operator Wrapped<T>(T value)
		//{
		//	return new Wrapped<T>(value);
		//}

		///// <summary>
		///// Converts Wrapped{T} to T.
		///// </summary>
		///// <remarks>
		///// This works. But not allowing due to inconsistency (see above conversion operator limitation).
		///// 
		///// It allows this:
		/////     // same as value = wrappedT.Value;
		/////     value = wrappedT;
		///// </remarks>
		//public static implicit operator T(Wrapped<T> wrappedT)
		//{
		//	return wrappedT.Value;
		//}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/TimeSpanExtensionTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class TimeSpanExtensionTest
	{
		[Test]
		[TestCase(0, 0, 0, 0, 10, "10ms")]
		[TestCase(0, 0, 0, 10, 10, "10s")]
		[TestCase(0, 0, 10, 10, 10, "10m")]
		[TestCase(0, 10, 10, 10, 10, "10h")]
		[TestCase(6, 10, 10, 10, 10, "6d")]
		[TestCase(10, 10, 10, 10, 10, "1wk")]
		[TestCase(30, 10, 10, 10, 10, "1mth")]
		[TestCase(365, 120, 0, 0, 0, "1y")]
		[TestCase(0, 0, 0, 0, 999, "999ms")]
		[TestCase(0, 0, 0, 0, 1000, "1s")]
		[TestCase(20, 10, 10, 10, 10, "2wk")]
		public void Round01(int d, int h, int m, int s, int ms, string result)
		{
			var ts = new TimeSpan(d, h, m, s, ms);
			Assert.AreEqual(result, ts.Round());
		}

		[Test]
		[TestCase(1.9d, "1s")]
		[TestCase(1.009d, "1s")]
		public void Round02(double s, string result)
		{
			var ts = TimeSpan.FromSeconds(s);
			Assert.AreEqual(result, ts.Round());
		}

		[Test]
		[TestCase(10)]
		[TestCase(100)]
		[TestCase(1000)]
		public void Days01(int n)
		{
			Assert.AreEqual(n, n.Days().TotalDays);
		}

		[Test]
		[TestCase(10)]
		[TestCase(100)]
		[TestCase(1000)]
		public void Hours01(int n)
		{
			Assert.AreEqual(n, n.Hours().TotalHours);
		}

		[Test]
		[TestCase(10)]
		[TestCase(100)]
		[TestCase(1000)]
		public void Minutes01(int n)
		{
			Assert.AreEqual(n, n.Minutes().TotalMinutes);
		}

		[Test]
		[TestCase(10)]
		[TestCase(100)]
		[TestCase(1000)]
		public void Seconds01(int n)
		{
			Assert.AreEqual(n, n.Seconds().TotalSeconds);
		}

		[Test]
		[TestCase(10)]
		[TestCase(100)]
		[TestCase(1000)]
		public void Milliseconds01(int n)
		{
			Assert.AreEqual(n, n.Milliseconds().TotalMilliseconds);
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/StringExtensionTest.cs<|end_filename|>
﻿using System;
using System.Linq;
using System.Numerics;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class StringExtensionTest
	{
		[Test]
		[TestCase((string)null)]
		[TestCase("")]
		public void IsNullOrEmpty01(string s)
		{
			Assert.IsTrue(s.IsNullOrEmpty());
		}

		[Test]
		[TestCase("s")]
		[TestCase("  ")]
		public void IsNullOrEmpty02(string s)
		{
			Assert.IsFalse(s.IsNullOrEmpty());
		}

		[Test]
		[TestCase((string)null)]
		[TestCase("  ")]
		[TestCase("")]
		public void IsNullOrWhiteSpace01(string s)
		{
			Assert.IsTrue(s.IsNullOrWhiteSpace());
		}

		[Test]
		[TestCase("s")]
		public void IsNullOrWhiteSpace02(string s)
		{
			Assert.IsFalse(s.IsNullOrWhiteSpace());
		}

		[Test]
		[TestCase((string)null, "")]
		[TestCase("s", "s")]
		[TestCase("", "")]
		[TestCase(" ", "")]
		public void OrEmpty01(string s, string expected)
		{
			Assert.AreEqual(expected, s.OrEmpty());
		}

		[Test]
		[TestCase((string)null, "default", "default")]
		[TestCase("", "default", "default")]
		[TestCase(" ", "default", "default")]
		[TestCase("s", "default", "s")]
		public void Or01(string s, string or, string expected)
		{
			Assert.AreEqual(expected, s.Or(or));
		}

		[Test]
		[TestCase((string)null, (string)null)]
		[TestCase("", (string)null)]
		[TestCase(" ", " ")]
		[TestCase("s", "s")]
		public void NullIfEmpty01(string s, string expected)
		{
			Assert.AreEqual(expected, s.NullIfEmpty());
		}

		[Test]
		[TestCase((string)null, (string)null)]
		[TestCase("", (string)null)]
		[TestCase(" ", (string)null)]
		[TestCase("s", "s")]
		public void NullIfWhiteSpace01(string s, string expected)
		{
			Assert.AreEqual(expected, s.NullIfWhiteSpace());
		}

		[Test]
		[TestCase("<", "&lt;")]
		[TestCase("s", "s")]
		[TestCase("", "")]
		[TestCase(" ", " ")]
		[TestCase((string)null, (string)null)]
		public void HtmlEncode01(string s, string expected)
		{
			Assert.AreEqual(expected, s.HtmlEncode());
		}

		[Test]
		[TestCase("&lt;", "<")]
		[TestCase("s", "s")]
		[TestCase("", "")]
		[TestCase(" ", " ")]
		[TestCase((string)null, (string)null)]
		public void HtmlDecode01(string s, string expected)
		{
			Assert.AreEqual(expected, s.HtmlDecode());
		}

		[Test]
		[TestCase(" ", "+")]
		[TestCase("s", "s")]
		[TestCase("", "")]
		[TestCase((string)null, (string)null)]
		public void UrlEncode01(string s, string expected)
		{
			Assert.AreEqual(expected, s.UrlEncode());
		}

		[Test]
		[TestCase("+", " ")]
		[TestCase("s", "s")]
		[TestCase("", "")]
		[TestCase((string)null, (string)null)]
		public void UrlDecode01(string s, string expected)
		{
			Assert.AreEqual(expected, s.UrlDecode());
		}

		[Test]
		[TestCase("{} is a {1}", "{0} is a {1}", "this", "test")]
		[TestCase("{}", "{0}", 1)]
		[TestCase("{:C}", "{0:C}", 3.14d)]
		[TestCase("{,10:#,##0.00}", "{0,10:#,##0.00}", 3.14d)]
		[TestCase("{{}}{,10:#,##0.00}", "{{}}{0,10:#,##0.00}", 3.14d)]
		[TestCase("}}{{{,10:#,##0.00}", "}}{{{0,10:#,##0.00}", 3.14d)]
		[TestCase("{{{,10:#,##0.00}}}", "{{{0,10:#,##0.00}}}", 3.14d)]
		[TestCase("{}{:C}{{{,10:#,##0.00}}}", "{0}{1:C}{{{2,10:#,##0.00}}}", 1, 3.14d, 3.14d)]
		[TestCase("{}{1:C}{{{,10:#,##0.00}}}", "{0}{1:C}{{{2,10:#,##0.00}}}", 1, 3.14d, 3.14d)]
		[TestCase("no fields", "no fields", 1)]
		[TestCase("{{", "{{", 1)]
		[TestCase("}}", "}}", 1)]
		[TestCase(
			"{} {} {} by the {}. The {} {} {} are surely {}. So if {} {} {} on the {}, I'm sure {} {} {} {}.",
			"{0} {1} {2} by the {3}. The {4} {5} {6} are surely {7}. So if {8} {9} {10} on the {11}, I'm sure {12} {13} {14} {15}.",
			"She", "sells", "seashells", "seashore", "shells", "she", "sells", "seashells", "she", "sells", "shells", "seashore", "she", "sells", "seashore", "shells"
			)]
		//with meta
		[TestCase("{meta}", "{0}", 1)]
		[TestCase("{meta:C}", "{0:C}", 3.14d)]
		[TestCase("{meta,10:#,##0.00}", "{0,10:#,##0.00}", 3.14d)]
		[TestCase("{meta0}{meta1:C}{{{meta2,10:#,##0.00}}}", "{0}{1:C}{{{2,10:#,##0.00}}}", 1, 3.14d, 3.14d)]
		[TestCase("{meta0}{1:C}{{{meta1,10:#,##0.00}}}", "{0}{1:C}{{{2,10:#,##0.00}}}", 1, 3.14d, 3.14d)]
		[TestCase("The name is {last}. {first} {last}.", "The name is {0}. {1} {0}.", "Bond", "James")]
		[TestCase("The name is {0}. {first} {last}.", "The name is {0}. {1} {2}.", "Bond", "James", "Bond")]
		public void Format01(string format, string formatConverted, params object[] args)
		{
			Assert.AreEqual(string.Format(formatConverted, args), format.format(args));
		}

		[Test]
		[TestCase("{}{1:C}{{{3,10:#,##0.00}}}", "{0}{1:C}{{{3,10:#,##0.00}}}", 1, 3.14d, 3.14d)]
		[TestCase("{}{3:C}{{{,10:#,##0.00}}}", "{0}{3:C}{{{2,10:#,##0.00}}}", 1, 3.14d, 3.14d)]
		[TestCase("{{}{1:C}{{{,10:#,##0.00}}}", "{{}{1:C}{{{,10:#,##0.00}}}", 1, 3.14d, 3.14d)]
		public void Format02(string format, string formatConverted, params object[] args)
		{
			Assert.Throws<FormatException>(() => string.Format(formatConverted, args));
			Assert.Throws<FormatException>(() => format.format(args));
		}

		[Test]
		[TestCase("", true, true)]
		[TestCase("true", false, true)]
		[TestCase("false", true, false)]
		[TestCase(((string)null), true, true)]
		[TestCase("True", false, true)]
		[TestCase("FALSE", true, false)]
		public void ToBool01(string value, bool defaultValue, bool result)
		{
			Assert.AreEqual(result, value.ToBool(defaultValue));
		}

		[Test]
		[TestCase("pass", "pa$$", "pa55", "p@ss", "p@$$", "p@55")]
		[TestCase("ai", "a1", "a!", "@i", "@1", "@!")]
		[TestCase("ia", "i@", "1a", "1@", "!a", "!@")]
		[TestCase("aii", "a11", "a!!", "@ii", "@11", "@!!")]
		[TestCase("jmnpuz")]
		[TestCase("")]
		[TestCase("pw", "puu", "p2u")]
		public void Munge01(string s, params string[] munges)
		{
			var result = s.Munge().ToArray();
			foreach (var muItem in result)
			{
				Console.WriteLine(muItem);
			}
			Assert.AreEqual(munges.Length, result.Count());
			Assert.IsTrue(munges.SequenceEqual(result));
		}

		[Test]
		[TestCase("h@x0r", "haxor")]
		[TestCase("@1", "ai", "al")]
		[TestCase("1@", "ia", "la")]
		[TestCase("@11", "aii", "all")]
		[TestCase("1<", "ik", "iv", "lk", "lv")]
		[TestCase("1<<", "ikk", "ivv", "lkk", "lvv")]
		[TestCase("jmnpuz")]
		[TestCase("")]
		[TestCase("puu", "pw")]
		[TestCase("p2u", "pw")]
		public void Unmunge01(string s, params string[] unmunges)
		{
			var result = s.Unmunge().ToList();
			foreach (var muItem in result)
			{
				Console.WriteLine(muItem);
			}
			Assert.AreEqual(unmunges.Length, result.Count);
			Assert.IsTrue(unmunges.SequenceEqual(result));
		}

		[Test]
		[TestCase("this")]
		[TestCase("on")]
		[TestCase("o n")]
		[TestCase("")]
		public void Scrabble01(string word)
		{
			var result = word.Scrabble();
			foreach (var item in result)
			{
				Console.WriteLine(item);
			}
			var scrabbleCount = (int)word.Length.ScrabbleCount();
			var actualCount = result.Count();
			Console.WriteLine("scrabble count:{0}", scrabbleCount);
			Console.WriteLine("result count:{0}", actualCount);
			Assert.AreEqual(actualCount, result.Distinct().Count());
			Assert.AreEqual(scrabbleCount, actualCount);
		}

		[Test]
		[TestCase("this", 2)]
		[TestCase("on", 1)]
		[TestCase("o n", 2)]
		[TestCase("", 0)]
		public void Scrabble02(string word, int limit)
		{
			var result = word.Scrabble(limit);
			foreach (var item in result)
			{
				Console.WriteLine(item);
			}
			var scrabbleCount = (int)word.Length.ScrabbleCount(limit);
			var actualCount = result.Count();
			Console.WriteLine("scrabble count:{0}", scrabbleCount);
			Console.WriteLine("result count:{0}", actualCount);
			Assert.AreEqual(actualCount, result.Distinct().Count());
			Assert.AreEqual(scrabbleCount, actualCount);
		}

		[Test]
		[TestCase("2013-04-01T03:42:14-04:00", 2013, 4, 1, 7, 42, 14, DateTimeKind.Utc)]
		[TestCase("2012-02-29T23:00:00-04:00", 2012, 3, 1, 3, 0, 0, DateTimeKind.Utc)]
		public void ParseAsUtc01(string s,
			int year, int month, int day, int hour, int min, int sec, DateTimeKind kind)
		{
			var date = s.ParseAsUtc();
			var expectedDate = new DateTime(year, month, day, hour, min, sec, kind);
			Assert.AreEqual(expectedDate, date);
			Assert.AreEqual(expectedDate.Kind, date.Kind);
			Assert.AreEqual(expectedDate, date.ToUniversalTime());
			Assert.AreEqual(expectedDate.Kind, date.ToUniversalTime().Kind);
		}

		[Test]
		[TestCase("war and peace", "War And Peace")]
		[TestCase("but FBI is working", "But FBI Is Working")]
		[TestCase("wAr aNd pEaCe", "War And Peace")]
		[TestCase("McDonalds aNd pEaCe", "Mcdonalds And Peace")]
		public void ToTitleCase01(string s, string expected)
		{
			Assert.AreEqual(expected, s.ToTitleCase());
		}

		private void Permutation(string s, int r, BigInteger count, string[] spotchecks)
		{
			var permutations = s.Permutation(r);
			var bigCount = permutations.BigCount();
			Assert.AreEqual(permutations.Distinct().BigCount(), bigCount);
			Assert.AreEqual(s.Length.Permutation(r), bigCount);
			Assert.AreEqual(count, bigCount);
			foreach (var item in permutations)
			{
				Assert.AreEqual(r, item.Length);
			}
			foreach (var spotcheck in spotchecks)
			{
				Assert.IsTrue(permutations.Contains(spotcheck));
			}
			Console.WriteLine("permutations = {0}", permutations.Count());
		}

		[Test]
		[Category("slow")]
		[TestCase("abcdefghijklmnopqrstuvwxyz", 4, 358800, new[] { "abcd", "wxyz" })]
		public void Permutation01(string s, int r, long count, string[] spotchecks)
		{
			Permutation(s, r, count, spotchecks);
		}

		/// <remarks>For r 5, takes 13s-40s.</remarks>
		[Test]
		[Category("very.slow")]
		[TestCase("abcdefghijklmnopqrstuvwxyz", 5, 7893600, new[] { "abcde", "vwxyz" })]
		public void Permutation01_veryslow(string s, int r, long count, string[] spotchecks)
		{
			Permutation(s, r, count, spotchecks);
		}

		[Test]
		[TestCase("abcdefghijklmnopqrstuvwxyz", 2, 650, new[] { "ab", "yz" })]
		[TestCase("abcd", 4, 24, new[] { "abcd", "dcba" })]
		public void Permutation02(string s, int r, int count, string[] spotchecks)
		{
			Permutation(s, r, count, spotchecks);
		}

		[Test]
		[TestCase("abcdefghijklmnopqrstuvwxyz", (26 + 1))]
		public void Permutation03(string s, int r)
		{
			Assert.DoesNotThrow(() => s.Permutation(r));
		}

		private void Combination(string s, int r, BigInteger count, string[] spotchecks)
		{
			var combinations = s.Combination(r);
			var bigCount = combinations.BigCount();
			Assert.AreEqual(combinations.Distinct().BigCount(), bigCount);
			Assert.AreEqual(s.Length.Combination(r), bigCount);
			Assert.AreEqual(count, bigCount);
			foreach (var item in combinations)
			{
				Assert.AreEqual(r, item.Length);
			}
			foreach (var spotcheck in spotchecks)
			{
				Assert.IsTrue(combinations.Contains(spotcheck));
			}
			Console.WriteLine("combinations = {0}", combinations.Count());
		}

		[Test]
		[TestCase("abcdefghijklmnopqrstuvwxyz", 4, 14950, new[] { "abcd", "bcde" })]
		public void Combination01(string s, int r, long count, string[] spotchecks)
		{
			Combination(s, r, count, spotchecks);
		}

		/// <remarks>For r 25, takes 13s-40s.</remarks>
		[Test]
		[Category("very.slow")]
		[TestCase("abcdefghijklmnopqrstuvwxyz", 25, 26, new[] { "abcdefghijklmnopqrstuvwxy", "bcdefghijklmnopqrstuvwxyz" })]
		public void Combination01_veryslow(string s, int r, long count, string[] spotchecks)
		{
			Combination(s, r, count, spotchecks);
		}

		[Test]
		[TestCase("abc", 2, 3, new[] { "ab", "ac", "bc" })]
		public void Combination02(string s, int r, int count, string[] spotchecks)
		{
			Combination(s, r, count, spotchecks);
		}

		[Test]
		[TestCase("abcdefghijklmnopqrstuvwxyz", (26 + 1))]
		public void Combination03(string s, int r)
		{
			Assert.DoesNotThrow(() => s.Combination(r));
		}

		[Test]
		[TestCase("<EMAIL>|<EMAIL>,<EMAIL>;<EMAIL>,|;",
			new[] { "<EMAIL>", "<EMAIL>", "<EMAIL>", "<EMAIL>" })]
		public static void SplitCsv01(string s, string[] items)
		{
			var result = s.SplitCsv();
			Console.WriteLine(string.Join(" ", result));
			Assert.True(result.Count() == items.Count());
			Assert.True(items.Except(result).IsEmpty());
		}

		[Test]
		[TestCase("123", 1, "3")]
		[TestCase("123", 3, "123")]
		[TestCase("123", 5, "123")]
		[TestCase("123", 0, "")]
		[TestCase("", 1, "")]
		[TestCase("123\r\n123", 5, "\r\n123")]
		public static void SubstringTillEnd01(string s, int n, string expected)
		{
			Assert.AreEqual(expected, s.SubstringTillEnd(n));
		}

		[Test]
		public static void SubstringTillEnd02()
		{
			Assert.Throws<ArgumentNullException>(() => ((string)null).SubstringTillEnd(1));
			Assert.Throws<ArgumentOutOfRangeException>(() => "123".SubstringTillEnd(-1));
		}

		[Test]
		[TestCase("this", 1, 1, "")]
		[TestCase("this", 1, 3, "hi")]
		[TestCase("this", 1, 4, "his")]
		public static void SubstringByIndex01(string s, int start, int end, string expected)
		{
			Assert.AreEqual(expected, s.SubstringByIndex(start, end));
		}

		[Test]
		[TestCase("this", 1, 0)]
		[TestCase("this", 1, 5)]
		public static void SubstringByIndex02(string s, int start, int end)
		{
			Assert.Throws<ArgumentOutOfRangeException>(() => s.SubstringByIndex(start, end));
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/BitSetTest.cs<|end_filename|>
﻿using System;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class BitSetTest
	{
		[Test]
		public void Add01()
		{
			var bitset = new BitSet(1023);

			Assert.IsFalse(bitset.Has(250));
			bitset.Add(250);
			Assert.IsTrue(bitset.Has(250));

			Assert.IsFalse(bitset.Has(0));
			bitset.Add(0);
			Assert.IsTrue(bitset.Has(0));

			Assert.IsFalse(bitset.Has(1023));
			bitset.Add(1023);
			Assert.IsTrue(bitset.Has(1023));

			bitset.Add(250);
			Assert.IsTrue(bitset.Has(250));
		}

		[Test]
		public void Add01u()
		{
			var bitset = new BitSet((uint)1023);

			Assert.IsFalse(bitset.Has((uint)250));
			bitset.Add((uint)250);
			Assert.IsTrue(bitset.Has((uint)250));

			Assert.IsFalse(bitset.Has((uint)0));
			bitset.Add((uint)0);
			Assert.IsTrue(bitset.Has((uint)0));

			Assert.IsFalse(bitset.Has((uint)1023));
			bitset.Add((uint)1023);
			Assert.IsTrue(bitset.Has((uint)1023));

			bitset.Add((uint)250);
			Assert.IsTrue(bitset.Has((uint)250));
		}

		[Test]
		public void Remove01()
		{
			var bitset = new BitSet(2000);

			bitset.Remove(750);
			Assert.IsFalse(bitset.Has(750));
			bitset.Add(750);
			Assert.IsTrue(bitset.Has(750));
			bitset.Remove(750);
			Assert.IsFalse(bitset.Has(750));

			bitset.Remove(0);
			Assert.IsFalse(bitset.Has(0));
			bitset.Add(0);
			Assert.IsTrue(bitset.Has(0));
			bitset.Remove(0);
			Assert.IsFalse(bitset.Has(0));

			bitset.Remove(1023);
			Assert.IsFalse(bitset.Has(1023));
			bitset.Add(1023);
			Assert.IsTrue(bitset.Has(1023));
			bitset.Remove(1023);
			Assert.IsFalse(bitset.Has(1023));

			bitset.Remove(750);
			Assert.IsFalse(bitset.Has(750));
		}

		[Test]
		public void Remove01u()
		{
			var bitset = new BitSet((uint)2000);

			bitset.Remove((uint)750);
			Assert.IsFalse(bitset.Has((uint)750));
			bitset.Add((uint)750);
			Assert.IsTrue(bitset.Has((uint)750));
			bitset.Remove((uint)750);
			Assert.IsFalse(bitset.Has((uint)750));

			bitset.Remove((uint)0);
			Assert.IsFalse(bitset.Has((uint)0));
			bitset.Add((uint)0);
			Assert.IsTrue(bitset.Has((uint)0));
			bitset.Remove((uint)0);
			Assert.IsFalse(bitset.Has((uint)0));

			bitset.Remove((uint)1023);
			Assert.IsFalse(bitset.Has((uint)1023));
			bitset.Add((uint)1023);
			Assert.IsTrue(bitset.Has((uint)1023));
			bitset.Remove((uint)1023);
			Assert.IsFalse(bitset.Has((uint)1023));

			bitset.Remove((uint)750);
			Assert.IsFalse(bitset.Has((uint)750));
		}

		[Test]
		public void Toggle01()
		{
			var bitset = new BitSet(1500);

			Assert.IsFalse(bitset.Has(500));
			bitset.Toggle(500);
			Assert.IsTrue(bitset.Has(500));
			bitset.Toggle(500);
			Assert.IsFalse(bitset.Has(500));

			Assert.IsFalse(bitset.Has(0));
			bitset.Toggle(0);
			Assert.IsTrue(bitset.Has(0));
			bitset.Toggle(0);
			Assert.IsFalse(bitset.Has(0));

			Assert.IsFalse(bitset.Has(1023));
			bitset.Toggle(1023);
			Assert.IsTrue(bitset.Has(1023));
			bitset.Toggle(1023);
			Assert.IsFalse(bitset.Has(1023));
		}

		[Test]
		public void Toggle01u()
		{
			var bitset = new BitSet((uint)1500);

			Assert.IsFalse(bitset.Has((uint)500));
			bitset.Toggle((uint)500);
			Assert.IsTrue(bitset.Has((uint)500));
			bitset.Toggle((uint)500);
			Assert.IsFalse(bitset.Has((uint)500));

			Assert.IsFalse(bitset.Has((uint)0));
			bitset.Toggle((uint)0);
			Assert.IsTrue(bitset.Has((uint)0));
			bitset.Toggle((uint)0);
			Assert.IsFalse(bitset.Has((uint)0));

			Assert.IsFalse(bitset.Has((uint)1023));
			bitset.Toggle((uint)1023);
			Assert.IsTrue(bitset.Has((uint)1023));
			bitset.Toggle((uint)1023);
			Assert.IsFalse(bitset.Has((uint)1023));
		}

		[Test]
		public void IEnumerable01()
		{
			var bitset = new BitSet(255);
			var inputs = new[] { 0, 30, 31, 32, 33, 100, 200, 255 };
			foreach (var item in inputs)
			{
				bitset.Add(item);
			}
			var size = 0;
			foreach (int item in bitset)
			{
				size++;
			}
			Assert.AreEqual(inputs.Length, size);
		}

		[Test]
		public void IEnumerable01u()
		{
			var bitset = new BitSet((uint)255);
			var inputs = new uint[] { 0, 30, 31, 32, 33, 100, 200, 255 };
			foreach (var item in inputs)
			{
				bitset.Add(item);
			}
			var size = 0;
			foreach (var item in bitset)
			{
				size++;
			}
			Assert.AreEqual(inputs.Length, size);
		}

		[Test]
		public void Ctor01()
		{
			Assert.DoesNotThrow(() => { var bitset = new BitSet(0); });
			Assert.Throws<ArgumentOutOfRangeException>(() => { var bitset = new BitSet(-1); });
		}

		[Test]
		public void Ctor01u()
		{
			Assert.DoesNotThrow(() => { var bitset = new BitSet((uint)0); });
		}

		[Test]
		public void Count01()
		{
			var bitset = new BitSet(15);
			var inputs = new[] { 0, 1, 2, 3, 4 };
			foreach (var item in inputs)
			{
				bitset.Add(item);
				bitset.Add(item);
			}
			Assert.AreEqual(bitset.Count, 5);
			for (int i = 0; i < 2; i++)
			{
				bitset.Remove(inputs[i]);
				bitset.Remove(inputs[i]);
			}
			Assert.AreEqual(bitset.Count, 3);
			foreach (var item in inputs)
			{
				bitset.Toggle(item);
			}
			Assert.AreEqual(bitset.Count, 2);
		}

		[Test]
		public void Count01u()
		{
			var bitset = new BitSet(15);
			var inputs = new uint[] { 0, 1, 2, 3, 4 };
			foreach (var item in inputs)
			{
				bitset.Add(item);
				bitset.Add(item);
			}
			Assert.AreEqual(bitset.Count, 5);
			for (int i = 0; i < 2; i++)
			{
				bitset.Remove(inputs[i]);
				bitset.Remove(inputs[i]);
			}
			Assert.AreEqual(bitset.Count, 3);
			foreach (var item in inputs)
			{
				bitset.Toggle(item);
			}
			Assert.AreEqual(bitset.Count, 2);
		}

		[Test]
		public void Clear01()
		{
			var bitset = new BitSet(20);
			foreach (var item in new[] { 0, 1, 2, 3, 4 })
			{
				bitset.Add(item);
			}
			Assert.AreNotEqual(0, bitset.Count);
			bitset.Clear();
			Assert.AreEqual(0, bitset.Count);
		}

		[Test]
		public void FullCapacity01()
		{
			int max = 50;
			var bitset = new BitSet(max);
			for (int i = 0; i <= max; i++)
			{
				bitset.Add(i);
			}
			Assert.AreEqual(max + 1, bitset.Count);
			for (int i = 0; i <= max; i++)
			{
				Assert.IsTrue(bitset.Has(i));
			}
		}

		[Test]
		public void FullCapacity01u()
		{
			uint max = 50;
			var bitset = new BitSet(max);
			for (uint i = 0; i <= max; i++)
			{
				bitset.Add(i);
			}
			Assert.AreEqual(max + 1, bitset.Count);
			for (uint i = 0; i <= max; i++)
			{
				Assert.IsTrue(bitset.Has(i));
			}
		}

		[Test]
		public void Capacity01()
		{
			Assert.AreEqual(1, new BitSet(30).flags.Length);
			Assert.AreEqual(1, new BitSet(31).flags.Length);
			Assert.AreEqual(2, new BitSet(32).flags.Length);
			Assert.AreEqual(2, new BitSet(62).flags.Length);
			Assert.AreEqual(2, new BitSet(63).flags.Length);
			Assert.AreEqual(3, new BitSet(64).flags.Length);
		}

		[Test]
		public void Capacity01u()
		{
			Assert.AreEqual(1, new BitSet((uint)30).flags.Length);
			Assert.AreEqual(1, new BitSet((uint)31).flags.Length);
			Assert.AreEqual(2, new BitSet((uint)32).flags.Length);
			Assert.AreEqual(2, new BitSet((uint)62).flags.Length);
			Assert.AreEqual(2, new BitSet((uint)63).flags.Length);
			Assert.AreEqual(3, new BitSet((uint)64).flags.Length);
		}

		[Test]
		public void Capacity02()
		{
			Assert.AreEqual((int.MaxValue / 32) + 1, new BitSet(int.MaxValue).flags.Length);
		}

		[Test]
		public void Capacity02u()
		{
			Assert.AreEqual((uint.MaxValue / 32) + 1, new BitSet(uint.MaxValue).flags.Length);
		}

		[Test]
		public void OutOfRange01()
		{
			var bitset = new BitSet(1023);
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Has(-1));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Has(1024));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Add(-1));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Add(1024));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Remove(-1));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Remove(1024));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Toggle(-1));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Toggle(1024));
		}

		[Test]
		public void OutOfRange01u()
		{
			var bitset = new BitSet(1023);
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Has((uint)1024));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Add((uint)1024));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Remove((uint)1024));
			Assert.Throws<ArgumentOutOfRangeException>(() => bitset.Toggle((uint)1024));
		}
	}
}


<|start_filename|>src/rm.Extensions/GenericEqualityComparer.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace rm.Extensions
{
	/// <summary>
	/// Generic class that implements IEqualityComparer{T} for {TKey} key selector.
	/// </summary>
	/// <remarks>http://stackoverflow.com/questions/188120/can-i-specify-my-explicit-type-comparator-inline</remarks>
	public class GenericEqualityComparer<T, TKey> : IEqualityComparer<T>
	{
		private Func<T, TKey> projection;

		public GenericEqualityComparer(Func<T, TKey> projection)
		{
			projection.ThrowIfArgumentNull(nameof(projection));
			this.projection = projection;
		}

		#region IEqualityComparer<T> methods

		public bool Equals(T x, T y)
		{
			if (x == null && y == null)
			{
				return true;
			}
			if (x == null || y == null)
			{
				return false;
			}
			// note: projection(x).Equals(projection(y)) uses object.Equals(object), 
			// instead of TKey.Equals(Tkey), which gives incorrect results.
			return EqualityComparer<TKey>.Default.Equals(projection(x), projection(y));
		}

		public int GetHashCode(T obj)
		{
			return projection(obj).GetHashCode();
		}

		#endregion
	}

	/// <summary>
	/// Helper class to create GenericEqualityComparer{T, TKey}.
	/// </summary>
	public static class GenericEqualityComparer<T>
	{
		public static IEqualityComparer<T> By<TKey>(Func<T, TKey> projection)
		{
			return new GenericEqualityComparer<T, TKey>(projection);
		}
	}
}


<|start_filename|>src/rm.Extensions/Helper.cs<|end_filename|>
﻿using System;

namespace rm.Extensions
{
	/// <summary>
	/// Helper class.
	/// </summary>
	public static class Helper
	{
		/// <summary>
		/// Swaps parameters.
		/// </summary>
		/// <example>Helper.Swap(ref a, ref b);</example>
		public static void Swap<T>(ref T t1, ref T t2)
		{
			var temp = t1;
			t1 = t2;
			t2 = temp;
		}

		/// <summary>
		/// Swaps array elements for given indices.
		/// </summary>
		/// <example>Helper.Swap(array, i, j);</example>
		public static void Swap<T>(T[] a, int i, int j)
		{
			T t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
	}
}


<|start_filename|>tests/rm.ExtensionsTest/GraphExtensionTest.cs<|end_filename|>
﻿using System.Collections.Generic;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class GraphExtensionTest
	{
		[Test(Description = " 1->2->3 ")]
		public void IsCyclic_False_01()
		{
			var n1 = new GraphNode("1");
			var n2 = new GraphNode("2");
			var n3 = new GraphNode("3");
			n1.Neighbors = new[] { n2 };
			n2.Neighbors = new[] { n3 };
			var graph = new Graph(new[] { n1, n2, n3 });

			Assert.IsFalse(graph.IsCyclic());
		}

		[Test(Description = " 1<-2<-3 ")]
		public void IsCyclic_False_02()
		{
			var n1 = new GraphNode("1");
			var n2 = new GraphNode("2");
			var n3 = new GraphNode("3");
			n3.Neighbors = new[] { n2 };
			n2.Neighbors = new[] { n1 };
			var graph = new Graph(new[] { n1, n2, n3 });

			Assert.IsFalse(graph.IsCyclic());
		}

		[Test(Description = " 1->2->3, 1->3 ")]
		public void IsCyclic_False_03()
		{
			var n1 = new GraphNode("1");
			var n2 = new GraphNode("2");
			var n3 = new GraphNode("3");
			n1.Neighbors = new[] { n2, n3 };
			n2.Neighbors = new[] { n3 };
			var graph = new Graph(new[] { n1, n2, n3 });

			Assert.IsFalse(graph.IsCyclic());
		}

		[Test(Description = " 1<->2 ")]
		public void IsCyclic_True_01()
		{
			var n1 = new GraphNode("1");
			var n2 = new GraphNode("2");
			n1.Neighbors = new[] { n2 };
			n2.Neighbors = new[] { n1 };
			var graph = new Graph(new[] { n1, n2 });

			Assert.IsTrue(graph.IsCyclic());
		}

		[Test(Description = " 1->2->3, 3->1 ")]
		public void IsCyclic_True_02()
		{
			var n1 = new GraphNode("1");
			var n2 = new GraphNode("2");
			var n3 = new GraphNode("3");
			n1.Neighbors = new[] { n2 };
			n2.Neighbors = new[] { n3 };
			n3.Neighbors = new[] { n1 };
			var graph = new Graph(new[] { n1, n2, n3 });

			Assert.IsTrue(graph.IsCyclic());
		}

		[Test(Description = " 1->2<->3, 2->4, 4->3 ")]
		public void IsCyclic_True_03()
		{
			var n1 = new GraphNode("1");
			var n2 = new GraphNode("2");
			var n3 = new GraphNode("3");
			var n4 = new GraphNode("4");
			n1.Neighbors = new[] { n2 };
			n2.Neighbors = new[] { n3, n4 };
			n3.Neighbors = new[] { n2 };
			n4.Neighbors = new[] { n3 };
			var graph = new Graph(new[] { n1, n2, n3, n4 });

			Assert.IsTrue(graph.IsCyclic());
		}

		[Test(Description = " 1->2, 3->4, 4->3 ")]
		public void IsCyclic_True_04()
		{
			var n1 = new GraphNode("1");
			var n2 = new GraphNode("2");
			var n3 = new GraphNode("3");
			var n4 = new GraphNode("4");
			n1.Neighbors = new[] { n2 };
			n3.Neighbors = new[] { n4 };
			n4.Neighbors = new[] { n3 };
			var graph = new Graph(new[] { n1, n2, n3, n4 });

			Assert.IsTrue(graph.IsCyclic());
		}
	}

	#region Graph interfaces

	/// <summary>
	/// Graph.
	/// </summary>
	public class Graph : IGraph
	{
		#region IGraph methods
		public IEnumerable<IGraphNode> Nodes { get; private set; }
		#endregion

		/// <summary>
		/// ctor.
		/// </summary>
		public Graph(IEnumerable<IGraphNode> nodes)
		{
			this.Nodes = nodes;
		}
	}

	/// <summary>
	/// Graph node.
	/// </summary>
	public class GraphNode : IGraphNode
	{
		#region IGraphNode methods

		public string Id { get; private set; }

		public IEnumerable<IGraphNode> Neighbors { get; set; }

		#endregion

		private static readonly IEnumerable<IGraphNode> emptyGraphNodes = new GraphNode[0];

		/// <summary>
		/// ctor.
		/// </summary>
		public GraphNode(string id, IEnumerable<IGraphNode> neighbors = null)
		{
			this.Id = id;
			this.Neighbors = neighbors ?? emptyGraphNodes;
		}

		public override string ToString()
		{
			return Id;
		}
	}

	#endregion
}


<|start_filename|>src/rm.Extensions/GenericComparer.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace rm.Extensions
{
	/// <summary>
	/// Generic class that implements IComparer{T}.
	/// </summary>
	public class GenericComparer<T> : IComparer<T>
	{
		private Func<T, T, int> compare;

		public GenericComparer(Func<T, T, int> compare)
		{
			compare.ThrowIfArgumentNull(nameof(compare));
			this.compare = compare;
		}

		#region IComparer<T> methods

		public int Compare(T x, T y)
		{
			return compare(x, y);
		}

		#endregion
	}
}


<|start_filename|>tests/rm.ExtensionsTest/NameValueCollectionExtensionTest.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using NUnit.Framework;
using rm.Extensions;

namespace rm.ExtensionsTest
{
	[TestFixture]
	public class NameValueCollectionExtensionTest
	{
		private NameValueCollection GetCollection(string[] args)
		{
			if (args == null || args.Length == 0)
			{
				return new NameValueCollection();
			}
			if (args.Length % 2 != 0)
			{
				throw new ApplicationException("args length should be even.");
			}
			var collection = new NameValueCollection();
			for (int i = 0; i < args.Length; i += 2)
			{
				collection.Add(args[i], args[i + 1]);
			}
			return collection;
		}

		[Test]
		[TestCase("?k1=v1&k2=v2", "k1", "v1", "k2", "v2")]
		[TestCase("?k1=v1&k1=v1", "k1", "v1", "k1", "v1")]
		[TestCase("?k1%2C=v1%2C", "k1,", "v1,")]
		[TestCase("")]
		public void ToQueryString01(string expected, params string[] args)
		{
			var nvc = GetCollection(args);
			Assert.AreEqual(expected, nvc.ToQueryString());
			Assert.AreEqual(expected.Replace("?", ""), nvc.ToQueryString(false));
		}

		[Test]
		[TestCase(null, "v")]
		[TestCase("k", null)]
		public void ToQueryString02(params string[] args)
		{
			var nvc = GetCollection(args);
			Assert.Throws<NullReferenceException>(() =>
			{
				var query = nvc.ToQueryString();
			});
		}
	}
}


<|start_filename|>src/rm.Extensions/BitSet.cs<|end_filename|>
﻿using System;
using System.Collections;
using System.Collections.Generic;
using Ex = rm.Extensions.ExceptionHelper;

namespace rm.Extensions
{
	/// <summary>
	/// BitSet is a set that uses bit arithmatic to save space.
	/// </summary>
	/// <remarks>
	/// BitSet is space efficient compared to bool[] if the set is dense and not sparse.
	/// <para>
	/// Note: Cannot implement IEnumerable{int}.GetEnumerator() without loss of information.
	/// </para>
	/// </remarks>
	public class BitSet : IEnumerable<uint>, ICollection<uint>, IReadOnlyCollection<uint>
	{
		#region data members

		/// <summary>
		/// int array used as bit array for flags (32 bits for each int).
		/// </summary>
		/// <remarks>32, bytes: 4, binary: 11111 (+1), mask: 0x1f, power: 2^5</remarks>
		internal readonly int[] flags;

		/// <summary>
		/// The max int allowed in BitSet.
		/// </summary>
		public uint Max { get; }

		/// <summary>
		/// The number of elements in BitSet.
		/// </summary>
		public int Count { get; private set; }

		#endregion

		#region ctors

		/// <summary>
		/// Creates a BitSet for 0 to max, both inclusive.
		/// </summary>
		/// <param name="max">Max (inclusive), including 0.</param>
		public BitSet(uint max)
		{
			max.ThrowIfArgumentOutOfRange(nameof(max));
			Max = max;
			int capacity = (int)((max >> 5) + 1);
			flags = new int[capacity];
		}

		/// <summary>
		/// Creates a BitSet for 0 to max, both inclusive.
		/// </summary>
		/// <param name="max">Max (inclusive), including 0.</param>
		public BitSet(int max)
			: this((uint)(max.ThrowIfArgumentOutOfRange(nameof(max))))
		{ }

		#endregion

		#region methods

		/// <summary>
		/// Returns index and offset for <paramref name="n"/>.
		/// </summary>
		private (uint, int) GetIndexOffset(uint n)
		{
			var index = (n >> 5);
			var offset = (int)(n & 0b1_1111);
			return (index, offset);
		}

		/// <summary>
		/// Returns true if BitSet contains <paramref name="n"/>.
		/// </summary>
		public bool Has(uint n)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
			(uint index, int offset) = GetIndexOffset(n);
			return Has(index, offset);
		}

		/// <summary>
		/// Returns true if BitSet has the flags[<paramref name="index"/>]'s
		/// <paramref name="offset"/> bit set.
		/// </summary>
		private bool Has(uint index, int offset)
		{
			return ((flags[index] >> offset) & 1) == 1;
		}

		/// <summary>
		/// Returns true if BitSet contains <paramref name="n"/>.
		/// </summary>
		public bool Has(int n)
		{
			Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
			return Has((uint)n);
		}

		/// <summary>
		/// Adds <paramref name="n"/>.
		/// </summary>
		public void Add(uint n)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
			(uint index, int offset) = GetIndexOffset(n);
			if (Has(index, offset))
			{
				return;
			}
			flags[index] |= (1 << offset);
			Count++;
		}

		/// <summary>
		/// Adds <paramref name="n"/>.
		/// </summary>
		public void Add(int n)
		{
			Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
			Add((uint)n);
		}

		/// <summary>
		/// Removes <paramref name="n"/>.
		/// </summary>
		/// <returns>Returns true if removed else false.</returns>
		public bool Remove(uint n)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
			(uint index, int offset) = GetIndexOffset(n);
			if (!Has(index, offset))
			{
				return false;
			}
			flags[index] &= ~(1 << offset);
			Count--;
			return true;
		}

		/// <summary>
		/// Removes <paramref name="n"/>.
		/// </summary>
		/// <returns>Returns true if removed else false.</returns>
		public bool Remove(int n)
		{
			Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
			return Remove((uint)n);
		}

		/// <summary>
		/// Toggles <paramref name="n"/>.
		/// </summary>
		public void Toggle(uint n)
		{
			n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
			(uint index, int offset) = GetIndexOffset(n);
			flags[index] ^= (1 << offset);
			Count += Has(index, offset) ? +1 : -1;
		}

		/// <summary>
		/// Toggles <paramref name="n"/>.
		/// </summary>
		public void Toggle(int n)
		{
			Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
			Toggle((uint)n);
		}

		/// <summary>
		/// Clears the BitSet.
		/// </summary>
		/// <remarks>This is expensive.</remarks>
		public void Clear()
		{
			for (int i = 0; i < flags.Length; i++)
			{
				flags[i] = 0;
			}
			Count = 0;
		}

		#region IEnumerable<uint> methods

		public IEnumerator<uint> GetEnumerator()
		{
			var ycount = 0;
			for (uint i = 0; i <= Max; i++)
			{
				if (ycount == Count)
				{
					yield break;
				}
				if (Has(i))
				{
					yield return i;
					ycount++;
				}
			}
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		// Note: Cannot implement IEnumerable<int>.GetEnumerator() without loss of information

		#endregion

		#region ICollection<uint> methods

		public bool Contains(uint n)
		{
			return Has(n);
		}

		public bool Contains(int n)
		{
			return Has((uint)n);
		}

		public bool IsReadOnly => false;

		[Obsolete("Not implemented", true)]
		public void CopyTo(uint[] array, int arrayIndex)
		{
			throw new NotImplementedException();
		}

		#endregion

		#endregion
	}
}
