【问题标题】:Extension method for Enumerable.Intersperse?Enumerable.Intersperse 的扩展方法?
【发布时间】:2009-04-15 19:27:34
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: c# ienumerable


    【解决方案1】:

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

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

    【讨论】:

    • 你的观点是有道理的,但我对你的回答感到困惑。在您的示例中,似乎穿插的项目将首先出现,我认为这是不对的。
    • 我更喜欢你的,马克。它不需要对 if 块的否定只设置一次标志。
    • @Daniel:第一次通过循环它根本不输出,所以第一个枚举值在第一个穿插的项目之前返回。
    • @Daniel Pratt - 不,代码对我来说看起来不错。 Joel 对我使用了“项目”和“价值”的相反含义,但代码是正确的。
    • 啊,明白了。那些讨厌的!很容易错过。
    【解决方案2】:

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

    【讨论】:

    • 使用 GetEnumerator() 时,应 Dispose() 迭代器
    • 如果集合很大,最好消除分支。顺便说一句,这是一个经典的“栅栏柱问题”,你需要 n+1 或 n-1 个东西。 String.Join() 是 C#ers 接触这些的最常见方式。
    • @ChrisMoschini 方便为这个概念贴上标签!
    【解决方案3】:

    写起来很容易:

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

    【讨论】:

      【解决方案4】:

      这是一个对插入附加项进行更多控制的示例:

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

      【讨论】:

        【解决方案5】:

        这里有一些其他方法可以做到这一点。它们都应该具有相当的性能,没有一个需要首先完整地遍历输入序列。它们都正确处理空输入和单项输入。

        多选

        我更喜欢这样的方式:

        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 ]
        [  ]
        [  ]
        [  ]
        [  ]
        [  ]
        [  ]
        

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-02-13
            • 2016-03-05
            • 1970-01-01
            • 1970-01-01
            • 2015-03-12
            相关资源
            最近更新 更多