// MIT License - Copyright (c) 2023 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Core.Extension
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Random;
using WallstopStudios.UnityHelpers.Core.Helper;
using WallstopStudios.UnityHelpers.Utils;
///
/// Extension methods for IEnumerable providing collection operations, conversions, shuffling, and iteration utilities.
///
///
/// Thread Safety: Most methods are not thread-safe unless noted otherwise. Concurrent enumeration requires external synchronization.
/// Performance: Methods are optimized for common collection types (IList, IReadOnlyList, HashSet) with pattern matching.
/// Allocations: Documented per method. Many methods use pooled buffers to minimize allocations.
///
public static class IEnumerableExtensions
{
///
/// Converts an enumerable collection to a LinkedList.
///
/// The type of elements in the collection.
/// The source enumerable to convert.
/// A new LinkedList containing all elements from the source.
///
/// Null handling: Throws ArgumentNullException if source is null (thrown by LinkedList constructor).
/// Thread safety: Not thread-safe. No Unity main thread requirement.
/// Performance: O(n) where n is the number of elements in source.
/// Allocations: Allocates a new LinkedList and a node for each element.
/// Edge cases: Empty source results in an empty LinkedList.
///
/// Thrown when source is null.
public static LinkedList ToLinkedList(this IEnumerable source)
{
return new LinkedList(source);
}
///
/// Converts an enumerable to an IList, avoiding allocation if the enumerable is already an IList.
///
/// The type of elements in the collection.
/// The enumerable to convert.
/// The original enumeration if it's already an IList, otherwise a new List containing all elements.
///
/// Null handling: Throws NullReferenceException if enumeration is null when calling ToList().
/// Thread safety: Not thread-safe. No Unity main thread requirement.
/// Performance: O(1) if enumeration is already IList, O(n) otherwise where n is the number of elements.
/// Allocations: No allocation if enumeration is already an IList. Otherwise allocates a new List.
/// Edge cases: Returns the original IList reference if enumeration is already an IList, enabling type-checking optimizations.
///
public static IList AsList(this IEnumerable enumeration)
{
if (enumeration is IList list)
{
return list;
}
return enumeration.ToList();
}
///
/// Sorts the elements of a sequence in ascending order using a comparison function.
///
/// The type of the elements of enumeration.
/// A sequence of values to order.
/// A comparison function that returns negative if first is less than second, 0 if equal, positive if greater.
/// A new List containing the sorted elements.
///
/// Null handling: Comparer can be invoked with null elements depending on enumeration content.
/// Thread safety: Not thread-safe. No Unity main thread requirement.
/// Performance: O(n log n) where n is the number of elements (uses List.Sort).
/// Allocations: Allocates a result List. Uses pooled buffer for intermediate work.
/// Edge cases: Empty or single element collections return without sorting.
///
/// Thrown when comparer is null (propagated from FuncBasedComparer).
public static List OrderBy(this IEnumerable enumeration, Func comparer)
{
FuncBasedComparer typedComparer = new(comparer);
using PooledResource> lease = Buffers.List.Get(out List buffer);
buffer.AddRange(enumeration);
if (buffer.Count <= 1)
{
return new List(buffer);
}
buffer.Sort(typedComparer);
return new List(buffer);
}
///
/// Sorts the elements of a sequence in ascending order using their natural IComparable ordering.
///
/// The type of the elements of enumerable. Must implement IComparable.
/// A sequence of values to order.
/// A new List containing the sorted elements in ascending order.
///
/// Null handling: Behavior with null elements depends on T's CompareTo implementation.
/// Thread safety: Not thread-safe. No Unity main thread requirement.
/// Performance: O(n log n) where n is the number of elements (uses List.Sort).
/// Allocations: Allocates a result List. Uses pooled buffer for intermediate work.
/// Edge cases: Empty or single element collections return without sorting.
///
public static List Ordered(this IEnumerable enumerable)
where T : IComparable
{
using PooledResource> lease = Buffers.List.Get(out List buffer);
buffer.AddRange(enumerable);
if (buffer.Count <= 1)
{
return new List(buffer);
}
buffer.Sort();
return new List(buffer);
}
///
/// Returns a randomly shuffled version of the enumerable.
///
/// The type of the elements of enumerable.
/// A sequence of values to shuffle.
/// The random number generator to use. If null, uses PRNG.Instance.
/// A new List containing the same elements in random order.
///
/// Null handling: If enumerable is null, will throw when enumerated. If random is null, uses PRNG.Instance.
/// Thread safety: Not thread-safe if random is shared. No Unity main thread requirement.
/// Performance: O(n) where n is the number of elements (uses Fisher-Yates shuffle via IListExtensions.Shuffle).
/// Allocations: Allocates a result List. Uses pooled buffer for intermediate work.
/// Edge cases: Empty or single element collections return unchanged. Shuffle quality depends on random implementation.
///
public static List Shuffled(this IEnumerable enumerable, IRandom random = null)
{
using PooledResource> lease = Buffers.List.Get(out List buffer);
buffer.AddRange(enumerable);
buffer.Shuffle(random);
return new List(buffer);
}
///
/// Creates an infinite repeating sequence from the given enumerable.
///
/// The type of the elements of enumerable.
/// A sequence of values to repeat infinitely.
/// An infinite IEnumerable that cycles through the source elements repeatedly.
///
/// Null handling: If enumerable is null, behavior is undefined. Empty enumerables immediately yield break.
/// Thread safety: Not thread-safe. No Unity main thread requirement.
/// Performance: O(1) amortized per element. Optimized for common collection types (IList, HashSet, etc). Unknown collection types use buffering on first iteration.
/// Allocations: No allocations for known collection types. Unknown types allocate a pooled buffer during first enumeration.
/// Edge cases: Empty collections immediately stop enumeration. Unknown collection types buffer all elements on first pass. The iterator will loop forever for non-empty collections.
///
public static IEnumerable Infinite(this IEnumerable enumerable)
{
switch (enumerable)
{
case IReadOnlyList { Count: 0 }:
{
yield break;
}
case IReadOnlyList readonlyList:
{
while (true)
{
for (int i = 0; i < readonlyList.Count; ++i)
{
yield return readonlyList[i];
}
}
}
case IList { Count: 0 }:
{
yield break;
}
case IList list:
{
while (true)
{
for (int i = 0; i < list.Count; ++i)
{
yield return list[i];
}
}
}
case HashSet { Count: 0 }:
{
yield break;
}
case HashSet hashSet:
{
while (true)
{
foreach (T element in hashSet)
{
yield return element;
}
}
}
case Queue { Count: 0 }:
{
yield break;
}
case Queue queue:
{
while (true)
{
foreach (T element in queue)
{
yield return element;
}
}
}
case Stack { Count: 0 }:
{
yield break;
}
case Stack stack:
{
while (true)
{
foreach (T element in stack)
{
yield return element;
}
}
}
case SortedSet { Count: 0 }:
{
yield break;
}
case SortedSet sortedSet:
{
while (true)
{
foreach (T element in sortedSet)
{
yield return element;
}
}
}
case LinkedList { Count: 0 }:
{
yield break;
}
case LinkedList linkedList:
{
while (true)
{
foreach (T element in linkedList)
{
yield return element;
}
}
}
}
using PooledResource> buffer = Buffers.List.Get(out List bufferList);
foreach (T element in enumerable)
{
bufferList.Add(element);
yield return element;
}
if (bufferList.Count == 0)
{
yield break;
}
while (true)
{
foreach (T element in bufferList)
{
yield return element;
}
}
}
///
/// Splits an enumerable into partitions of the specified size.
///
/// The type of the elements of items.
/// The sequence to partition.
/// The maximum number of elements per partition.
/// An IEnumerable of IEnumerable where each inner enumerable contains up to 'size' elements.
///
/// Null handling: Throws NullReferenceException if items is null when GetEnumerator is called.
/// Thread safety: Not thread-safe. No Unity main thread requirement.
/// Performance: O(n) where n is the total number of elements. Lazy evaluation - partitions are created as enumerated.
/// Allocations: Uses a pooled List buffer for each partition, reducing allocations. The buffer is reused across partitions.
/// Edge cases: Last partition may have fewer than 'size' elements. Size less than 1 will cause infinite loop or no partitions. Empty items yields no partitions.
///
public static IEnumerable> Partition(this IEnumerable items, int size)
{
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size), size, "Size must be positive.");
}
using IEnumerator enumerator = items.GetEnumerator();
while (enumerator.MoveNext())
{
// Allocate one independent list per partition to ensure the yielded value
// is not mutated by subsequent iterations.
List partition = new(size);
int count = 0;
do
{
partition.Add(enumerator.Current);
} while (++count < size && enumerator.MoveNext());
yield return partition;
}
}
///
/// Splits an enumerable into partitions of the specified size, returning pooled lists.
/// Each yielded value must be disposed by the consumer to return the list to the pool.
///
/// Element type.
/// Sequence to partition.
/// Maximum elements per partition.
/// Sequence of pooled list resources; dispose each to return to pool.
public static IEnumerable>> PartitionPooled(
this IEnumerable items,
int size
)
{
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size), size, "Size must be positive.");
}
return new PartitionPooledEnumerable(items, size);
}
private sealed class PartitionPooledEnumerable : IEnumerable>>
{
private readonly IEnumerable _items;
private readonly int _size;
public PartitionPooledEnumerable(IEnumerable items, int size)
{
_items = items;
_size = size;
}
public IEnumerator>> GetEnumerator()
{
return new PartitionPooledEnumerator(_items.GetEnumerator(), _size);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
private sealed class PartitionPooledEnumerator : IEnumerator>>
{
private readonly IEnumerator _source;
private readonly int _size;
private readonly Dictionary, PooledResource>> _leases = new();
private readonly Action> _returnLeaseAction;
private bool _disposed;
public PartitionPooledEnumerator(IEnumerator source, int size)
{
_source = source;
_size = size;
_returnLeaseAction = ReturnLease;
}
public PooledResource> Current { get; private set; }
object IEnumerator.Current => Current;
public bool MoveNext()
{
if (_disposed)
{
return false;
}
if (!_source.MoveNext())
{
Current = default;
return false;
}
PooledResource> pooled = Buffers.GetList(_size, out List partition);
partition.Add(_source.Current);
int count = 1;
while (count < _size && _source.MoveNext())
{
partition.Add(_source.Current);
count++;
}
_leases[partition] = pooled;
Current = new PooledResource>(partition, _returnLeaseAction);
return true;
}
public void Reset()
{
throw new NotSupportedException();
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_source.Dispose();
foreach (KeyValuePair, PooledResource>> lease in _leases)
{
lease.Value.Dispose();
}
_leases.Clear();
}
private void ReturnLease(List partition)
{
if (_leases.Remove(partition, out PooledResource> pooled))
{
pooled.Dispose();
}
}
}
}
}