I've got a fairly simple task here, but I think it's worth trying to perfect the implementation, because I can probably learn a thing or two about using .NET's enumerables correctly in the process.
I have two "lists" of strings, let's call them values
and separators
. The former is one element longer than the latter, and I want to interleave them into a single string (so I want to riffle the separators in between the values). Example:
var values = new []{ "abc", "def", "ghi" };
var separators = new []{ "123", "456" };
// expected result: "abc123def456ghi"
I need this in a few places, and in my actual code these might be List
s or other enumerables, so I figured I'd add an extension method to IEnumerable<string>
, which I call on the values and pass the separators as an argument. Here's what I've got:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Retina.Extensions
{
public static class EnumerableExtension
{
public static string Riffle(this IEnumerable<string> values, IEnumerable<string> separators)
{
if (values.Count() != separators.Count() + 1)
throw new Exception("Riffled enumerable needs to be one shorter than this enumerable.");
var builder = new StringBuilder();
var zipped = values.Zip(separators, (val, sep) => new { val, sep });
foreach (var pair in zipped)
{
builder.Append(pair.val);
builder.Append(pair.sep);
}
builder.Append(values.Last());
return builder.ToString();
}
}
}
As I understand it, IEnumerable
is generally a lazy list, so both Count()
and Last()
potentially incur an additional iteration through the entire list, which seems unnecessary. Also, the intermediate zipped
enumerable seems awkward for something as simple as alternatingly iterating through two lists.
Is there a more idiomatic way to implement this and make better use of .NET's collections/enumerables? Is IEnumerable
even the correct choice here?
yield
[complying w/ comment min length requirement] \$\endgroup\$