Skip to content

Instantly share code, notes, and snippets.

@maftieu
Last active February 5, 2016 13:46
Show Gist options
  • Save maftieu/f175ea7766158ca804df to your computer and use it in GitHub Desktop.
Save maftieu/f175ea7766158ca804df to your computer and use it in GitHub Desktop.
C# - IEnumerable extensions
using System;
using System.Collections.Generic;
using System.Linq;
namespace Untunh.Helpers
{
/// <summary>
/// Provides a set of static methods for querying objects that implement System.Collections.Generic.IEnumerable<T>.
/// </summary>
public static class Extensions
{
/// <summary>
/// Picks one item of the <paramref name="source"/> sequence at a random index.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">The IEnumerable to pick one element of at a random index.</param>
/// <returns>An element at a random index of the specified sequence.</returns>
/// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
/// <exception cref="System.InvalidOperationException">
/// The <paramref name="source"/> sequence is empty.
/// </exception>
public static TSource PickOne<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw new ArgumentNullException("source");
var nbElements = source.Count();
if (nbElements == 0)
throw new InvalidOperationException("Sequence contains no elements.");
return source.ElementAt((new Random()).Next(0, nbElements));
}
/// <summary>
/// Picks many elements of the <paramref name="source"/> sequence at random indexes.
/// A same source element may appear more than once in the result sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">The IEnumerable to pick elements of at random indexes.</param>
/// <param name="count">Number of elements to pick from the sequence.</param>
/// <returns>A sequence of elements at random indexes of the specified sequence.</returns>
/// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
/// <exception cref="System.InvalidOperationException">
/// The <paramref name="source"/> sequence is empty.
/// </exception>
public static IEnumerable<TSource> PickMany<TSource>(this IEnumerable<TSource> source, int count)
{
if (source == null)
throw new ArgumentNullException("source");
return PickManyInternal(source, count, false);
}
/// <summary>
/// Picks many elements of the <paramref name="source"/> sequence at random unique indexes.
/// The same element from the <paramref name="source"/> sequence won't be picked more than once.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">The IEnumerable to pick elements of at random indexes.</param>
/// <param name="count">Number of elements to pick from the sequence.</param>
/// <returns>A sequence of elements at random indexes of the specified sequence.</returns>
/// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
/// <exception cref="System.InvalidOperationException">
/// The <paramref name="source"/> sequence is empty or does not contain enough elements to be picked.
/// </exception>
public static IEnumerable<TSource> PickManyUnique<TSource>(this IEnumerable<TSource> source, int count)
{
if (source == null)
throw new ArgumentNullException("source");
return PickManyInternal(source, count, true);
}
/// <summary>
/// Picks many elements of the <paramref name="source"/> list at random indexes.
/// If <paramref name="unique"/> is true, the same element from the <paramref name="source"/> won't
/// appear more than once in the result sequence ; otherwise, it may appear multiple times.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">The IEnumerable from which to pick elements of at random indexes.</param>
/// <param name="count">Number of elements to pick from the sequence.</param>
/// <param name="unique">true to pick an element at most one time ; otherwise false.</param>
/// <returns>A sequence of elements at random indexes of the specified sequence.</returns>
/// <exception cref="System.InvalidOperationException">
/// The <paramref name="source"/> sequence is empty or does not contain enough unique elements to be picked.
/// </exception>
internal static IEnumerable<TSource> PickManyInternal<TSource>(IEnumerable<TSource> source, int count, bool unique)
{
var nbElements = source.Count();
if (unique && (nbElements < count))
throw new InvalidOperationException("Sequence contains fewer elements than requested.");
int[] pickedIndexes = null;
if (unique)
{
pickedIndexes = new int[count];
for (int i = 0; i < count; ++i)
pickedIndexes[i] = Int32.MinValue;
}
var rnd = new Random();
for (int i = 0; i < count; i++)
{
int rndIdx = Int32.MinValue;
do
{
rndIdx = rnd.Next(0, nbElements);
} while (unique && pickedIndexes.Contains(rndIdx));
if (unique)
pickedIndexes[i] = rndIdx;
yield return source.ElementAt(rndIdx);
}
}
/// <summary>
/// Randomize the elements of the <paramref name="source"/> sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">Sequence elements to randomize.</param>
/// <returns>A IOrderedEnumerable&lt;TElement&gt; which elements are sorted on a random order.</returns>
/// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
public static IOrderedEnumerable<TSource> Randomize<TSource>(this IEnumerable<TSource> source)
{
return Randomize(source, Environment.TickCount);
}
/// <summary>
/// Randomize the elements of the <paramref name="source"/> sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">Sequence elements to randomize.</param>
/// <param name="seed">A number used to calculate a starting value for the pseudo-random number sequence.</param>
/// <returns>A IOrderedEnumerable&lt;TElement&gt; which elements are sorted on a random order.</returns>
/// <exception cref="System.ArgumentNullException"><paramref name="source"/> is null.</exception>
public static IOrderedEnumerable<TSource> Randomize<TSource>(this IEnumerable<TSource> source, int seed)
{
var rnd = new Random(seed);
return source.OrderBy(x => rnd.Next());
}
}
}
@maftieu
Copy link
Author

maftieu commented Feb 5, 2016

This PickMany* implementation is slower in worst cases (like picking X elts out of X) than the one with arrays, but is faster (in my tests) at picking few elements (like 100 out of 1500).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment