mirror of https://github.com/icsharpcode/ILSpy.git
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 |
|
} |
|
}
|
|
|