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

如果我有一个像IEnumerable:

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

我想循环所有的连续项目对(2号滑动窗口)。 这将是

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

我的解决办法是这样的

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

当我编写这段代码的时候,我想知道.NET框架中是否已经有了相同的function,并不仅仅是为了对,而是为了任何大小的元组。 恕我直言,应该有一个很好的方式来做这种滑动窗口操作。

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

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

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

为什么不接受一个select器,而不是要求一个元组(对)types:

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

这可以让你跳过中间对象,如果你想要的话:

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

或者你可以使用匿名types:

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

最简单的方法是使用ReactiveExtensions

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

并使自己成为一个扩展方法套件打击这一起

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

通过显式使用传入的迭代器来扩展以前的答案以避免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; } } 

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

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

这是我今天结束的一个:

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

用法:

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

C#3.0解决scheme(对不起:)

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

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

实际上,使C#3.0解决scheme成为唯一的东西就是.Skip.Take结构,所以如果只是将该范围内的元素添加到列表中,它应该是2.0的黄金。 这就是说,它仍然没有性能。

这是我的解决scheme使用堆栈。 简短而简洁。

 string[] items = new string[] { "a", "b", "c", "d" }; Stack<string> stack = new Stack<string>(items.Reverse()); while(stack.Count > 1) { Console.WriteLine("{0},{1}", stack.Pop(), stack.Peek()); } 

交替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 ); } } 

简单的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; } } 

使用示例:

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

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

如果它已经在.NET框架中,而不是返回对,它可能会接受一个select器function,因为缺乏对像C#和VB这样的语言元组的支持。

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

我不认为这里的任何答案真的会改进你的简单的迭代器实现,这对我来说似乎是最自然的 (而且看起来像是海报dahlbyk !)。

像这样的东西:

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

只是为了方便,这里是@ dahlbyk答案的无select版本。

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