55

次のような 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 ソリューションも高く評価します。

4

14 に答える 14

59

.NET 4 では、これはさらに簡単になります:-

var input = new[] { "a", "b", "c", "d", "e", "f" };
var result = input.Zip(input.Skip(1), (a, b) => Tuple.Create(a, b));
于 2010-08-02T18:13:40.753 に答える
43

タプル(ペア)タイプを要求するのではなく、セレクターを受け入れるだけではどうでしょうか。

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

更新:これを実際のプロジェクトに実装し、代わりにC#7.0を使用しました。ValueTuple

public static IEnumerable<(T, T)> Pairwise<T>(this IEnumerable<T> source)
{
    var previous = default(T);
    using (var it = source.GetEnumerator())
    {
        if (it.MoveNext())
            previous = it.Current;

        while (it.MoveNext())
            yield return (previous, previous = it.Current);
    }
}
于 2009-10-17T05:19:45.757 に答える
12

最も簡単な方法は 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();
}
于 2013-03-13T08:29:49.100 に答える
5

パーティーには少し遅れましたが、これらすべての拡張メソッドの代わりに、実際の「スライド」を使用し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));
}
于 2014-02-08T22:30:09.110 に答える
4

これがスタックを使用した私のソリューションです。短く簡潔です。

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

同じ概念を採用して、アイテムを元に戻す必要がなく、さらに簡単なキューを使用できます。

var queue = new Queue<string>(items);

while (queue.Count > 1)
{
   Console.WriteLine("{0},{1}", queue.Dequeue(), queue.Peek());
}

パフォーマンスについて一言:

タスクが実際のアプリケーションでボトルネックを引き起こしていることがわかっていない限り、真に最速の方法は何かを考え出す価値はないということを認識することが重要だと思います。代わりに、あなたのために仕事をするコードを書いてください。また、覚えやすいコードを使用して、次に必要になったときに手から簡単に流せるようにします。

それにもかかわらず、10.000.000 のランダムな文字列のパフォーマンス データを気にする場合:

Run #1
  InputZip             00:00:00.7355567
  PairwiseExtension    00:00:00.5290042
  Stack                00:00:00.6451204
  Queue                00:00:00.3245580
  ForLoop              00:00:00.7808004
  TupleExtension       00:00:03.9661995

Run #2
  InputZip             00:00:00.7386347
  PairwiseExtension    00:00:00.5369850
  Stack                00:00:00.6910079
  Queue                00:00:00.3246276
  ForLoop              00:00:00.8272945
  TupleExtension       00:00:03.9415258

Jon Skeet のマイクロ ベンチマーク ツールを使用してテスト済み。

テストのソースを確認したい場合は、ここにアクセスしてください: gist here

于 2014-09-25T20:59:44.390 に答える
2

渡されたイテレータを明示的に使用して、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;
  }
}

C#2の場合、拡張メソッドの前に、入力パラメーターから「this」を削除し、静的メソッドとして呼び出します。

于 2009-02-23T13:50:42.453 に答える
2

このようなもの:

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;
    }
}
于 2010-03-30T07:21:31.443 に答える
2

何かを見落としていたらすみませんが、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;
}
于 2018-08-25T05:18:20.513 に答える
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 のゴールデンになるはずです。とは言っても、それはまだパフォーマンスではありません。

于 2009-02-23T13:27:22.320 に答える
0

F#Seqモジュールでは、 に対するペアワイズ関数が定義IEnumerable<T>されていますが、この関数は .NET フレームワークにはありません。

既に .NET フレームワークにある場合、C# や VB などの言語ではタプルがサポートされていないため、ペアを返す代わりにセレクター関数を受け入れる可能性があります。

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

ここでの答えのどれも、単純なイテレータの実装を実際に改善するとは思わない.

于 2010-01-10T18:01:28.423 に答える
0

新しい C# 言語では、次のようなことができます。

        var pairlist = new (string, string)[] { ("a", "b"), ("b", "c"), ("c", "d") };

        foreach (var pair in pairlist)
        {
            //do something with pair.Item1 & pair.Item2
于 2022-01-19T13:22:23.543 に答える