11

我从 Haskell 中学习了intersperse 函数,并一直在寻找 c# 中的实现。

Intersperse 采用 2 个参数,一个 IEnumerable<T> 源和一个 T 元素。它返回一个 IEnumerable,其中元素插入源的每个元素之间。

一种可能的用例是将任意整数放在整数列表之间,例如:

// returns: {1, 0, 2, 0, 3}
(List<int>() {1, 2, 3}).Intersperse(0);

这是 string.Join(...) 的一般情况。

4

6 回答 6

14

其他人错过的东西:如果你只希望它在项目之间,而不是在前面或后面,你需要做一个额外的检查:

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;
    }
}

或者

public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, T element)
{
    var e = source.GetEnumerator(); 
    bool b = e.MoveNext();
    if (b) yield return e.Current;

    while (e.MoveNext())
    {
        yield return element;
        yield return e.Current;
    }
}           
于 2009-04-15T19:32:08.820 回答
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;
            }
        }
    }
}
于 2009-04-15T19:32:18.680 回答
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;
    }
}
于 2009-04-15T19:32:33.937 回答
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);
于 2020-03-28T11:27:28.347 回答
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将它们连接在一起。之后,跳过第一个分隔符——它只是一个额外的分隔符。与其他选项相比,我更喜欢这些选项的唯一原因是它们可以在紧要关头内联使用,而无需编写额外的函数。

以下是其他一些实现(注意我称它们为Delimitnot 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);
}

测试代码/示例

这里有一个带有测试代码的可运行示例。测试部分:

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 ]
[  ]
[  ]
[  ]
[  ]
[  ]
[  ]
于 2021-06-11T21:10:58.613 回答
-3

如果您想知道如何实现它,我会这样做:

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;
}
于 2009-04-15T19:31:46.730 回答