C# 或滑动窗口枚举器中的成对迭代

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/577590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 08:59:20  来源:igfitidea点击:

Pair-wise iteration in C# or sliding window enumerator

c#.netiteratorienumerable

提问by f3lix

If I have an IEnumerable like:

如果我有一个 IEnumerable 像:

string[] items = new string[] { "a", "b", "c", "d" };

I would like to loop thru all the pairs of consecutive items (sliding window of size 2). Which would be

我想遍历所有连续项目对(大小为 2 的滑动窗口)。哪个会

("a","b"), ("b", "c"), ("c", "d")

My solution was is this

我的解决方案是这样的

    public static IEnumerable<Pair<T, T>> Pairs(IEnumerable<T> enumerable) {
        IEnumerator<T> e = enumerable.GetEnumerator(); e.MoveNext();
        T current = e.Current;
        while ( e.MoveNext() ) {
            T next = e.Current;
            yield return new Pair<T, T>(current, next);
            current = next;
        }
    }

 // used like this :
 foreach (Pair<String,String> pair in IterTools<String>.Pairs(items)) {
    System.Out.PrintLine("{0}, {1}", pair.First, pair.Second)
 }

When I wrote this code, I wondered if there are already functions in the .NET framework that do the same thing and do it not just for pairs but for any size tuples. IMHO there should be a nice way to do this kind of sliding window operations.

当我编写这段代码时,我想知道 .NET 框架中是否已经有一些函数可以做同样的事情,并且不仅对对而且对任何大小的元组都这样做。恕我直言,应该有一种很好的方法来进行这种滑动窗口操作。

I use C# 2.0 and I can imagine that with C# 3.0 (w/ LINQ) there are more (and nicer) ways to do this, but I'm primarily interested in C# 2.0 solutions. Though, I will also appreciate C# 3.0 solutions.

我使用 C# 2.0,我可以想象使用 C# 3.0(w/ LINQ)有更多(更好)的方法来做到这一点,但我主要对 C# 2.0 解决方案感兴趣。不过,我也会欣赏 C# 3.0 解决方案。

回答by mqp

C# 3.0 solution (sorry:)

C# 3.0 解决方案(抱歉:)

public static IEnumerable<IEnumerable<T>> Tuples<T>(this IEnumerable<T> sequence, int nTuple)
{
    if(nTuple <= 0) throw new ArgumentOutOfRangeException("nTuple");

    for(int i = 0; i <= sequence.Count() - nTuple; i++)
        yield return sequence.Skip(i).Take(nTuple);
}

This isn't the most performant in the world, but it's sure pleasant to look at.

这不是世界上性能最好的,但看起来确实令人愉悦。

Really, the only thing making this a C# 3.0 solution is the .Skip.Take construct, so if you just change that to adding the elements in that range to a list instead, it should be golden for 2.0. That said, it's still not performant.

真的,唯一使它成为 C# 3.0 解决方案的是 .Skip.Take 构造,因此如果您只是将其更改为将该范围内的元素添加到列表中,那么它对于 2.0 来说应该是黄金。也就是说,它仍然没有性能。

回答by Richard

Expanding on the previous answerto avoid of O(n2) approach by explicitly using the passed iterator:

通过显式使用传递的迭代器扩展上一个答案以避免 O(n 2) 方法:

public static IEnumerable<IEnumerable<T>> Tuples<T>(this IEnumerable<T> input, int groupCount) {
  if (null == input) throw new ArgumentException("input");
  if (groupCount < 1) throw new ArgumentException("groupCount");

  var e = input.GetEnumerator();

  bool done = false;
  while (!done) {
    var l = new List<T>();
    for (var n = 0; n < groupCount; ++n) {
      if (!e.MoveNext()) {
        if (n != 0) {
          yield return l;
        }
        yield break;
      }
      l.Add(e.Current);
    }
    yield return l;
  }
}

For C# 2, before extension methods, drop the "this" from the input parameter and call as a static method.

对于 C# 2,在扩展方法之前,从输入参数中删除“this”并作为静态方法调用。

回答by Emperor XLII

Alternate Pairsimplementation, using last pair to store previous value:

替代Pairs实现,使用最后一对来存储以前的值:

static IEnumerable<Pair<T, T>> Pairs( IEnumerable<T> collection ) {
  Pair<T, T> pair = null;
  foreach( T item in collection ) {
    if( pair == null )
      pair = Pair.Create( default( T ), item );
    else
      yield return pair = Pair.Create( pair.Second, item );
  }
}

Simple Windowimplementation (only safe for private use, if caller does not save returned arrays; see note):

简单的Window实现(如果调用者不保存返回的数组,则只对私人使用是安全的;见注释):

static IEnumerable<T[]> Window( IEnumerable<T> collection, int windowSize ) {
  if( windowSize < 1 )
    yield break;

  int index = 0;
  T[] window = new T[windowSize];
  foreach( var item in collection ) {
    bool initializing = index < windowSize;

    // Shift initialized window to accomodate new item.
    if( !initializing )
      Array.Copy( window, 1, window, 0, windowSize - 1 );

    // Add current item to window.
    int itemIndex = initializing ? index : windowSize - 1;
    window[itemIndex] = item;

    index++;
    bool initialized = index >= windowSize;
    if( initialized )
      //NOTE: For public API, should return array copy to prevent 
      // modifcation by user, or use a different type for the window.
      yield return window;
  }
}

Example use:

使用示例:

for( int i = 0; i <= items.Length; ++i ) {
  Console.WriteLine( "Window size {0}:", i );
  foreach( string[] window in IterTools<string>.Window( items, i ) )
    Console.WriteLine( string.Join( ", ", window ) );
  Console.WriteLine( );
}

回答by dahlbyk

Rather than require a tuple (pair) type, why not just accept a selector:

与其需要一个元组(对)类型,为什么不只接受一个选择器:

public static IEnumerable<TResult> Pairwise<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> resultSelector)
{
    TSource previous = default(TSource);

    using (var it = source.GetEnumerator())
    {
        if (it.MoveNext())
            previous = it.Current;

        while (it.MoveNext())
            yield return resultSelector(previous, previous = it.Current);
    }
}

Which allows you to skip the intermediate object if you want:

如果需要,它允许您跳过中间对象:

string[] items = new string[] { "a", "b", "c", "d" };
var pairs = items.Pairwise((x, y) => string.Format("{0},{1}", x, y));

foreach(var pair in pairs)
    Console.WriteLine(pair);

Or you can use an anonymous type:

或者您可以使用匿名类型:

var pairs = items.Pairwise((x, y) => new { First = x, Second = y });

回答by Pete Montgomery

The F# Seqmodule defines the pairwise function over IEnumerable<T>, but this function is not in the .NET framework.

F#Seq模块在 上定义了成对函数IEnumerable<T>,但该函数不在 .NET 框架中。

If it were already in the .NET framework, instead of returning pairs it would probably accept a selector function due to the lack of support for tuples in languages like C# and VB.

如果它已经在 .NET 框架中,那么它可能会接受选择器函数而不是返回对,因为在 C# 和 VB 等语言中缺乏对元组的支持。

var pairs = ns.Pairwise( (a, b) => new { First = a, Second = b };

I don't think any of the answers here really improve on your simple iterator implementation, which seemed the most naturalto me (and the poster dahlbykby the looks of things!) too.

我认为这里的任何答案都没有真正改善您的简单迭代器实现,这对我来说似乎是最自然的(从事物的外观来看,海报dahlbyk也是如此!)。

回答by Quiz

Something like this:

像这样的东西:

public static IEnumerable<TResult> Pairwise<T, TResult>(this IEnumerable<T> enumerable, Func<T, T, TResult> selector)
{
    var previous = enumerable.First();
    foreach (var item in enumerable.Skip(1))
    {
        yield return selector(previous, item);
        previous = item;
    }
}

回答by Ian Mercer

In .NET 4 this becomes even easier:-

在 .NET 4 中,这变得更加容易:-

var input = new[] { "a", "b", "c", "d", "e", "f" };
var result = input.Zip(input.Skip(1), (a, b) => Tuple.Create(a, b));

回答by bradgonesurfing

The easiest way is to use ReactiveExtensions

最简单的方法是使用 ReactiveExtensions

using System.Reactive;
using System.Reactive.Linq;

and make yourself an extension method to kit bash this together

并让自己成为一种扩展方法来将其组合在一起

public static IEnumerable<IList<T>> Buffer<T>(this IEnumerable<T> seq, int bufferSize, int stepSize)
{
    return seq.ToObservable().Buffer(bufferSize, stepSize).ToEnumerable();
}

回答by James Holwell

Just for convenience, here is a selector-less version of @dahlbyk's answer.

为方便起见,这里是@dahlbyk 答案的无选择器版本。

public static IEnumerable<Tuple<T, T>> Pairwise<T>(this IEnumerable<T> enumerable)
{
    var previous = default(T);

    using (var e = enumerable.GetEnumerator())
    {
        if (e.MoveNext())
            previous = e.Current;

        while (e.MoveNext())
            yield return Tuple.Create(previous, previous = e.Current);
    }
}

回答by Sphinxxx

A little late to the party, but as an alternative to all these extension methods, one might use an actual "sliding" Collectionto hold (and discard) the data.

聚会有点晚了,但作为所有这些扩展方法的替代方法,可以使用实际的“滑动”Collection来保存(和丢弃)数据。

Here is one I ended up making today:

这是我今天最终制作的一个:

public class SlidingWindowCollection<T> : ICollection<T>
{
    private int _windowSize;
    private Queue<T> _source;

    public SlidingWindowCollection(int windowSize)
    {
        _windowSize = windowSize;
        _source = new Queue<T>(windowSize);
    }

    public void Add(T item)
    {
        if (_source.Count == _windowSize)
        {
            _source.Dequeue();
        }
        _source.Enqueue(item);
    }

    public void Clear()
    {
        _source.Clear();
    }

    ...and just keep forwarding all other ICollection<T> methods to _source.
}

Usage:

用法:

int pairSize = 2;
var slider = new SlidingWindowCollection<string>(pairSize);
foreach(var item in items)
{
    slider.Add(item);
    Console.WriteLine(string.Join(", ", slider));
}