Here are some other ways to do it. They should all be pretty performant, none require a full pass over the input sequence first. All of them handle empty input and single-item input correctly.
SelectMany
The way I prefer is this:
public static IEnumerable<T> Intersperse <T> (this IEnumerable<T> source, T delimiter) =>
source.SelectMany((item) => Enumerable.Empty<T>().Append(delimiter).Append(item)).Skip(1);
Or, alternatively:
public static IEnumerable<T> Intersperse <T> (this IEnumerable<T> source, T delimiter) =>
source.SelectMany((item) => new T[] { delimiter, item }).Skip(1);
They're both equivalent: For each item, create a new sequence { delimiter, item }, and use SelectMany
to concatenate those all together. Afterwards, skip over the first delimiter -- it's just an extra. The only reason I prefer those over other options is they can be used inline in a pinch without having to write extra functions.
Here are a few other implementations (note I called them Delimit
not Intersperse
):
Aggregate
It can be done with Aggregate
although I think it is clunky:
public static IEnumerable<T> Delimit2a <T> (this IEnumerable<T> source, T delimiter) =>
source.Aggregate(Enumerable.Empty<T>(), (delimited, item) => delimited.Append(delimiter).Append(item)).Skip(1);
public static IEnumerable<T> Delimit2b <T> (this IEnumerable<T> source, T delimiter) =>
source.Aggregate(null as IEnumerable<T>, (delimited, item) => (delimited?.Append(delimiter) ?? Enumerable.Empty<T>()).Append(item)) ?? Enumerable.Empty<T>();
2b is probably not worth considering: it omits the Skip(1)
but at the cost of a lot of extra verbosity and branching.
yield return
These are similar to the other yield return
based answers but with different approaches to handling the first element (cleaner, in my opinion):
public static IEnumerable<T> Delimit3a <T> (this IEnumerable<T> source, T delimiter) {
foreach (T item in source.Take(1)) // protects agains empty source
yield return item;
foreach (T item in source.Skip(1)) {
yield return delimiter;
yield return item;
}
}
public static IEnumerable<T> Delimit3b <T> (this IEnumerable<T> source, T delimiter) {
static IEnumerable<U> Helper<U> (IEnumerable<U> source, U delimiter) {
foreach (U item in source) {
yield return delimiter;
yield return item;
}
}
return Helper(source, delimiter).Skip(1);
}
Test Code / Example
There is a runnable example with test code here. The test portion:
public static void Main () {
foreach (int count in new int[] { 11, 2, 1, 0 }) {
p( Enumerable.Range(10, count).Delimit1a(-1) );
p( Enumerable.Range(10, count).Delimit1b(-1) );
p( Enumerable.Range(10, count).Delimit2a(-1) );
p( Enumerable.Range(10, count).Delimit2b(-1) );
p( Enumerable.Range(10, count).Delimit3a(-1) );
p( Enumerable.Range(10, count).Delimit3b(-1) );
}
}
static void p <T> (IEnumerable<T> e) =>
Console.WriteLine($"[ {string.Join(", ", e)} ]");
Outputs the following:
[ 10, -1, 11, -1, 12, -1, 13, -1, 14, -1, 15, -1, 16, -1, 17, -1, 18, -1, 19, -1, 20 ]
[ 10, -1, 11, -1, 12, -1, 13, -1, 14, -1, 15, -1, 16, -1, 17, -1, 18, -1, 19, -1, 20 ]
[ 10, -1, 11, -1, 12, -1, 13, -1, 14, -1, 15, -1, 16, -1, 17, -1, 18, -1, 19, -1, 20 ]
[ 10, -1, 11, -1, 12, -1, 13, -1, 14, -1, 15, -1, 16, -1, 17, -1, 18, -1, 19, -1, 20 ]
[ 10, -1, 11, -1, 12, -1, 13, -1, 14, -1, 15, -1, 16, -1, 17, -1, 18, -1, 19, -1, 20 ]
[ 10, -1, 11, -1, 12, -1, 13, -1, 14, -1, 15, -1, 16, -1, 17, -1, 18, -1, 19, -1, 20 ]
[ 10, -1, 11 ]
[ 10, -1, 11 ]
[ 10, -1, 11 ]
[ 10, -1, 11 ]
[ 10, -1, 11 ]
[ 10, -1, 11 ]
[ 10 ]
[ 10 ]
[ 10 ]
[ 10 ]
[ 10 ]
[ 10 ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]