Last active
August 29, 2015 14:19
-
-
Save xorxornop/96e1cb5db25c6054c02d to your computer and use it in GitHub Desktop.
Zip many enumerable item sources together
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
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 < 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 > 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