Last active
February 6, 2018 22:14
-
-
Save kpol/ba05486f5477bb5a99daa113e64009b8 to your computer and use it in GitHub Desktop.
Enumerable Extensions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading; | |
namespace LinqExtensions | |
{ | |
public static class EnumerableExtensions | |
{ | |
/// <summary> | |
/// Returns shuffled elements from a sequence (Fisher-Yates shuffle). | |
/// </summary> | |
/// <typeparam name="T">The type of the elements of source.</typeparam> | |
/// <param name="source">A sequence of values that needs to be shuffled.</param> | |
/// <returns>An <see cref="IEnumerable{T}"/> that contains shuffled elements of the input sequence.</returns> | |
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
var array = source.ToArray(); | |
var random = GlobalRandom.Instance; | |
for (var i = array.Length - 1; i >= 0; i--) | |
{ | |
var index = random.Next(0, i + 1); | |
yield return array[index]; | |
array[index] = array[i]; | |
// T tmp = array[i]; | |
// array[i] = array[index]; | |
// array[index] = tmp; | |
} | |
} | |
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) | |
{ | |
return source.MaxBy(selector, Comparer<TKey>.Default); | |
} | |
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, | |
Func<TSource, TKey> selector, IComparer<TKey> comparer) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
if (selector == null) throw new ArgumentNullException(nameof(selector)); | |
if (comparer == null) throw new ArgumentNullException(nameof(comparer)); | |
using (var sourceIterator = source.GetEnumerator()) | |
{ | |
if (!sourceIterator.MoveNext()) | |
{ | |
throw new InvalidOperationException("Sequence contains no elements"); | |
} | |
var max = sourceIterator.Current; | |
var maxKey = selector(max); | |
while (sourceIterator.MoveNext()) | |
{ | |
var candidate = sourceIterator.Current; | |
var candidateProjected = selector(candidate); | |
if (comparer.Compare(candidateProjected, maxKey) > 0) | |
{ | |
max = candidate; | |
maxKey = candidateProjected; | |
} | |
} | |
return max; | |
} | |
} | |
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) | |
{ | |
return source.MinBy(selector, Comparer<TKey>.Default); | |
} | |
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
if (selector == null) throw new ArgumentNullException(nameof(selector)); | |
if (comparer == null) throw new ArgumentNullException(nameof(comparer)); | |
using (var sourceIterator = source.GetEnumerator()) | |
{ | |
if (!sourceIterator.MoveNext()) | |
{ | |
throw new InvalidOperationException("Sequence contains no elements"); | |
} | |
var min = sourceIterator.Current; | |
var minKey = selector(min); | |
while (sourceIterator.MoveNext()) | |
{ | |
var candidate = sourceIterator.Current; | |
var candidateProjected = selector(candidate); | |
if (comparer.Compare(candidateProjected, minKey) < 0) | |
{ | |
min = candidate; | |
minKey = candidateProjected; | |
} | |
} | |
return min; | |
} | |
} | |
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector) | |
{ | |
return source.DistinctBy(selector, EqualityComparer<TKey>.Default); | |
} | |
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IEqualityComparer<TKey> comparer) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
if (selector == null) throw new ArgumentNullException(nameof(selector)); | |
if (comparer == null) throw new ArgumentNullException(nameof(comparer)); | |
var set = new HashSet<TKey>(comparer); | |
return source.Where(item => set.Add(selector(item))); | |
} | |
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
if (count <= 0) | |
{ | |
yield break; | |
} | |
var list = source as IReadOnlyList<T>; | |
if (list != null) | |
{ | |
var c = count > list.Count ? list.Count : count; | |
for (int i = list.Count - c; i < list.Count; i++) | |
{ | |
yield return list[i]; | |
} | |
} | |
else | |
{ | |
var q = new Queue<T>(count); | |
foreach (var item in source) | |
{ | |
if (q.Count == count) | |
{ | |
q.Dequeue(); | |
} | |
q.Enqueue(item); | |
} | |
foreach (var item in q) | |
{ | |
yield return item; | |
} | |
} | |
} | |
} | |
public class GlobalRandom : Random | |
{ | |
private static int _seed = Environment.TickCount; | |
private static readonly ThreadLocal<Random> Random = | |
new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref _seed))); | |
private GlobalRandom() { } | |
public static Random Instance => new GlobalRandom(); | |
public override int Next() => Random.Value.Next(); | |
public override int Next(int minValue, int maxValue) => Random.Value.Next(minValue, maxValue); | |
public override int Next(int maxValue) => Random.Value.Next(maxValue); | |
public override double NextDouble() => Random.Value.NextDouble(); | |
public override void NextBytes(byte[] buffer) => Random.Value.NextBytes(buffer); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment