2009-02-23 10 views
37

如果我有一個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

這似乎可以與Jon Skeet的`SmartEnumerator`共享很多實現,它告訴你一個項目是否是列表中的最後一個。 http://msmvps.com/blogs/jon_skeet/archive/2007/07/27/smart-enumerations.aspx – 2010-08-02 18:30:13

+0

作爲參考,這個函數在F#中被稱爲'窗口':http://stackoverflow.com/questions/8874901/is-the-an-equivalent-to-f-seq-windowed-in-c – Benjol 2014-12-12 06:17:27

回答

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的黃金。也就是說,它仍然沒有性能。

+5

不是最高性能的?這是一個O(n * n)實現!對於一個小的10個項目列表,遍歷整個列表20次 – configurator 2009-02-23 14:58:09

+0

這是真的,但它也是兩行(實際)代碼,並且是一個明顯簡單的實現。這取決於OP是否需要快速解決方案 - 也許他只需要在幾十個項目的列表上進行這項操作。 – mquander 2009-02-23 15:04:16

2

擴大對previous answer通過顯式使用所傳遞的迭代器,以避免爲O(n )方法:

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,前擴展方法中,刪除 「這」 從輸入參數並稱之爲靜態方法。

+0

這不會返回問題要求的結果。 `Enumerable.Range(1,5).Tuples(2)`返回`{{1,2},{3,4},{5}}`而不是所需的{{1,2},{2, 3},{3,4},{4,5}}`這是一個滑動窗口。 – 2016-04-27 08:11:22

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

我想知道`yield return ...(previous,previous = ...)`中的執行順序。 C#語言是否保證在第二個參數被評估之前準備第一個參數? – stakx 2013-01-27 09:28:53

0

的F#Seq模塊定義成對函數超過IEnumerable<T>,但這種功能不在.NET框架。

如果它已經在.NET框架中,而不是返回對,它可能會接受一個選擇器功能,由於缺乏像C#和VB語言的元組的支持。

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

我不認爲任何的答案在這裏真的對你簡單的迭代器實現,它似乎是最natural給我改善(和事物的外表海報dahlbyk!)太。

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

在.NET 4這就變得更容易: -

 var input = new[] { "a", "b", "c", "d", "e", "f" }; 
     var result = input.Zip(input.Skip(1), (a, b) => Tuple.Create(a, b)); 
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(); 
} 
0

只是爲了方便起見,這裏是@ 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); 
    } 
} 
3

有點遲到了,但作爲替代所有這些擴展方法,一個可能使用一個實際的「滑動體」 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)); 
} 
2

下面是一個使用堆棧我的解決方案。它簡短而簡潔。

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