问与答 .net-C#或滑动窗口枚举中的逐对迭代

dean · 2020-03-06 14:10:29 · 热度: 20

如果我有一个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(带有LINQ)有更多(更好)的方法来执行此操作,但是我主要对C#2.0解决方案感兴趣。 不过,我也会感谢C#3.0解决方案。

猜你喜欢:
共收到 12 条回复
edison #1 · 2020-03-06 14:10:29

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

var input = new[] { "a", "b", "c", "d", "e", "f" };
var result = input.Zip(input.Skip(1), (a, b) => Tuple.Create(a, b));
rodney #2 · 2020-03-06 14:10:31

而不是要求元组(对)类型,为什么不只接受选择器:

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 });
colby #3 · 2020-03-06 14:10:32

最简单的方法是使用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();
}
sonny #4 · 2020-03-06 14:10:34

聚会稍晚一些,但是作为所有这些扩展方法的替代方法,可以使用实际的“滑动” 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));
}
ben #5 · 2020-03-06 14:10:35

为了方便起见,这里是@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);
    }
}
lukas #6 · 2020-03-06 14:10:37

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

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”并作为静态方法进行调用。

lou #7 · 2020-03-06 14:10:38

这是我使用堆栈的解决方案。 它简短明了。

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());
}
tuca #8 · 2020-03-06 14:10:40

如果我忽略了某些内容,请原谅我,但为什么不做一些简单的事情,例如for循环?:

public static List <int []> ListOfPairs (int [] items)
{
    List <int> output = new List <int>();
    for (int i=0; i < items.Length-1; i++)
    {
        Int [] pair = new int [2];
        pair [0]=items [i];
        pair [1]=items [i+1];
        output.Add (pair);
    }
    return output;
}
tazhon #9 · 2020-03-06 14:10:41

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来说应该是黄金。 也就是说,它仍然没有表现。

yehudi #10 · 2020-03-06 14:10:42

像这样的东西:

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;
    }
}
lionel #11 · 2020-03-06 14:10:44

备用Window实现,使用最后一对存储先前的值:

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( );
}
saxon #12 · 2020-03-06 14:10:46

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

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

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

我认为您对简单的迭代器实现并没有真正改善这里的答案,对于我来说,这似乎也是最自然的(从事物的外观来看,这也是海报的达尔比克!)。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册