Skip to content

Instantly share code, notes, and snippets.

@xorxornop
Last active August 29, 2015 14:19
Show Gist options
  • Save xorxornop/96e1cb5db25c6054c02d to your computer and use it in GitHub Desktop.
Save xorxornop/96e1cb5db25c6054c02d to your computer and use it in GitHub Desktop.
Zip many enumerable item sources together
public static class ZipExtensions
{
/// <summary>
/// Zips items such that {A,D,G} and {B,E,H} and {C,F,I} => {A,B,C,D,E,F,G,H,I}.
/// </summary>
/// <typeparam name="T">Type of item.</typeparam>
/// <param name="sources">The sources of items to zip and order.</param>
/// <param name="exhaustative">
/// If set to <c>true</c>, when any source is exhausted of items,
/// processing continues with the remaining sources.
/// </param>
public static IEnumerable<T> Zip<T>(this IEnumerable<IEnumerable<T>> sources, bool exhaustative = true)
{
// List of enumerable data sources, expressed as enumerator state machines
var sourceEnumerators = new List<IEnumerator<T>>(sources.Select(enumerable => enumerable.GetEnumerator()));
/* Maintains list of enumerators that have been exhausted and should therefore
* be removed at end of the enumerator list foreach/while cycle */
var removalStack = new Stack<IEnumerator<T>>();
while (sourceEnumerators.Count > 0) {
foreach (var source in sourceEnumerators) {
// Check if item is available from this enumerator
if (source.MoveNext()) {
yield return source.Current;
} else if (exhaustative) {
// Schedule this enumerator to be removed from the sources
removalStack.Push(source);
} else {
yield break;
}
}
// Remove exhausted source
while (removalStack.Count > 0) {
sourceEnumerators.Remove(removalStack.Pop());
}
}
}
/// <summary>
/// Zips and orders items such that {B,D,F} and {A,C,E} => {A,B,C,D,E,F} (if set to ascending order).
/// </summary>
/// <typeparam name="T">Type of item.</typeparam>
/// <param name="sources">The sources of items to zip and order.</param>
/// <param name="order">
/// Comparison result between previous value and the next, thereby determining ordering.
/// Default is <see cref="ComparisonResult.LessThan" /> (ascending).
/// Cannot be <see cref="ComparisonResult.EqualTo" />.
/// </param>
/// <param name="exhaustative">
/// If set to <c>true</c>, when a single source is exhausted of items,
/// processing continues with the remaining sources.
/// </param>
/// <returns></returns>
public static IEnumerable<T> ZipOrderClassBy<T>(this IEnumerable<IOrderedEnumerable<T>> sources,
ComparisonResult order = ComparisonResult.LessThan, bool exhaustative = true) where T : class, IComparable<T>
{
// List of enumerable data sources, expressed as enumerator state machines
var sourceEnumerators = new List<IEnumerator<T>>(sources.Select(enumerable => enumerable.GetEnumerator()));
/* Maintains list of enumerators that have been exhausted and should therefore
* be removed at end of the enumerator list foreach/while cycle */
var removalStack = new Stack<IEnumerator<T>>();
// Maintains list of enumerators which have been advanced but their values unused thus far
var sourcesPeeked = new List<IEnumerator<T>>();
while (sourceEnumerators.Count > 0) {
T currentItem = null;
foreach (var source in sourceEnumerators) {
// Check if item is available from this enumerator
// Was item peeked in previous iterations?
if (sourcesPeeked.Contains(source) == false) {
if (source.MoveNext()) {
if (currentItem == null || currentItem.Compare(source.Current) == order) {
currentItem = source.Current;
} else {
sourcesPeeked.Add(source);
}
} else if (exhaustative) {
// Schedule this enumerator to be removed from the sources list
removalStack.Push(source);
} else {
yield break;
}
} else {
if (currentItem == null || currentItem.Compare(source.Current) == order) {
currentItem = source.Current;
sourcesPeeked.Remove(source);
}
}
}
// Remove exhausted source
while (removalStack.Count > 0) {
var source = removalStack.Pop();
sourceEnumerators.Remove(source);
}
if (currentItem == null) yield break;
else yield return currentItem;
}
}
/// <summary>
/// Zips and orders items such that {B,D,F} and {A,C,E} => {A,B,C,D,E,F} (if set to ascending order).
/// </summary>
/// <typeparam name="T">Type of item.</typeparam>
/// <param name="sources">The sources of items to zip and order.</param>
/// <param name="order">
/// Comparison result between previous value and the next, thereby determining ordering.
/// Default is <see cref="ComparisonResult.LessThan" /> (ascending).
/// Cannot be <see cref="ComparisonResult.EqualTo" />.
/// </param>
/// <param name="exhaustative">
/// If set to <c>true</c>, when a single source is exhausted of items,
/// processing continues with the remaining sources.
/// </param>
/// <returns></returns>
public static IEnumerable<T> ZipOrderStructBy<T>(this IEnumerable<IOrderedEnumerable<T>> sources,
ComparisonResult order = ComparisonResult.LessThan, bool exhaustative = true) where T : struct, IComparable<T>
{
// List of enumerable data sources, expressed as enumerator state machines
var sourceEnumerators = new List<IEnumerator<T>>(sources.Select(enumerable => enumerable.GetEnumerator()));
/* Maintains list of enumerators that have been exhausted and should therefore
* be removed at end of the enumerator list foreach/while cycle */
var removalStack = new Stack<IEnumerator<T>>();
// Maintains list of enumerators which have been advanced but their values unused thus far
var sourcesPeeked = new List<IEnumerator<T>>();
while (sourceEnumerators.Count > 0) {
T? currentItem = null;
foreach (var source in sourceEnumerators) {
// Check if item is available from this enumerator
// Was item peeked in previous iterations?
if (sourcesPeeked.Contains(source) == false) {
if (source.MoveNext()) {
if (currentItem == null || currentItem.Value.Compare(source.Current) == order) {
currentItem = source.Current;
} else {
sourcesPeeked.Add(source);
}
} else if (exhaustative) {
// Schedule this enumerator to be removed from the sources list
removalStack.Push(source);
} else {
yield break;
}
} else {
if (currentItem == null || currentItem.Value.Compare(source.Current) == order) {
currentItem = source.Current;
sourcesPeeked.Remove(source);
}
}
}
// Remove exhausted source
while (removalStack.Count > 0) {
var source = removalStack.Pop();
sourceEnumerators.Remove(source);
}
if (currentItem == null) yield break;
else yield return currentItem.Value;
}
}
}
/// <summary>
/// Result of a comparison operation between two items (x and y for example).
/// </summary>
/// <remarks>
/// This enum can be used instead of the traditional integer-return comparison,
/// where -1 is less than, 0 is equal to, and +1 is greater than.
/// To ensure this, pass the value through <see cref="System.Math.Sign(int)"/> first.
/// </remarks>
public enum ComparisonResult
{
/// <summary>
/// Item calling the comparison (x) is of less value than the
/// item being compared (y) such that <code>x &lt; y</code>.
/// Results in ascending order, if used in ordering operation.
/// </summary>
LessThan = -1,
/// <summary>
/// Item calling the comparison (x) is of equal value to the
/// item being compared (y) such that <code>x == y</code>.
/// </summary>
EqualTo = 0,
/// <summary>
/// Item calling the comparison (x) is of greater value than the
/// item being compared (y) such that <code>x &gt; y</code>.
/// Results in descending order, if used in ordering operation.
/// </summary>
GreaterThan = 1
}
public static class ComparisonExtensions
{
public static ComparisonResult Compare<T>(this IComparable<T> @this, T other) {
int result = Math.Sign(@this.CompareTo(other));
if (result < 0) return ComparisonResult.LessThan;
if (result > 0) return ComparisonResult.GreaterThan;
return ComparisonResult.EqualTo;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment