Itertools for C# - Cycle and Zip
Continuing our implementation of Python iterator functions in C#, we'll implement Cycle and Zip this time.
Cycle
IEnumerable Cycle(IEnumerable iterable)
Cycle keeps cycling through the enumerator, that is, it cycles back to the first element when the enumerator is exhausted. You can use it like
IEnumerable<int> source = new List<int>() { 1, 2, 3};
foreach(int element in Itertools.Cycle(source))
{
Console.WriteLine(element); // prints 1 2 3 1 2 3..
}
The implementation is again very straightforward - all we have to do is wrap the foreach over the iterable in an infinite loop. The outer loop while loop creates a new enumerator for every iteration, as foreach calls GetEnumerator() before looping over the returned enumerator.
public static IEnumerable<T> Cycle<T>(IEnumerable<T> iterable)
{
while (true)
{
foreach (T t in iterable)
{
yield return t;
}
}
}
The Python cycle function has a slightly different behavior - it caches elements in the iterable the first time around and then iterates over the cache after that. This means that changes to the collection will not be visible to the users of the cycle function, but only if the changes happen after cycle starts returning elements from its cache.
Zip
IEnumerable<T[]> Zip<T>(params IEnumerable<T>[] iterables)
Zip returns an enumerable array of elements, with each array containing one element per input iterator. The first array contains the first element of every iterator, the second array has every second element and so on.
IEnumerable<int> e1 = new List<int>() { 1, 2, 3};
IEnumerable<int> e2 = new List<int>() { 4, 5, 6};
IEnumerable<int[]> zippedValues = Itertools.Zip(e1, e2);
foreach(int[] arr in zippedValues)
{
Console.WriteLine("{");
foreach(int val in arr)
Console.WriteLine(val);
Console.WriteLine("}");
}
This prints {1,4}, {2,5}, {3,6}
The implementation is slightly more complex.
public static IEnumerable<T[]> Zip<T>(params IEnumerable<T>[] iterables)
{
IEnumerator<T>[] enumerators = Array.ConvertAll(iterables, (iterable) => iterable.GetEnumerator());
while (true)
{
int index = 0;
T[] values = new T[enumerators.Length];
foreach (IEnumerator<T> enumerator in enumerators)
{
if (!enumerator.MoveNext())
yield break;
values[index++] = enumerator.Current;
}
yield return values;
}
}
The code gets enumerators for all the iterables, moves all enumerators forward, accumulates their current values into an array and yields the array. It does this until any one of the enumerators runs out of elements.
Next time, we'll look at Tee - an interesting function that also proved to be a little tricky to implement.