Repetición por pares en C # o ventana deslizante enumerador

Si tengo un IEnumerable como:

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

Me gustaría pasar por todos los pares de elementos consecutivos (ventana deslizante de tamaño 2). Cuál podría ser

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

Mi solución fue esta

  public static IEnumerable<Pair> Pairs(IEnumerable enumerable) { IEnumerator e = enumerable.GetEnumerator(); e.MoveNext(); T current = e.Current; while ( e.MoveNext() ) { T next = e.Current; yield return new Pair(current, next); current = next; } } // used like this : foreach (Pair pair in IterTools.Pairs(items)) { System.Out.PrintLine("{0}, {1}", pair.First, pair.Second) } 

Cuando escribí este código, me pregunté si ya hay funciones en .NET Framework que hagan lo mismo y que lo hagan no solo para pares sino para tuplas de cualquier tamaño. En mi humilde opinión, debería haber una buena manera de hacer este tipo de operaciones de ventana deslizante.

Uso C # 2.0 y puedo imaginar que con C # 3.0 (con LINQ) hay más (y mejores) formas de hacerlo, pero estoy interesado principalmente en las soluciones C # 2.0. Sin embargo, también apreciaré las soluciones C # 3.0.

En .NET 4, esto se vuelve aún más fácil: –

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

En lugar de requerir un tipo de tupla (par), ¿por qué no simplemente aceptar un selector?

 public static IEnumerable Pairwise(this IEnumerable source, Func 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); } } 

Lo que le permite omitir el objeto intermedio si lo desea:

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

O puede usar un tipo anónimo:

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

La forma más fácil es usar ReactiveExtensions

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

y hazte un método de extensión para kit bash esto juntos

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

Un poco tarde para la fiesta, pero como alternativa a todos estos métodos de extensión, uno podría usar una Collection “deslizante” real para contener (y descartar) los datos.

Aquí hay uno que terminé haciendo hoy:

 public class SlidingWindowCollection : ICollection { private int _windowSize; private Queue _source; public SlidingWindowCollection(int windowSize) { _windowSize = windowSize; _source = new Queue(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 methods to _source. } 

Uso:

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

Expandir la respuesta anterior para evitar el enfoque O (n 2 ) mediante el uso explícito del iterador pasado:

 public static IEnumerable> Tuples(this IEnumerable 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(); 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; } } 

Para C # 2, antes de los métodos de extensión, suelte "this" del parámetro de entrada y llame como método estático.

Aquí está mi solución usando una stack. Es corto y conciso.

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

Solución C # 3.0 (lo siento 🙂

 public static IEnumerable> Tuples(this IEnumerable 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); } 

Este no es el mejor rendimiento del mundo, pero es agradable de ver.

En realidad, lo único que hace de esto una solución C # 3.0 es la construcción .Skip.Take, así que si simplemente cambias eso para agregar los elementos de ese rango a una lista, debería ser oro para 2.0. Dicho eso, todavía no está funcionando.

Solo por comodidad, aquí hay una versión sin selector de la respuesta de @ dahlbyk.

 public static IEnumerable> Pairwise(this IEnumerable 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); } } 

Implementación de Pairs alternativas, utilizando el último par para almacenar el valor anterior:

 static IEnumerable> Pairs( IEnumerable collection ) { Pair 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 ); } } 

Implementación simple de Window (solo segura para uso privado, si la persona que llama no guarda las matrices devueltas, vea la nota):

 static IEnumerable Window( IEnumerable 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; } } 

Ejemplo de uso:

 for( int i = 0; i <= items.Length; ++i ) { Console.WriteLine( "Window size {0}:", i ); foreach( string[] window in IterTools.Window( items, i ) ) Console.WriteLine( string.Join( ", ", window ) ); Console.WriteLine( ); } 

El módulo F # Seq define la función pairwise sobre IEnumerable , pero esta función no está en .NET framework.

Si ya estuviera en el marco de .NET, en lugar de devolver pares, probablemente aceptaría una función de selector debido a la falta de soporte para tuplas en idiomas como C # y VB.

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

No creo que ninguna de las respuestas aquí realmente mejore en la implementación simple de tu iterador, que me pareció la más natural (¡y el póster dahlbyk por el aspecto de las cosas!) También.

Algo como esto:

 public static IEnumerable Pairwise(this IEnumerable enumerable, Func selector) { var previous = enumerable.First(); foreach (var item in enumerable.Skip(1)) { yield return selector(previous, item); previous = item; } } 

Perdóname si estoy pasando por alto algo, pero ¿por qué no algo simple, como un bucle for?

 public static List  ListOfPairs (int [] items) { List  output = new List (); 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; }