我从Haskell学到了intersperse function,并一直在寻找c#中的实现。
Intersperse采用2个参数,一个IEnumerable< T>源和T元素。它返回一个IEnumerable,其中元素插入源的每个元素之间。
一种可能的用例是在整数列表之间放置一个任意整数,例如:
// returns: {1, 0, 2, 0, 3}
(List<int>() {1, 2, 3}).Intersperse(0);
这是string.Join(...)的一般情况。
答案 0 :(得分:13)
其他人错过了一些东西:如果你只想在它之间,而不是在前面或后面,你需要做一个额外的检查:
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, T element)
{
bool first = true;
foreach (T value in source)
{
if (!first) yield return element;
yield return value;
first = false;
}
}
答案 1 :(得分:5)
我根据Linq解决方案的精神编写了一个懒惰的解决方案!我提出的其他解决方案涉及在返回数据之前遍历整个列表,然后返回结果列表。
其他一些答案在循环的每次迭代都有if检查。
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, T element)
{
using (var enumerator = source.GetEnumerator()) {
if (enumerator.MoveNext()) {
yield return enumerator.Current;
while (enumerator.MoveNext()) {
yield return element;
yield return enumerator.Current;
}
}
}
}
答案 2 :(得分:2)
写起来很容易:
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, T value) {
bool first = true;
foreach(T item in source) {
if(first) { first = false; }
else { yield return value; }
yield return item;
}
}
答案 3 :(得分:0)
下面是一个示例,可以更好地控制插入其他项目:
public delegate T IntersperseFunc<T>(T prev, T next, int index);
public delegate T InterspersePrevNextFunc<T>(T prev, T next);
public delegate T IntersperseIndexFunc<out T>(int index);
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, IntersperseFunc<T> elementFunc)
{
if (source == null) throw new ArgumentNullException(nameof(source));
using (var enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
var index = 0;
var prev = enumerator.Current;
yield return prev;
while (enumerator.MoveNext())
{
var next = enumerator.Current;
yield return elementFunc(prev, next, index++);
yield return next;
prev = next;
}
}
}
}
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, InterspersePrevNextFunc<T> elementFunc)
=> Intersperse(source, (prev, next, index) => elementFunc(prev, next));
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, IntersperseIndexFunc<T> elementFunc)
=> Intersperse(source, (prev, next, index) => elementFunc(index));
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, Func<T> elementFunc)
=> Intersperse(source, (prev, next, index) => elementFunc());
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, T element)
=> Intersperse(source, (prev, next, index) => element);
答案 4 :(得分:0)
这里有一些其他方法可以做到这一点。它们都应该非常高效,没有一个需要首先完整地传递输入序列。它们都可以正确处理空输入和单项输入。
我喜欢的方式是这样的:
public static IEnumerable<T> Intersperse <T> (this IEnumerable<T> source, T delimiter) =>
source.SelectMany((item) => Enumerable.Empty<T>().Append(delimiter).Append(item)).Skip(1);
或者:
public static IEnumerable<T> Intersperse <T> (this IEnumerable<T> source, T delimiter) =>
source.SelectMany((item) => new T[] { delimiter, item }).Skip(1);
它们是等价的:对于每个 item,创建一个新序列 { delimiter, item },并使用 SelectMany
将它们连接在一起。之后,跳过第一个分隔符——它只是一个额外的。与其他选项相比,我更喜欢这些选项的唯一原因是它们可以在紧要关头内使用,而无需编写额外的函数。
以下是一些其他实现(注意我称它们为 Delimit
而不是 Intersperse
):
虽然我认为它很笨重,但可以使用 Aggregate
完成:
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 可能不值得考虑:它省略了 Skip(1)
,但代价是大量额外的冗长和分支。
这些类似于其他基于 yield return
的答案,但处理第一个元素的方法不同(我认为更简洁):
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);
}
有一个带有测试代码 here 的可运行示例。测试部分:
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)} ]");
输出以下内容:
[ 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 ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
答案 5 :(得分:-2)
如果你想知道如何实现它,我会这样做:
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> collection, T value)
{
foreach(T item in collection)
{
yield return item;
yield return value;
}
yield break;
}