如果我有一个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框架中是否已经存在执行相同操作的函数,并且它不仅适用于对,而且适用于任何大小的元组。 恕我直言,应该有一个很好的方法来做这种滑动窗口操作。
我使用C#2.0,我可以想象使用C#3.0(w / LINQ)有更多(更好)的方法,但我主要对C#2.0解决方案感兴趣。不过,我也很欣赏C#3.0解决方案。
答案 0 :(得分:50)
在.NET 4中,这变得更加容易: -
var input = new[] { "a", "b", "c", "d", "e", "f" };
var result = input.Zip(input.Skip(1), (a, b) => Tuple.Create(a, b));
答案 1 :(得分:35)
不是要求元组(对)类型,为什么不接受选择器:
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);
或者您可以使用匿名类型:
var pairs = items.Pairwise((x, y) => new { First = x, Second = y });
答案 2 :(得分:9)
最简单的方法是使用ReactiveExtensions
using System.Reactive;
using System.Reactive.Linq;
并将自己作为套件bash的扩展方法
public static IEnumerable<IList<T>> Buffer<T>(this IEnumerable<T> seq, int bufferSize, int stepSize)
{
return seq.ToObservable().Buffer(bufferSize, stepSize).ToEnumerable();
}
答案 3 :(得分:5)
派对有点晚了,但作为所有这些扩展方法的替代方法,可以使用实际的“滑动”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));
}
答案 4 :(得分:3)
通过显式使用传递的迭代器扩展previous answer以避免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,在扩展方法之前,从输入参数中删除“this”并作为静态方法调用。
答案 5 :(得分:2)
为方便起见,这里是@ 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);
}
}
答案 6 :(得分:2)
这是我使用Stack的解决方案。它简洁明了。
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());
}
答案 7 :(得分:2)
如果我忽略了某些内容,请原谅我,但为什么不做一些简单的事情,例如for循环呢?:
#include <iostream>
using namespace std;
void swap(int &a, int &b){
b = (a+b) - (a=b);
}
int main() {
int a=1,b=6;
swap(a,b);
cout<<a<<b;
return 0;
}
答案 8 :(得分:1)
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);
}
这不是世界上最高效的,但看起来确实令人愉快。
真的,唯一让它成为C#3.0解决方案的是.Skip.Take构造,所以如果你只是将其改为将该范围中的元素添加到列表中,那么它应该是2.0的黄金。也就是说,它仍然没有表现。
答案 9 :(得分:0)
替代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( );
}
答案 10 :(得分:0)
F#Seq
模块定义IEnumerable<T>
上的成对函数,但此函数不在.NET框架中。
如果它已经在.NET框架中,而不是返回对,它可能会接受一个选择器函数,因为在C#和VB等语言中缺乏对元组的支持。
var pairs = ns.Pairwise( (a, b) => new { First = a, Second = b };
我认为这里的任何答案都没有真正改进你的简单迭代器实现,这对我来说似乎是最natural(以及事物外观的海报dahlbyk)。
答案 11 :(得分:0)
这样的事情:
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;
}
}
答案 12 :(得分:0)
我在@dahlbyk 的回答中创建了 2020 年末更新代码的略微修改版本。它更适合启用可为空引用类型 (<Nullable>enable</Nullable>
) 的项目。我还添加了基本文档。
/// <summary>
/// Enumerates over tuples of pairs of the elements from the original sequence. I.e. { 1, 2, 3 } becomes { (1, 2), (2, 3) }. Note that { 1 } becomes { }.
/// </summary>
public static IEnumerable<(T, T)> Pairwise<T>(this IEnumerable<T> source)
{
using var it = source.GetEnumerator();
if (!it.MoveNext())
yield break;
var previous = it.Current;
while (it.MoveNext())
yield return (previous, previous = it.Current);
}