Параллельная итерация в С# или счетчике переходов

Если у меня есть 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 Framework функции, которые делают то же самое и делают это не только для пар, но и для кортежей любого размера. ИМХО должен быть хороший способ сделать такие операции скользящего окна.

Я использую С# 2.0, и могу себе представить, что с С# 3.0 (w/LINQ) есть больше (и более приятных) способов сделать это, но меня в первую очередь интересуют решения С# 2.0. Хотя, я также буду признателен за решения С# 3.0.

Ответ 1

В .NET 4 это становится еще проще: -

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

Ответ 2

Вместо того, чтобы требовать тип кортежа (пары), почему бы просто не принять селектор:

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

Ответ 3

Самый простой способ - использовать ReactiveExtensions

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

и сделайте себе метод расширения для набора bash this вместе

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

Ответ 4

Немного поздно для вечеринки, но в качестве альтернативы всем этим методам расширения можно использовать фактическое "скользящее" 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));
}

Ответ 5

Развернувшись на предыдущем ответе, чтобы избежать подхода 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;
  }
}

Для С# 2 перед процедурами расширения отпустите "this" из входного параметра и вызовите как статический метод.

Ответ 6

Решение С# 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);
}

Это не самая результативная в мире, но на нее приятно смотреть.

Действительно, единственное, что делает это решение С# 3.0, это конструкция .Skip.Take, поэтому, если вы просто измените это на добавление элементов в этом диапазоне к списку вместо этого, оно должно быть золотым для 2.0. Тем не менее, он все еще не работает.

Ответ 7

Вот мое решение, используя 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());
}

Ответ 8

Альтернативная реализация 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( );
}

Ответ 9

Модуль F # Seq определяет парную функцию над IEnumerable<T>, но эта функция не находится в платформе .NET.

Если бы он был уже в платформе .NET, вместо того, чтобы возвращать пары, он, вероятно, согласился бы с селекторной функцией из-за отсутствия поддержки кортежей на таких языках, как С# и VB.

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

Я не думаю, что любой из ответов здесь действительно улучшит вашу простую реализацию итератора, которая казалась мне самой natural мне (и плакат dahlbyk по внешнему виду вещей!) тоже.

Ответ 10

Что-то вроде этого:

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

Ответ 11

Просто для удобства, вот версия ответа @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);
    }
}