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;
}
}
}
}