Itération par paires en C # ou enumerator de fenêtre glissante

Si j’ai un IEnumerable comme:

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

Je voudrais passer en boucle toutes les paires d’éléments consécutifs (fenêtre coulissante de taille 2). Ce qui serait

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

Ma solution était ceci

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

Lorsque j’ai écrit ce code, je me demandais s’il existe déjà des fonctions dans le framework .NET qui font la même chose et qui le font non seulement pour les paires, mais pour tous les tuples de taille. IMHO il devrait y avoir une belle façon de faire ce genre d’opérations de fenêtre coulissante.

J’utilise C # 2.0 et je peux imaginer qu’avec C # 3.0 (w / LINQ), il y a plus de façons (et plus) de le faire, mais je m’intéresse surtout aux solutions C # 2.0. Cependant, j’apprécierai également les solutions C # 3.0.

Dans .NET 4, cela devient encore plus facile: –

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

Plutôt que d’exiger un type de tuple (paire), pourquoi ne pas accepter un sélecteur:

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

Ce qui vous permet d’ignorer l’object intermédiaire si vous le souhaitez:

 ssortingng[] items = new ssortingng[] { "a", "b", "c", "d" }; var pairs = items.Pairwise((x, y) => ssortingng.Format("{0},{1}", x, y)); foreach(var pair in pairs) Console.WriteLine(pair); 

Ou vous pouvez utiliser un type anonyme:

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

Le moyen le plus simple est d’utiliser ReactiveExtensions

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

et faites de vous-même une méthode d’extension pour assembler ce kit

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

Un peu tard pour la soirée, mais comme alternative à toutes ces méthodes d’extension, on pourrait utiliser une collection “glissante” pour contenir (et rejeter) les données.

En voici un que j’ai fini par faire aujourd’hui:

 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. } 

Usage:

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

Étendre la réponse précédente pour éviter l’approche O (n 2 ) en utilisant explicitement l’iterator passé:

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

Pour C # 2, avant les méthodes d'extension, supprimez le "this" du paramètre d'entrée et appelez-le comme méthode statique.

Voici ma solution en utilisant une stack. C’est court et concis.

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

Solution C # 3.0 (désolé 🙂

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

Ce n'est pas le plus performant au monde, mais c'est certainement agréable à regarder.

En réalité, la seule chose qui en fait une solution C # 3.0 est la construction .Skip.Take, donc si vous changez cela en ajoutant les éléments de cette plage à une liste, elle devrait être dorée pour 2.0. Cela dit, ce n'est toujours pas performant.

Pour plus de commodité, voici une version sans sélecteur de la réponse 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); } } 

Implémentation de Pairs alternatives, en utilisant la dernière paire pour stocker la valeur précédente:

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

Implémentation de Window simple (uniquement pour un usage privé, si l’appelant ne sauvegarde pas les tableaux renvoyés; voir note):

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

Exemple d’utilisation:

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

Le module F # Seq définit la fonction par paire sur IEnumerable , mais cette fonction ne se trouve pas dans le framework .NET.

S’il était déjà dans le framework .NET, au lieu de renvoyer des paires, il accepterait probablement une fonction de sélecteur en raison du manque de prise en charge des tuples dans les langages tels que C # et VB.

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

Je ne pense pas que les réponses ici améliorent vraiment votre implémentation d’iterator simple, qui m’a semblé la plus naturelle (et l’affiche dahlbyk par l’apparence des choses!) Aussi .

Quelque chose comme ça:

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

Pardonnez-moi si j’ignore quelque chose, mais pourquoi pas quelque chose de simple, comme une boucle?

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