// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core; namespace Umbraco.Extensions; /// /// Extensions for enumerable sources /// public static class EnumerableExtensions { public static bool IsCollectionEmpty(this IReadOnlyCollection? list) => list == null || list.Count == 0; /// /// Wraps this object instance into an IEnumerable{T} consisting of a single item. /// /// Type of the object. /// The instance that will be wrapped. /// An IEnumerable{T} consisting of a single item. public static IEnumerable Yield(this T item) { // see EnumeratorBenchmarks - this is faster, and allocates less, than returning an array yield return item; } internal static bool HasDuplicates(this IEnumerable items, bool includeNull) { var hs = new HashSet(); foreach (T item in items) { if ((item != null || includeNull) && !hs.Add(item)) { return true; } } return false; } public static IEnumerable> InGroupsOf(this IEnumerable? source, int groupSize) { if (source == null) { throw new ArgumentNullException("source"); } if (groupSize <= 0) { throw new ArgumentException("Must be greater than zero.", "groupSize"); } // following code derived from MoreLinq and does not allocate bazillions of tuples T[]? temp = null; var count = 0; foreach (T item in source) { if (temp == null) { temp = new T[groupSize]; } temp[count++] = item; if (count != groupSize) { continue; } yield return temp /*.Select(x => x)*/; temp = null; count = 0; } if (temp != null && count > 0) { yield return temp.Take(count); } } public static IEnumerable SelectByGroups( this IEnumerable source, Func, IEnumerable> selector, int groupSize) { // don't want to use a SelectMany(x => x) here - isn't this better? // ReSharper disable once LoopCanBeConvertedToQuery foreach (IEnumerable resultGroup in source.InGroupsOf(groupSize).Select(selector)) { foreach (TResult result in resultGroup) { yield return result; } } } /// /// Returns a sequence of length whose elements are the result of invoking /// . /// /// /// The factory. /// The count. /// public static IEnumerable Range(Func factory, int count) { for (var i = 1; i <= count; i++) { yield return factory.Invoke(i - 1); } } /// The if not null. /// The items. /// The action. /// The type public static void IfNotNull(this IEnumerable items, Action action) where TItem : class { if (items != null) { foreach (TItem item in items) { item.IfNotNull(action); } } } /// /// Returns true if all items in the other collection exist in this collection /// /// /// /// /// public static bool ContainsAll(this IEnumerable source, IEnumerable other) { if (source == null) { throw new ArgumentNullException("source"); } if (other == null) { throw new ArgumentNullException("other"); } return other.Except(source).Any() == false; } /// /// Returns true if the source contains any of the items in the other list /// /// /// /// /// public static bool ContainsAny(this IEnumerable source, IEnumerable other) => other.Any(source.Contains); /// /// Removes all matching items from an . /// /// /// The list. /// The predicate. /// public static void RemoveAll(this IList list, Func predicate) { for (var i = 0; i < list.Count; i++) { if (predicate(list[i])) { list.RemoveAt(i--); } } } /// /// Removes all matching items from an . /// /// /// The list. /// The predicate. /// public static void RemoveAll(this ICollection list, Func predicate) { T[] matches = list.Where(predicate).ToArray(); foreach (T match in matches) { list.Remove(match); } } public static IEnumerable SelectRecursive( this IEnumerable source, Func> recursiveSelector, int maxRecusionDepth = 100) { var stack = new Stack>(); stack.Push(source.GetEnumerator()); try { while (stack.Count > 0) { if (stack.Count > maxRecusionDepth) { throw new InvalidOperationException("Maximum recursion depth reached of " + maxRecusionDepth); } if (stack.Peek().MoveNext()) { TSource current = stack.Peek().Current; yield return current; stack.Push(recursiveSelector(current).GetEnumerator()); } else { stack.Pop().Dispose(); } } } finally { while (stack.Count > 0) { stack.Pop().Dispose(); } } } /// /// Filters a sequence of values to ignore those which are null. /// /// /// The coll. /// /// public static IEnumerable WhereNotNull(this IEnumerable coll) where T : class => coll.Where(x => x != null)!; public static IEnumerable ForAllThatAre( this IEnumerable sequence, Action projection) where TActual : class => sequence.Select( x => { if (x is TActual casted) { projection.Invoke(casted); } return x; }); /// /// Finds the index of the first item matching an expression in an enumerable. /// /// The type of the enumerated objects. /// The enumerable to search. /// The expression to test the items against. /// The index of the first matching item, or -1. public static int FindIndex(this IEnumerable items, Func predicate) => FindIndex(items, 0, predicate); /// /// Finds the index of the first item matching an expression in an enumerable. /// /// The type of the enumerated objects. /// The enumerable to search. /// The index to start at. /// The expression to test the items against. /// The index of the first matching item, or -1. public static int FindIndex(this IEnumerable items, int startIndex, Func predicate) { if (items == null) { throw new ArgumentNullException("items"); } if (predicate == null) { throw new ArgumentNullException("predicate"); } if (startIndex < 0) { throw new ArgumentOutOfRangeException("startIndex"); } var index = startIndex; if (index > 0) { items = items.Skip(index); } foreach (T item in items) { if (predicate(item)) { return index; } index++; } return -1; } /// Finds the index of the first occurrence of an item in an enumerable. /// The enumerable to search. /// The item to find. /// The index of the first matching item, or -1 if the item was not found. public static int IndexOf(this IEnumerable items, T item) => items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); /// /// Determines if 2 lists have equal elements within them regardless of how they are sorted /// /// /// /// /// /// /// The logic for this is taken from: /// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies /// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon /// public static bool UnsortedSequenceEqual(this IEnumerable? source, IEnumerable? other) { if (source == null && other == null) { return true; } if (source == null || other == null) { return false; } ILookup list1Groups = source.ToLookup(i => i); ILookup list2Groups = other.ToLookup(i => i); return list1Groups.Count == list2Groups.Count && list1Groups.All(g => g.Count() == list2Groups[g.Key].Count()); } /// /// Transforms an enumerable. /// /// /// /// /// public static IEnumerable Transform( this IEnumerable source, Func, IEnumerable> transform) => transform(source); /// /// Gets a null IEnumerable as an empty IEnumerable. /// /// /// /// public static IEnumerable EmptyNull(this IEnumerable? items) => items ?? Enumerable.Empty(); // the .OfType() filter is nice when there's only one type // this is to support filtering with multiple types public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) => contents.Where(x => types.Contains(x?.GetType())); public static IEnumerable SkipLast(this IEnumerable source) { using (IEnumerator e = source.GetEnumerator()) { if (e.MoveNext() == false) { yield break; } for (T value = e.Current; e.MoveNext(); value = e.Current) { yield return value; } } } public static IOrderedEnumerable OrderBy( this IEnumerable source, Func keySelector, Direction sortOrder) => sortOrder == Direction.Ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector); }