Combining sequences
These operations take two or more sequences and produce one. They differ in how they pick the next element:
Merge by comparing, Interleave by taking turns, ZipLongest by position, Flatten by exhausting one
sequence before starting the next. Intersperse is the odd one out: it combines a sequence with a single
element.
All of them are lazy and enumerate each input at most once.
Merge
Merges two or more sorted sequences into one sorted sequence, like the merge step of a merge sort.
IEnumerable<TSource> Merge<TSource>(this IEnumerable<TSource> source1, IEnumerable<TSource> source2, Option<IComparer<TSource>> comparer = default)
IEnumerable<TSource> Merge<TSource>(this IEnumerable<TSource> source1, IEnumerable<TSource> source2, IEnumerable<TSource> source3, Option<IComparer<TSource>> comparer = default)
IEnumerable<TSource> Merge<TSource>(this IEnumerable<TSource> source1, IEnumerable<TSource> source2, IEnumerable<TSource> source3, IEnumerable<TSource> source4, Option<IComparer<TSource>> comparer = default)
IEnumerable<TSource> Merge<TSource>(this IEnumerable<IEnumerable<TSource>> sources, Option<IComparer<TSource>> comparer = default)
The inputs must already be sorted by the same comparer, which defaults to Comparer<T>.Default. Merge does
not check this; on unsorted input it still produces all elements, but not in order. At each step it looks at
the current head of every input and yields the smallest. When two heads compare equal, the one from the earlier
input wins, so the merge is stable.
Merge is lazy and streams: it holds one element per input, never materializes anything, and works on infinite
inputs. This is what distinguishes it from Concat(...).OrderBy(...), which has to read everything first.
Example
var odds = Sequence.Return(1, 3, 5, 9);
var evens = Sequence.Return(2, 4, 6);
odds.Merge(evens);
// [1, 2, 3, 4, 5, 6, 9]
var byNewest = Comparer<LogEntry>.Create((a, b) => b.Timestamp.CompareTo(a.Timestamp));
var combinedLog = Sequence.Return(webLog, dbLog, workerLog).Merge(byNewest);
Interleave
Takes one element from each sequence in turn, round-robin, until all sequences are exhausted.
IEnumerable<TSource> Interleave<TSource>(this IEnumerable<TSource> source, params IEnumerable<TSource>[] otherSources)
IEnumerable<TSource> Interleave<TSource>(this IEnumerable<IEnumerable<TSource>> source)
Sequences of different lengths are fine: a sequence that runs out simply drops out of the rotation and the
remaining ones continue. The result therefore contains every element of every input. Interleave is lazy and
advances each input only when it is that input's turn.
For two sequences of the same length, Interleave is Zip followed by flattening the pairs.
Example
var numbers = Sequence.Return(1, 2, 3, 4);
var letters = Sequence.Return("a", "b");
numbers.Select(n => n.ToString()).Interleave(letters);
// ["1", "a", "2", "b", "3", "4"]
Alternating rows from several sources in a fair way, for example to mix results of different providers:
var mixed = Sequence.Return(newsFeed, blogFeed, podcastFeed).Interleave().Take(30);
Intersperse
Inserts a separator element between every two elements of a sequence.
IEnumerable<TSource> Intersperse<TSource>(this IEnumerable<TSource> source, TSource element)
The separator goes only between elements: an empty sequence stays empty, and a single element is returned without a separator. A sequence of n elements becomes one of 2n - 1. The operation is lazy.
This is the sequence counterpart of string.Join: JoinToString(", ") is Intersperse(", ") followed by
concatenation. Use Intersperse when the result should stay a sequence, for example to put spacer items into a
list of UI elements.
Example
Sequence.Return(9, 7, 3).Intersperse(0);
// [9, 0, 7, 0, 3]
Sequence.Return("a").Intersperse("-");
// ["a"]
var layout = widgets.Intersperse(new Spacer());
ZipLongest
Pairs up two sequences element by element, like Zip, but keeps going until the longer one is exhausted.
IEnumerable<EitherOrBoth<TLeft, TRight>> ZipLongest<TLeft, TRight>(this IEnumerable<TLeft> left, IEnumerable<TRight> right)
IEnumerable<TResult> ZipLongest<TLeft, TRight, TResult>(this IEnumerable<TLeft> left, IEnumerable<TRight> right, Func<EitherOrBoth<TLeft, TRight>, TResult> resultSelector)
Zip stops at the end of the shorter sequence and silently drops the rest of the longer one. ZipLongest
instead produces an EitherOrBoth<L, R> for every position: Both(left, right) while both sequences have
elements, then Left(left) or Right(right) for the remaining elements of whichever is longer. There is no
default value to invent, so nothing is invented.
EitherOrBoth is handled with Match or Switch, which take three functions. The result selector overload is
the shorter way to write ZipLongest(right).Select(selector).
Example
var expected = Sequence.Return("a", "b", "c");
var actual = Sequence.Return("a", "x");
expected.ZipLongest(actual, pair => pair.Match(
both: (e, a) => e == a ? $"{e} ok" : $"{e} != {a}",
left: e => $"{e} missing",
right: a => $"{a} unexpected"));
// ["a ok", "b != x", "c missing"]
Flatten
Concatenates a sequence of sequences into one sequence.
IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> enumerable)
Flatten is SelectMany(Identity): the inner sequences are enumerated one after the other, in order, lazily.
It is the inverse of Chunk, and exists because SelectMany(x => x) says much less than its name.
The same operation on a known number of sequences is Sequence.Concat(first, second, third), which is the
variadic form of LINQ's Concat.
Example
var pages = Sequence.Return(
Sequence.Return(1, 2, 3),
Sequence.Return(4, 5),
Enumerable.Empty<int>(),
Sequence.Return(6));
pages.Flatten();
// [1, 2, 3, 4, 5, 6]
numbers.Chunk(3).Flatten().SequenceEqual(numbers);
// true
CartesianProduct
The Cartesian product of two sequences is the sequence of all pairs with the first element from the first sequence and the second from the second. Funcky does not ship a method for it, because LINQ already has one under a different name, and this section exists so that you find it.
Recipe
SelectMany with a selector that ignores its argument is the Cartesian product:
// As tuples
var pairs = first.SelectMany(_ => second, ValueTuple.Create);
// With a selector
var combined = first.SelectMany(_ => second, (a, b) => ...);
// In query syntax, which reads most naturally
var combined =
from a in first
from b in second
select ...;
The second sequence is enumerated once per element of the first, so if it is expensive to produce, materialize it before.
Example
Every playing card is a suit combined with a rank:
var suits = Sequence.Return("♠", "♣", "♥", "♦");
var ranks = Sequence.Return("2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A");
var deck =
from suit in suits
from rank in ranks
select $"{rank}{suit}";
// ["2♠", "3♠", …, "A♦"], 52 cards