// This project is licensed under The MIT License (MIT) // // Copyright 2013 David Koontz, Logan Barnett, Corey Nolan, Alex Burley // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // Please direct questions, patches, and suggestions to the project page at // https://github.com/dkoontz/GoodStuff using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace Otter { public delegate bool Predicate(T1 item1, T2 item2); public static class IntExtensions { /// /// Calls the provided callback action repeatedly. /// /// /// Used to invoke an action a fixed number of times. /// /// 5.Times(() => Console.WriteLine("Hey!")); /// /// is the equivalent of /// /// for(var i = 0; i < 5; i++) { /// Console.WriteLine("Hey!"); /// } /// public static void Times(this int iterations, Action callback) { for (var i = 0; i < iterations; ++i) { callback(); } } /// /// Calls the provided callback action repeatedly passing in the current value of i /// /// /// Used to invoke an action a fixed number of times. /// /// 5.Times(i => Console.WriteLine("Hey # " + i)); /// /// is the equivalent of /// /// for(var i = 0; i < 5; i++) { /// Console.WriteLine("Hey # " + i); /// } /// public static void Times(this int iterations, Action callback) { for (var i = 0; i < iterations; ++i) { callback(i); } } /// /// Iterates from the start up to the given end value inclusive, calling the provided callback with each value in the sequence. /// /// /// Used to iterate from a start value to a target value /// /// 0.UpTo(5, i => Console.WriteLine(i)); /// /// is the equivalent of /// /// for(var i = 0; i <= 5; i++) { /// Console.WriteLine(i); /// } /// public static void UpTo(this int value, int endValue, Action callback) { for (var i = value; i <= endValue; ++i) { callback(i); } } /// /// Iterates from the start down to the given end value inclusive, calling the provided callback with each value in the sequence. /// /// /// Used to iterate from a start value to a target value /// /// 5.DownTo(0, i => Console.WriteLine(i)); /// /// is the equivalent of /// /// for(var i = 5; i >= 0; i++) { /// Console.WriteLine(i); /// } /// public static void DownTo(this int value, int endValue, Action callback) { for (var i = value; i >= endValue; --i) { callback(i); } } public static bool IsEven(this int value) { return value % 2 == 0; } public static bool IsOdd(this int value) { return value % 2 == 1; } } public static class FloatExtensions { /// /// Maps a value in one range to the equivalent value in another range. /// public static float MapToRange(this float value, float range1Min, float range1Max, float range2Min, float range2Max) { return MapToRange(value, range1Min, range1Max, range2Min, range2Max, true); } /// /// Maps a value in one range to the equivalent value in another range. Clamps the value to be valid within the range if clamp is specified as true. /// public static float MapToRange(this float value, float range1Min, float range1Max, float range2Min, float range2Max, bool clamp) { value = range2Min + ((value - range1Min) / (range1Max - range1Min)) * (range2Max - range2Min); if (clamp) { if (range2Min < range2Max) { if (value > range2Max) value = range2Max; if (value < range2Min) value = range2Min; } // Range that go negative are possible, for example from 0 to -1 else { if (value > range2Min) value = range2Min; if (value < range2Max) value = range2Max; } } return value; } /// /// Converts a float into a percent value (1 => 100%) /// /// /// public static int ToPercent(this float value) { return Convert.ToInt32(value * 100); } } public static class IEnumerableExtensions { /// /// Iterates over each element in the IEnumerable, passing in the element to the provided callback. /// public static void Each(this IEnumerable iterable, Action callback) { foreach (var value in iterable) { callback(value); } } /// /// Iterates over each element backwards in the IEnumerable, passing in the element to the provided callback. /// /// /// /// public static void EachReverse(this IEnumerable iterable, Action callback) { for (var i = iterable.Count() - 1; i >= 0; i--) { callback(iterable.ElementAt(i)); } } /// /// Iterates over each element in the IEnumerable, passing in the element to the provided callback. Since the IEnumerable is /// not generic, a type must be specified as a type parameter to Each. /// /// /// IEnumerable myCollection = new List(); /// ... /// myCollection.Each(i => Debug.Log("i: " + i)); /// public static void Each(this IEnumerable iterable, Action callback) { foreach (T value in iterable) { callback(value); } } // /// // /// Iterates over each element in the IEnumerable, passing in the element to the provided callback. // /// // public static void Each(this IEnumerable iterable, Action callback) { // foreach(object value in iterable) { // callback(value); // } // } /// /// Iterates over each element in the IEnumerable, passing in the element and the index to the provided callback. /// public static void EachWithIndex(this IEnumerable iterable, Action callback) { var i = 0; foreach (var value in iterable) { callback(value, i); ++i; } } /// /// Iterates over each element in the IEnumerable, passing in the element and the index to the provided callback. /// public static void EachWithIndex(this IEnumerable iterable, Action callback) { var i = 0; foreach (T value in iterable) { callback(value, i); ++i; } } /// /// Iterates over each element in the two dimensional array, passing in the index to the provided callback. /// public static void EachIndex(this IEnumerable iterable, Action callback) { var i = 0; #pragma warning disable 0168 foreach (var value in iterable) { #pragma warning restore 0168 callback(i); ++i; } } /// /// Iterates over each element in the two dimensional array, passing in the index to the provided callback. /// public static void EachIndex(this IEnumerable iterable, Action callback) { var i = 0; #pragma warning disable 0219 foreach (T value in iterable) { #pragma warning restore 0219 callback(i); ++i; } } /// /// Iterates over each element in both the iterable1 and iterable2 collections, passing in the current element of each collection into the provided callback. /// public static void InParallelWith(this IEnumerable iterable1, IEnumerable iterable2, Action callback) { if (iterable1.Count() != iterable2.Count()) throw new ArgumentException(string.Format("Both IEnumerables must be the same length, iterable1: {0}, iterable2: {1}", iterable1.Count(), iterable2.Count())); var i1Enumerator = iterable1.GetEnumerator(); var i2Enumerator = iterable2.GetEnumerator(); while (i1Enumerator.MoveNext()) { i2Enumerator.MoveNext(); callback(i1Enumerator.Current, i2Enumerator.Current); } } /// /// Iterates over each element in both the iterable1 and iterable2 collections, passing in the current element of each collection into the provided callback. /// public static void InParallelWith(this IEnumerable iterable1, IEnumerable iterable2, Action callback) { var i1Enumerator = iterable1.GetEnumerator(); var i2Enumerator = iterable2.GetEnumerator(); var i1Count = 0; var i2Count = 0; while (i1Enumerator.MoveNext()) ++i1Count; while (i2Enumerator.MoveNext()) ++i2Count; if (i1Count != i2Count) throw new ArgumentException(string.Format("Both IEnumerables must be the same length, iterable1: {0}, iterable2: {1}", i1Count, i2Count)); i1Enumerator.Reset(); i2Enumerator.Reset(); while (i1Enumerator.MoveNext()) { i2Enumerator.MoveNext(); callback(i1Enumerator.Current, i2Enumerator.Current); } } public static bool IsEmpty(this IEnumerable iterable) { return iterable.Count() == 0; } public static bool IsEmpty(this IEnumerable iterable) { // MoveNext returns false if we are at the end of the collection return !iterable.GetEnumerator().MoveNext(); } public static bool IsNotEmpty(this IEnumerable iterable) { return iterable.Count() > 0; } public static bool IsNotEmpty(this IEnumerable iterable) { // MoveNext returns false if we are at the end of the collection return iterable.GetEnumerator().MoveNext(); } /// /// Matches all elements where the given condition is not true. This is the /// opposite of Linq's Where clause. /// public static IEnumerable ExceptWhere(this IEnumerable iterable, Func condition) { return iterable.Where(element => !condition(element)); } #region MoreLINQ project code // MinBy and MoreBy methods are provided via the MoreLINQ project (c) Jon Skeet // https://code.google.com/p/morelinq/source/browse/MoreLinq/MinBy.cs // https://code.google.com/p/morelinq/source/browse/MoreLinq/MaxBy.cs /// /// Returns the first element that has the smallest value (as determined by the selector) within the collection /// (as determined by the comparer). This is equivalent to using Min except that the element itself /// is returned, and not the value used to make the Min determination. /// public static TSource MinBy(this IEnumerable source, Func selector) { return source.MinBy(selector, Comparer.Default); } /// /// Returns the first element that has the smallest value (as determined by the selector) within the collection /// (as determined by the comparer). This is equivalent to using Min except that the element itself /// is returned, and not the value used to make the Min determination. /// public static TSource MinBy(this IEnumerable source, Func selector, IComparer comparer) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); if (comparer == null) throw new ArgumentNullException("comparer"); using (var sourceIterator = source.GetEnumerator()) { if (!sourceIterator.MoveNext()) { throw new InvalidOperationException("Sequence contains no elements"); } var minValue = sourceIterator.Current; var minKey = selector(minValue); while (sourceIterator.MoveNext()) { var candidate = sourceIterator.Current; var candidateProjected = selector(candidate); if (comparer.Compare(candidateProjected, minKey) < 0) { minValue = candidate; minKey = candidateProjected; } } return minValue; } } /// /// Returns the first element that has the largest value (as determined by the selector) within the collection /// (as determined by the comparer). This is equivalent to using Max except that the element itself /// is returned, and not the value used to make the Max determination. /// public static TSource MaxBy(this IEnumerable source, Func selector) { return source.MaxBy(selector, Comparer.Default); } /// /// Returns the first element that has the largest value (as determined by the selector) within the collection /// (as determined by the comparer). This is equivalent to using Max except that the element itself /// is returned, and not the value used to make the Max determination. /// public static TSource MaxBy(this IEnumerable source, Func selector, IComparer comparer) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); if (comparer == null) throw new ArgumentNullException("comparer"); using (var sourceIterator = source.GetEnumerator()) { if (!sourceIterator.MoveNext()) { throw new InvalidOperationException("Sequence contains no elements"); } var maxValue = sourceIterator.Current; var maxKey = selector(maxValue); while (sourceIterator.MoveNext()) { var candidate = sourceIterator.Current; var candidateProjected = selector(candidate); if (comparer.Compare(candidateProjected, maxKey) > 0) { maxValue = candidate; maxKey = candidateProjected; } } return maxValue; } } #endregion } public static class ArrayExtensions { [ThreadStatic] static System.Random randomNumberGenerator = new Random(DateTime.Now.Millisecond + System.Threading.Thread.CurrentThread.GetHashCode()); /// /// Returns the first index in the array where the target exists. If the target cannot be found, returns -1. /// public static int IndexOf(this T[] array, T target) { for (var i = 0; i < array.Length; ++i) { if (array[i].Equals(target)) return i; } return -1; } /// /// Returns a sub-section of the current array, starting at the specified index and continuing to the end of the array. /// public static T[] FromIndexToEnd(this T[] array, int start) { var subSection = new T[array.Length - start]; array.CopyTo(subSection, start); return subSection; } /// /// Wrapper for System.Array.FindIndex to allow it to be called directly on an array. /// public static int FindIndex(this T[] array, Predicate match) { return Array.FindIndex(array, match); } /// /// Wrapper for System.Array.FindIndex to allow it to be called directly on an array. /// public static int FindIndex(this T[] array, int startIndex, Predicate match) { return Array.FindIndex(array, startIndex, match); } /// /// Wrapper for System.Array.FindIndex to allow it to be called directly on an array. /// public static int FindIndex(this T[] array, int startIndex, int count, Predicate match) { return Array.FindIndex(array, startIndex, count, match); } /// Returns a randomly selected item from the array public static T RandomElement(this T[] array) { if (array.Length == 0) throw new IndexOutOfRangeException("Cannot retrieve a random value from an empty array"); return array[randomNumberGenerator.Next(array.Length)]; } /// Returns a randomly selected item from the array determined by a float array of weights public static T RandomElement(this T[] array, float[] weights) { return array.RandomElement(weights.ToList()); } /// Returns a randomly selected item from the array determined by a List of weights public static T RandomElement(this T[] array, List weights) { if (array.IsEmpty()) throw new IndexOutOfRangeException("Cannot retrieve a random value from an empty array"); if (array.Count() != weights.Count()) throw new IndexOutOfRangeException("array of weights must be the same size as input array"); var randomWeight = randomNumberGenerator.NextDouble() * weights.Sum(); var totalWeight = 0f; var index = weights.FindIndex(weight => { totalWeight += weight; return randomWeight <= totalWeight; }); return array[index]; } /// /// Iterates over each element in the two dimensional array, passing in the element and the index to the provided callback. /// public static void EachWithIndex(this T[,] collection, Action callback) { for (var x = 0; x < collection.GetLength(0); ++x) { for (var y = 0; y < collection.GetLength(1); ++y) { callback(collection[x, y], x, y); } } } /// /// Iterates over each element in the two dimensional array, passing in the index to the provided callback. /// public static void EachIndex(this T[,] collection, Action callback) { for (var x = 0; x < collection.GetLength(0); ++x) { for (var y = 0; y < collection.GetLength(1); ++y) { callback(x, y); } } } } public static class ListExtensions { [ThreadStatic] static System.Random randomNumberGenerator = new Random(DateTime.Now.Millisecond + System.Threading.Thread.CurrentThread.GetHashCode()); /// /// Returns a sub-section of the current list, starting at the specified index and continuing to the end of the list. /// public static List FromIndexToEnd(this List list, int start) { return list.GetRange(start, list.Count - start); } /// /// Returns the first index in the IList where the target exists. If the target cannot be found, returns -1. /// public static int IndexOf(this IList list, T target) { for (var i = 0; i < list.Count; ++i) { if (list[i].Equals(target)) return i; } return -1; } /// Returns a randomly selected item from IList public static T RandomElement(this IList list) { if (list.IsEmpty()) throw new IndexOutOfRangeException("Cannot retrieve a random value from an empty list"); return list[Rand.Int(list.Count)]; // Using Otter's RNG for consistency //return list[randomNumberGenerator.Next(list.Count)]; } /// /// Returns a randomly selected item from IList or default. /// /// /// /// public static T RandomElementOrDefault(this IList list) { if (list.IsEmpty()) return default(T); return RandomElement(list); } /// Returns a randomly selected item from IList determined by a IEnumerable of weights public static T RandomElement(this IList list, IEnumerable weights) { if (list.IsEmpty()) throw new IndexOutOfRangeException("Cannot retrieve a random value from an empty list"); if (list.Count != weights.Count()) throw new IndexOutOfRangeException("List of weights must be the same size as input list"); var randomWeight = randomNumberGenerator.NextDouble() * weights.Sum(); var totalWeight = 0f; var index = 0; foreach (var weight in weights) { totalWeight += weight; if (randomWeight <= totalWeight) { break; } } return list[index]; } public static IList Shuffle(this IList list) { // OrderBy and Sort are both broken for AOT compliation on older MonoTouch versions // https://bugzilla.xamarin.com/show_bug.cgi?id=2155#c11 var shuffledList = new List(list); T temp; for (var i = 0; i < shuffledList.Count; ++i) { temp = shuffledList[i]; var swapIndex = randomNumberGenerator.Next(list.Count); shuffledList[i] = shuffledList[swapIndex]; shuffledList[swapIndex] = temp; } return shuffledList; } public static IList InPlaceShuffle(this IList list) { // OrderBy and Sort are both broken for AOT compliation on older MonoTouch versions // https://bugzilla.xamarin.com/show_bug.cgi?id=2155#c11 for (var i = 0; i < list.Count; ++i) { var temp = list[i]; var swapIndex = randomNumberGenerator.Next(list.Count); list[i] = list[swapIndex]; list[swapIndex] = temp; } return list; } public static IList InPlaceOrderBy(this IList list, Func elementToSortValue) where TKey : IComparable { // Provides both and in-place sort as well as an AOT on iOS friendly replacement for OrderBy if (list.Count < 2) { return list; } int startIndex; int currentIndex; int smallestIndex; T temp; for (startIndex = 0; startIndex < list.Count; ++startIndex) { smallestIndex = startIndex; for (currentIndex = startIndex + 1; currentIndex < list.Count; ++currentIndex) { if (elementToSortValue(list[currentIndex]).CompareTo(elementToSortValue(list[smallestIndex])) < 0) { smallestIndex = currentIndex; } } temp = list[startIndex]; list[startIndex] = list[smallestIndex]; list[smallestIndex] = temp; } return list; } /// /// Attempts to Insert the item, but Adds it if the index is invalid. /// public static void InsertOrAdd(this IList list, int atIndex, T item) { if (atIndex >= 0 && atIndex < list.Count) { list.Insert(atIndex, item); } else { list.Add(item); } } /// /// Returns the element after the given element. This can wrap. If the element is the only one in the list, itself is returned. /// public static T ElementAfter(this IList list, T element, bool wrap = true) { var targetIndex = list.IndexOf(element) + 1; if (wrap) { return targetIndex >= list.Count ? list[0] : list[targetIndex]; } return list[targetIndex]; } /// /// Returns the element before the given element. This can wrap. If the element is the only one in the list, itself is returned. /// public static T ElementBefore(this IList list, T element, bool wrap = true) { var targetIndex = list.IndexOf(element) - 1; if (wrap) { return targetIndex < 0 ? list[list.Count - 1] : list[targetIndex]; } return list[targetIndex]; } } public static class DictionaryExtensions { /// /// Iterates over a Dictionary passing in both the key and value to the provided callback. /// public static void Each(this Dictionary dictionary, Action callback) { foreach (var keyValuePair in dictionary) { callback(keyValuePair.Key, keyValuePair.Value); } } /// /// Iterates over a Dictionary passing in both the key and value to the provided callback. /// public static void EachWithIndex(this Dictionary dictionary, Action callback) { var i = 0; foreach (var keyValuePair in dictionary) { callback(keyValuePair.Key, keyValuePair.Value, i++); } } public static void RemoveAll(this Dictionary dictionary, Predicate callback) { var keysToRemove = new List(); foreach (var keyValuePair in dictionary) { if (callback(keyValuePair.Key, keyValuePair.Value)) { keysToRemove.Add(keyValuePair.Key); } } foreach (var key in keysToRemove) { dictionary.Remove(key); } } } public static class StringExtensions { /// /// Interpolates the arguments into the string using string.Format /// /// The string to be interpolated into /// The values to be interpolated into the string public static string Interpolate(this string formatString, params object[] args) { return string.Format(formatString, args); } /// /// Alias for for the typing averse /// /// The string to be interpolated into /// The values to be interpolated into the string public static string Fmt(this string formatString, params object[] args) { return Interpolate(formatString, args); } public static T ToEnum(this string enumValueName) { return (T)Enum.Parse(typeof(T), enumValueName); } public static T ToEnum(this string enumValueName, bool ignoreCase) { return (T)Enum.Parse(typeof(T), enumValueName, ignoreCase); } public static string Last(this string value, int count) { if (count > value.Length) throw new ArgumentOutOfRangeException(string.Format("Cannot return more characters than exist in the string (wanted {0} string contains {1}", count, value.Length)); return value.Substring(value.Length - count, count); } public static string SnakeCase(this string camelizedString) { var parts = new List(); var currentWord = new StringBuilder(); foreach (var c in camelizedString) { if (char.IsUpper(c) && currentWord.Length > 0) { parts.Add(currentWord.ToString()); currentWord = new StringBuilder(); } currentWord.Append(char.ToLower(c)); } if (currentWord.Length > 0) { parts.Add(currentWord.ToString()); } return string.Join("_", parts.ToArray()); } public static string Capitalize(this string word) { return word.Substring(0, 1).ToUpper() + word.Substring(1); } } public static class TypeExtensions { /// /// Returns an array of all concrete subclasses of the provided type. /// public static Type[] Subclasses(this Type type) { var typeList = new List(); System.AppDomain.CurrentDomain.GetAssemblies().Each(a => typeList.AddRange(a.GetTypes())); return typeList.Where(t => t.IsSubclassOf(type) && !t.IsAbstract).ToArray(); } /// /// Returns an array of the provided type and all concrete subclasses of that type. /// public static Type[] TypeAndSubclasses(this Type type) { var typeList = new List(); System.AppDomain.CurrentDomain.GetAssemblies().Each(a => typeList.AddRange(a.GetTypes())); return typeList.Where(t => (t == type || t.IsSubclassOf(type)) && !t.IsAbstract).ToArray(); } } public static class EnumExtensions { /// /// Returns the next enum value wrapping to the first value if passed the last /// public static T Next(this Enum enumValue) { var values = Enum.GetValues(enumValue.GetType()); var enumerator = values.GetEnumerator(); while (enumerator.MoveNext()) { if (enumerator.Current.Equals(enumValue)) { if (enumerator.MoveNext()) { return (T)enumerator.Current; } } } return default(T); } } }