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