using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Chernobyl.Utility; using JetBrains.Annotations; using Microsoft.FSharp.Collections; namespace Chernobyl.Collections.Generic { /// /// Utility and extension methods for and its /// dependencies. /// public static class Enumerable { /// /// Returns the zero based index location of /// within . This method uses /// for comparison. /// /// The type contained within the /// . /// The instance to loop over to determine the index /// where is located true. /// The instance to search for. /// The index where is located or -1 /// if was not located. public static int IndexOf(this IEnumerable source, T value) { return source.IndexOf(value, null); } /// /// Returns the zero based index location of /// within . /// /// The type contained within the /// . /// The instance to loop over to determine the index /// where is located true. /// The instance to search for. /// The instance used to perform the comparison /// or null if should be used. /// The index where is located or -1 /// if was not located. public static int IndexOf(this IEnumerable source, T value, IEqualityComparer comparer) { comparer = comparer ?? EqualityComparer.Default; return source.IndexWhere(item => comparer.Equals(item, value)); } /// /// Returns the zero based index where the provided /// returns true. /// /// The type contained within the /// . /// The instance to loop over to determine the index /// where returns true. /// The predicate use to determine the index /// returned. /// The index where is true or -1 /// if never returned true. public static int IndexWhere(this IEnumerable source, Predicate predicate) { int index = 0; foreach (T item in source) { if (predicate(item)) return index; ++index; } return -1; } /// /// Returns true if is empty, false if /// otherwise. /// /// The instance to determine if empty. /// True if is empty, false if /// otherwise. public static bool IsEmpty(this IEnumerable enumerable) { return enumerable.GetEnumerator().MoveNext() == false; } /// /// Returns true if is null or empty, false if /// otherwise. /// /// The instance to determine if null or empty. /// True if is null or empty, false if /// otherwise. [ContractAnnotation("enumerable:null => false")] public static bool IsEmptyOrNull(this IEnumerable enumerable) { return enumerable == null || enumerable.IsEmpty(); } /// /// Returns the elements of the specified sequence or the specified /// value in a singleton collection if the sequence is empty. /// /// The type of the elements of source. /// The sequence to return the specified value /// for if it is empty. /// The method that is to create the default value /// if the sequence is empty. /// An that contains the instance /// created by if source is empty; otherwise, /// source. public static IEnumerable DefaultIfEmpty(this IEnumerable enumerable, Func creator) { // ReSharper disable PossibleMultipleEnumeration return enumerable.IsEmpty() ? creator().ToEnumerable() : enumerable; // ReSharper restore PossibleMultipleEnumeration } /// /// Applies a specified function to the corresponding elements of two /// sequences, producing a sequence of the results. /// /// The type of the elements of the first input /// sequence. /// The type of the elements of the second input /// sequence. /// The first input sequence. /// The second input sequence. /// An that contains elements of /// the two input sequences, combined using . public static IEnumerable> Zip(this IEnumerable first, IEnumerable second) { return first.Zip(second, (arg1, arg2) => new Pair(arg1, arg2)); } /// /// Applies a specified function to the corresponding elements of two /// sequences, producing a sequence of the results. /// /// The type of the elements of the first input /// sequence. /// The type of the elements of the second input /// sequence. /// The type of the elements of the result /// sequence. /// The first input sequence. /// The second input sequence. /// A function that specifies how to /// combine the corresponding elements of the two sequences. /// An that contains elements of /// the two input sequences, combined by . public static IEnumerable Zip(this IEnumerable first, IEnumerable second, Func resultSelector) { // ReSharper disable PossibleMultipleEnumeration first.ThrowIfNull("first"); second.ThrowIfNull("second"); resultSelector.ThrowIfNull("factory"); using (var item1 = first.GetEnumerator()) using (var item2 = second.GetEnumerator()) while (item1.MoveNext() && item2.MoveNext()) yield return resultSelector(item1.Current, item2.Current); // ReSharper restore PossibleMultipleEnumeration } /// /// Returns a never ending filled with values from . /// Use on the returned /// instance to limit the size. /// /// The type returned by . /// The method that will create the values. public static IEnumerable From(Func func) { while (true) yield return func(); } /// /// Returns a never ending filled with values from . /// Use on the returned /// instance to limit the size. /// /// The type returned by . /// The method that takes the current index and returns result values. public static IEnumerable From(Func func) { for (var i = 0;; i++) yield return func(i); } /// /// Converts an into a method where each invocation of the /// method returns the next value from the . /// /// The instance to pull the values from. /// The value that will be returned if nothing is left in the /// . public static Func AsFunc(this IEnumerator enumerator, T fillerValue = default(T)) { return () => enumerator.MoveNext() ? enumerator.Current : fillerValue; } /// /// Passes each item in to . /// /// The type of the item in . /// The items that are to be provided to /// . /// The method to invoke with each item from /// . public static void For(this IEnumerable enumerable, Action callback) { foreach (var item in enumerable) callback(item); // Note: we don't return 'enumerable' here because we don't want users thinking that this // method executes when the IEnumerable is looped through (such as when invoking ToArray). } /// /// Passes each item in to along /// with the index of that item. /// /// The type of the item in . /// The items that are to be provided to /// . /// The method to invoke with each item from /// and the index of that item. public static void For(this IEnumerable enumerable, Action callback) { int index = 0; foreach (var item in enumerable) { callback(item, index); index++; } // Note: we don't return 'enumerable' here because we don't want users thinking that this // method executes when the IEnumerable is looped through (such as when invoking ToArray). } /// /// Executes on each item that gets iterated on when iterating /// over . /// public static IEnumerable OnIterate(this IEnumerable enumerable, Action callback) { return enumerable.Select(t => { callback(t); return t; }); } /// /// Returns the values that have duplicates in . /// /// The type of the item in . /// The enumerable to look in for duplicated /// items. /// The values that have duplicates in . public static IEnumerable Duplicated(this IEnumerable enumerable) { return enumerable.GroupBy(x => x) .Where(group => @group.Count() > 1) .Select(group => @group.Key); } /// /// Copies the items from the into the /// until either there is nothing left to copy from or /// has been filled completely. /// /// The type of the destination and source arrays. /// The instance to copy the items from. /// The instance to copy the items to. /// The location from the start of destination to begin /// placing items. /// Thrown if the /// is greater than or equal to the length of . public static void Fill(this IEnumerable source, T[] destination, int destinationOffset = 0) { // NOTE: You will notice that there is no generic version of this code, i.e. where source // is IEnumerable and destination is IList. This is because the non-generic version of // this method cannot handle the case where the destination array has been casted to // another array type (such as a byte array that has been casted to a float array) while // the generic version can. You'll get an ArgumentException from the non-generic version // in this case, though the non-generic version still works for other cases. destinationOffset.Throw(nameof(destinationOffset)) .IfGreaterThanOrEqualTo(destination.Length, $"{nameof(destination)}.Length"); var copyCount = destination.Length - destinationOffset; using (var sourceEnumerator = source.GetEnumerator()) { for (int i = 0; i < copyCount && sourceEnumerator.MoveNext(); i++) { destination[destinationOffset] = sourceEnumerator.Current; destinationOffset++; } } } /// /// Determines whether the specified /// contains entirely unique items. This method was taken from: /// http://stackoverflow.com/questions/5391264/best-way-to-find-out-if-ienumerable-has-unique-values /// /// The type of the item contained within the /// . /// The to test for /// unique values. /// True if the specified is unique; /// otherwise, false. public static bool IsUnique(this IEnumerable values) { return IsUnique(values, null); } /// /// Determines whether the specified /// contains entirely unique items. This method was taken from: /// http://stackoverflow.com/questions/5391264/best-way-to-find-out-if-ienumerable-has-unique-values /// and modified to use an . /// /// The type of the item contained within the /// . /// The to test for /// unique values. /// The /// implementation to use when comparing values in the set, or null to /// use the default implementation /// for the set type. /// True if the specified is unique; /// otherwise, false. public static bool IsUnique(this IEnumerable values, IEqualityComparer comparer) { HashSet hs = new HashSet(comparer); return values.All(hs.Add); } /// /// Returns a single random item from the /// specified. /// /// The type of the items contained within the /// . /// The set of values to look in for the random /// item. public static T Random(this IEnumerable values) => values.Random(1, false).First(); /// /// Returns a single random item from the /// specified. /// /// The type of the items contained within the /// . /// The set of values to look in for the random /// item. /// The number generator /// to use when finding random items. public static T Random(this IEnumerable values, Random random) => values.Random(1, false, random).First(); /// /// Returns a set of random values from the /// specified. /// /// The type of the items contained within the /// . /// The set of values to look in for the random /// items. /// The number of random items to return. If /// is true then this method will /// clamp this amount to the number of items in . /// True if duplicate random items can be /// returned, false if otherwise. /// The items that were randomly selected from . public static IEnumerable Random(this IEnumerable values, uint count, bool allowDuplicates) => values.Random(count, allowDuplicates, new Random()); /// /// Returns a set of random values from the /// specified. /// /// The type of the items contained within the /// . /// The set of values to look in for the random /// items. /// The number of random items to return. If /// is true then this method will /// clamp this amount to the number of items in . /// True if duplicate random items can be /// returned, false if otherwise. /// The number generator /// to use when finding random items. /// The items that were randomly selected from . public static IEnumerable Random(this IEnumerable values, uint count, bool allowDuplicates, Random random) { if (values.Any() == false) throw new ArgumentOutOfRangeException("values", values, "IEnumerable must contain items to choose from."); List temporary = new List(values); // If no duplicates are allowed, then the maximum we can return is // all of the values randomized in a different order. if (allowDuplicates == false) count = (uint)Math.Min((int)count, temporary.Count); for (uint i = 0; i < count; ++i) { int randomItemIndex = random.Next(temporary.Count); yield return temporary[randomItemIndex]; if (allowDuplicates == false) temporary.RemoveAt(randomItemIndex); } } /// /// Groups the provided into multiple sets whose sizes are /// determined by except the last group which may contain less than /// the count if has fewer than needed for all groups. /// /// The to break into groups. /// The method that dictates the size of each group. public static IEnumerable> Batch(this IEnumerable values, Func count) { return values.BatchSelect(() => (count(), 0), (batch, size) => batch); } /// /// Groups the provided into multiple sets whose sizes are /// determined by except the last group which may contain less than /// the count if has fewer than needed for all groups. The sets /// are then passed through the selector for conversion, if necessary. /// /// The to break into groups. /// The method that dictates the size of each group. /// The method that converts the provided batch and its requested /// size into the result. public static IEnumerable BatchSelect( this IEnumerable values, Func<(int, TCount)> count, Func, (int, TCount), TResult> selector) { // ReSharper disable PossibleMultipleEnumeration while (values.Any()) { var size = count(); var result = values.Take(size.Item1); values = values.Skip(size.Item1); yield return selector(result, size); } // ReSharper restore PossibleMultipleEnumeration } /// /// Returns an that returns and caches the content of /// . will only be enumerated once, and /// only as far as necessary. /// For more info see F# Seq.Cache method: /// https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/seq.cache%5B%27t%5D-function-%5Bfsharp%5D /// /// The whose contents are to be cached as /// the result of this method is enumerated. public static IEnumerable Cache(this IEnumerable source) { return SeqModule.Cache(source); } /// /// Returns an that has placed in /// between each item in . /// /// The collection that is to have the placed /// between each item. /// The value to go between each item in . public static IEnumerable Intersperse(this IEnumerable source, T value) { // Implementation was taken from https://stackoverflow.com/a/753329. bool first = true; foreach (T item in source) { if (!first) yield return value; yield return item; first = false; } } } }