You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
413 lines
11 KiB
413 lines
11 KiB
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
|
|
namespace ICSharpCode.Decompiler.Util
|
|
{
|
|
static class CollectionExtensions
|
|
{
|
|
public static void Deconstruct<K, V>(this KeyValuePair<K, V> pair, out K key, out V value)
|
|
{
|
|
key = pair.Key;
|
|
value = pair.Value;
|
|
}
|
|
|
|
#if !NETCORE
|
|
public static IEnumerable<(A, B)> Zip<A, B>(this IEnumerable<A> input1, IEnumerable<B> input2)
|
|
{
|
|
return input1.Zip(input2, (a, b) => (a, b));
|
|
}
|
|
#endif
|
|
|
|
public static IEnumerable<(A?, B?)> ZipLongest<A, B>(this IEnumerable<A> input1, IEnumerable<B> input2)
|
|
{
|
|
using (var it1 = input1.GetEnumerator())
|
|
{
|
|
using (var it2 = input2.GetEnumerator())
|
|
{
|
|
bool hasElements1 = true;
|
|
bool hasElements2 = true;
|
|
while (true)
|
|
{
|
|
if (hasElements1)
|
|
hasElements1 = it1.MoveNext();
|
|
if (hasElements2)
|
|
hasElements2 = it2.MoveNext();
|
|
if (!(hasElements1 || hasElements2))
|
|
break;
|
|
yield return ((hasElements1 ? it1.Current : default), (hasElements2 ? it2.Current : default));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<T> Slice<T>(this IReadOnlyList<T> input, int offset, int length)
|
|
{
|
|
for (int i = offset; i < offset + length; i++)
|
|
{
|
|
yield return input[i];
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<T> Slice<T>(this IReadOnlyList<T> input, int offset)
|
|
{
|
|
int length = input.Count;
|
|
for (int i = offset; i < length; i++)
|
|
{
|
|
yield return input[i];
|
|
}
|
|
}
|
|
|
|
#if !NETCORE
|
|
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> input)
|
|
{
|
|
return new HashSet<T>(input);
|
|
}
|
|
#endif
|
|
|
|
public static IEnumerable<T> SkipLast<T>(this IReadOnlyCollection<T> input, int count)
|
|
{
|
|
return input.Take(input.Count - count);
|
|
}
|
|
|
|
public static IEnumerable<T> TakeLast<T>(this IReadOnlyCollection<T> input, int count)
|
|
{
|
|
return input.Skip(input.Count - count);
|
|
}
|
|
|
|
public static T? PopOrDefault<T>(this Stack<T> stack)
|
|
{
|
|
if (stack.Count == 0)
|
|
return default(T);
|
|
return stack.Pop();
|
|
}
|
|
|
|
public static T? PeekOrDefault<T>(this Stack<T> stack)
|
|
{
|
|
if (stack.Count == 0)
|
|
return default(T);
|
|
return stack.Peek();
|
|
}
|
|
|
|
public static int MaxOrDefault<T>(this IEnumerable<T> input, Func<T, int> selector, int defaultValue = 0)
|
|
{
|
|
int max = defaultValue;
|
|
foreach (var element in input)
|
|
{
|
|
int value = selector(element);
|
|
if (value > max)
|
|
max = value;
|
|
}
|
|
return max;
|
|
}
|
|
|
|
public static int IndexOf<T>(this IReadOnlyList<T> collection, T value)
|
|
{
|
|
var comparer = EqualityComparer<T>.Default;
|
|
int index = 0;
|
|
foreach (T item in collection)
|
|
{
|
|
if (comparer.Equals(item, value))
|
|
{
|
|
return index;
|
|
}
|
|
index++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> input)
|
|
{
|
|
foreach (T item in input)
|
|
collection.Add(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equivalent to <code>collection.Select(func).ToArray()</code>, but more efficient as it makes
|
|
/// use of the input collection's known size.
|
|
/// </summary>
|
|
public static U[] SelectArray<T, U>(this ICollection<T> collection, Func<T, U> func)
|
|
{
|
|
U[] result = new U[collection.Count];
|
|
int index = 0;
|
|
foreach (var element in collection)
|
|
{
|
|
result[index++] = func(element);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equivalent to <code>collection.Select(func).ToImmutableArray()</code>, but more efficient as it makes
|
|
/// use of the input collection's known size.
|
|
/// </summary>
|
|
public static ImmutableArray<U> SelectImmutableArray<T, U>(this IReadOnlyCollection<T> collection, Func<T, U> func)
|
|
{
|
|
var builder = ImmutableArray.CreateBuilder<U>(collection.Count);
|
|
foreach (var element in collection)
|
|
{
|
|
builder.Add(func(element));
|
|
}
|
|
return builder.MoveToImmutable();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equivalent to <code>collection.Select(func).ToArray()</code>, but more efficient as it makes
|
|
/// use of the input collection's known size.
|
|
/// </summary>
|
|
public static U[] SelectReadOnlyArray<T, U>(this IReadOnlyCollection<T> collection, Func<T, U> func)
|
|
{
|
|
U[] result = new U[collection.Count];
|
|
int index = 0;
|
|
foreach (var element in collection)
|
|
{
|
|
result[index++] = func(element);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equivalent to <code>collection.Select(func).ToArray()</code>, but more efficient as it makes
|
|
/// use of the input collection's known size.
|
|
/// </summary>
|
|
public static U[] SelectArray<T, U>(this List<T> collection, Func<T, U> func)
|
|
{
|
|
U[] result = new U[collection.Count];
|
|
int index = 0;
|
|
foreach (var element in collection)
|
|
{
|
|
result[index++] = func(element);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equivalent to <code>collection.Select(func).ToArray()</code>, but more efficient as it makes
|
|
/// use of the input collection's known size.
|
|
/// </summary>
|
|
public static U[] SelectArray<T, U>(this T[] collection, Func<T, U> func)
|
|
{
|
|
U[] result = new U[collection.Length];
|
|
int index = 0;
|
|
foreach (var element in collection)
|
|
{
|
|
result[index++] = func(element);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Equivalent to <code>collection.Select(func).ToList()</code>, but more efficient as it makes
|
|
/// use of the input collection's known size.
|
|
/// </summary>
|
|
public static List<U> SelectList<T, U>(this ICollection<T> collection, Func<T, U> func)
|
|
{
|
|
List<U> result = new List<U>(collection.Count);
|
|
foreach (var element in collection)
|
|
{
|
|
result.Add(func(element));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static IEnumerable<U> SelectWithIndex<T, U>(this IEnumerable<T> source, Func<int, T, U> func)
|
|
{
|
|
int index = 0;
|
|
foreach (var element in source)
|
|
yield return func(index++, element);
|
|
}
|
|
|
|
public static IEnumerable<(int, T)> WithIndex<T>(this ICollection<T> source)
|
|
{
|
|
int index = 0;
|
|
foreach (var item in source)
|
|
{
|
|
yield return (index, item);
|
|
index++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The merge step of merge sort.
|
|
/// </summary>
|
|
public static IEnumerable<T> Merge<T>(this IEnumerable<T> input1, IEnumerable<T> input2, Comparison<T> comparison)
|
|
{
|
|
using (var enumA = input1.GetEnumerator())
|
|
using (var enumB = input2.GetEnumerator())
|
|
{
|
|
bool moreA = enumA.MoveNext();
|
|
bool moreB = enumB.MoveNext();
|
|
while (moreA && moreB)
|
|
{
|
|
if (comparison(enumA.Current, enumB.Current) <= 0)
|
|
{
|
|
yield return enumA.Current;
|
|
moreA = enumA.MoveNext();
|
|
}
|
|
else
|
|
{
|
|
yield return enumB.Current;
|
|
moreB = enumB.MoveNext();
|
|
}
|
|
}
|
|
while (moreA)
|
|
{
|
|
yield return enumA.Current;
|
|
moreA = enumA.MoveNext();
|
|
}
|
|
while (moreB)
|
|
{
|
|
yield return enumB.Current;
|
|
moreB = enumB.MoveNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the minimum element.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
|
|
public static T MinBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IComparable<K>
|
|
{
|
|
return source.MinBy(keySelector, Comparer<K>.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the minimum element.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
|
|
public static T MinBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K>? keyComparer)
|
|
{
|
|
if (source == null)
|
|
throw new ArgumentNullException(nameof(source));
|
|
if (keySelector == null)
|
|
throw new ArgumentNullException(nameof(keySelector));
|
|
if (keyComparer == null)
|
|
keyComparer = Comparer<K>.Default;
|
|
using (var enumerator = source.GetEnumerator())
|
|
{
|
|
if (!enumerator.MoveNext())
|
|
throw new InvalidOperationException("Sequence contains no elements");
|
|
T minElement = enumerator.Current;
|
|
K minKey = keySelector(minElement);
|
|
while (enumerator.MoveNext())
|
|
{
|
|
T element = enumerator.Current;
|
|
K key = keySelector(element);
|
|
if (keyComparer.Compare(key, minKey) < 0)
|
|
{
|
|
minElement = element;
|
|
minKey = key;
|
|
}
|
|
}
|
|
return minElement;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the maximum element.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
|
|
public static T MaxBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IComparable<K>
|
|
{
|
|
return source.MaxBy(keySelector, Comparer<K>.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the maximum element.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
|
|
public static T MaxBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K>? keyComparer)
|
|
{
|
|
if (source == null)
|
|
throw new ArgumentNullException(nameof(source));
|
|
if (keySelector == null)
|
|
throw new ArgumentNullException(nameof(keySelector));
|
|
if (keyComparer == null)
|
|
keyComparer = Comparer<K>.Default;
|
|
using (var enumerator = source.GetEnumerator())
|
|
{
|
|
if (!enumerator.MoveNext())
|
|
throw new InvalidOperationException("Sequence contains no elements");
|
|
T maxElement = enumerator.Current;
|
|
K maxKey = keySelector(maxElement);
|
|
while (enumerator.MoveNext())
|
|
{
|
|
T element = enumerator.Current;
|
|
K key = keySelector(element);
|
|
if (keyComparer.Compare(key, maxKey) > 0)
|
|
{
|
|
maxElement = element;
|
|
maxKey = key;
|
|
}
|
|
}
|
|
return maxElement;
|
|
}
|
|
}
|
|
|
|
public static void RemoveLast<T>(this IList<T> list)
|
|
{
|
|
if (list == null)
|
|
throw new ArgumentNullException(nameof(list));
|
|
list.RemoveAt(list.Count - 1);
|
|
}
|
|
|
|
public static T? OnlyOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate) => OnlyOrDefault(source.Where(predicate));
|
|
|
|
public static T? OnlyOrDefault<T>(this IEnumerable<T> source)
|
|
{
|
|
bool any = false;
|
|
T? first = default;
|
|
foreach (var t in source)
|
|
{
|
|
if (any)
|
|
return default(T);
|
|
first = t;
|
|
any = true;
|
|
}
|
|
|
|
return first;
|
|
}
|
|
|
|
#if !NETCORE
|
|
public static int EnsureCapacity<T>(this List<T> list, int capacity)
|
|
{
|
|
if (capacity < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(capacity));
|
|
if (list.Capacity < capacity)
|
|
{
|
|
const int DefaultCapacity = 4;
|
|
const int MaxLength = 0X7FFFFFC7;
|
|
|
|
int newcapacity = list.Capacity == 0 ? DefaultCapacity : 2 * list.Capacity;
|
|
|
|
if ((uint)newcapacity > MaxLength)
|
|
newcapacity = MaxLength;
|
|
|
|
if (newcapacity < capacity)
|
|
newcapacity = capacity;
|
|
|
|
list.Capacity = newcapacity;
|
|
}
|
|
|
|
return list.Capacity;
|
|
}
|
|
#endif
|
|
|
|
#region Aliases/shortcuts for Enumerable extension methods
|
|
public static bool Any<T>(this ICollection<T> list) => list.Count > 0;
|
|
public static bool Any<T>(this T[] array, Predicate<T> match) => Array.Exists(array, match);
|
|
public static bool Any<T>(this List<T> list, Predicate<T> match) => list.Exists(match);
|
|
|
|
public static bool All<T>(this T[] array, Predicate<T> match) => Array.TrueForAll(array, match);
|
|
public static bool All<T>(this List<T> list, Predicate<T> match) => list.TrueForAll(match);
|
|
|
|
public static T? FirstOrDefault<T>(this T[] array, Predicate<T> predicate) => Array.Find(array, predicate);
|
|
public static T? FirstOrDefault<T>(this List<T> list, Predicate<T> predicate) => list.Find(predicate);
|
|
|
|
public static T Last<T>(this IList<T> list) => list[list.Count - 1];
|
|
#endregion
|
|
}
|
|
}
|