5

これは好奇心からこの質問をしたいのですが...

これが私のコードです:

for (int i = 0; i < myList.Count - 1; ++i)
{
    for (int j = i+1; j < myList.Count; ++j)
    {
        DoMyStuff(myList[i], myList[j]);
    }
}

非常に単純なループですが、明らかにリストでのみ機能します...しかし、私は疑問に思っていました...コレクションのタイプから独立させるために、このループをどのようにコーディングできますか(IEnumerableから派生します...)私の最初の考え:

IEnumerator it1 = myList.GetEnumerator();
while (it1.MoveNext())
{
    IEnumerator it2 = it1; // this part is obviously wrong
    while (it2.MoveNext())
    {
        DoMyStuff(it1.Current, it2.Current);
    }
}
4

6 に答える 6

3

列挙子には n 番目の要素を取得する効率的な方法がないため、列挙可能な要素をリストにコピーしてから、既存のコードを使用することをお勧めします。

void CrossMap<T>(IEnumerable<T> enumerable)
{
    List<T> myList = enumerable.ToList();

    for (int i = 0; i < myList.Count - 1; ++i)
    {
        for (int j = i+1; j < myList.Count; ++j)
        {
            DoMyStuff(myList[i], myList[j]);
        }
    }
}

ただし、いくつかのコレクション型で実行できる、かなりトリッキーなハックがあります。BCL の一部のコレクション型の列挙子は、参照型ではなく値型として宣言されているため、列挙子の状態を別の変数にコピーすることで、その状態の暗黙的な複製を作成できます。

// notice the struct constraint!
void CrossMap<TEnum, T>(TEnum enumerator) where TEnum : struct, IEnumerator<T>
{
    while (enumerator.MoveNext())
    {
        TEnum enum2 = enumerator;    // value type, so this makes an implicit clone!
        while (enum2.MoveNext())
        {
            DoMyStuff(enumerator.Current, enum2.Current);
        }
    }
}

// to use (you have to specify the type args exactly)
List<int> list = Enumerable.Range(0, 10).ToList();
CrossMap<List<int>.Enumerator, int>(list.GetEnumerator());

これは非常にわかりにくく、非常に使いにくいため、パフォーマンスとスペースが重要な場合にのみこれを行う必要があります。

于 2012-12-04T13:09:41.970 に答える
1

拡張メソッドがCount()ありElementAt(int)、 で宣言されていIEnumerable<T>ます。これらは名前空間で宣言されておりSystem.Linq、C# 3 以降のバージョンの C# を使用している場合、既定で .cs ファイルに含まれている必要があります。つまり、次のようにするだけです。

for (int i = 0; i < myList.Count() - 1; ++i)
{
  for (int j = i+1; j < myList.Count(); ++j)
  {
    DoMyStuff(myList.ElementAt(i), myList.ElementAt(j));
  }
}

ただし、これらはメソッドであり、反復中に何度も呼び出されることに注意してください。そのため、次のように結果を変数に保存することをお勧めします。

var elementCount = myList.Count();
for (int i = 0; i < elementCount - 1; ++i)
{
  var iElement = myList.ElementAt(i);
  for (int j = i+1; j < elementCount; ++j)
  {
    DoMyStuff(iElement, myList.ElementAt(j));
  }
}

また、適格な要素のすべてのペアを選択する LINQ を試してから、次のようなシンプルなforeach処理を呼び出すこともできます。

var result = myList.SelectMany((avalue, aindex) => 
               myList.Where((bvalue, bindex) => aindex < bindex)
                     .Select(bvalue => new {First = avalue, Second = bvalue}));

foreach (var item in result)
{
  DoMyStuff(item.First, item.Second);
}
于 2012-12-04T13:06:52.097 に答える
1

これは、遅延パラダイムを真に使用してIEnumerable、単一のIEnumerable入力から重複しない組み合わせのストリームを生成する方法です。最初のペアはすぐに返されます (リストのキャッシュはありません)が、外側の列挙子を前方に移動するたびに発生する操作中に遅延が増加します (非常に高い値の n または非常に高価なIEnumerables を除いて、まだ感知できません) 。Skip(n)

public static IEnumerable<Tuple<T, T>> Combinate<T>(this IEnumerable<T> enumerable) {
    var outer = enumerable.GetEnumerator();
    var n = 1;
    while (outer.MoveNext()) {
        foreach (var item in enumerable.Skip(n))
            yield return Tuple.Create(outer.Current, item);
        n++;
    }
}

あなたのケースでそれを使用する方法は次のとおりです。

foreach(var pair in mySource.Combinate())
    DoMyStuff(pair.Item1, pair.Item2);

あとがき

の「n 番目」の要素を取得する効率的な方法がないことは、誰もが (ここと他の場所で) 指摘していIEnumerableます。これはIEnumerable、基礎となるソース コレクションさえ存在する必要がないためです。たとえば、次のばかげた小さな関数は、実験用の値を消費できる限り迅速に動的に生成し、任意のカウントではなく、指定された期間継続します。

public static IEnumerable<double> Sample(double milliseconds, Func<double> generator) {
    var sw = new Stopwatch();
    var timeout = TimeSpan.FromMilliseconds(milliseconds);
    sw.Start();
    while (sw.Elapsed < timeout)
        yield return generator();
}
于 2012-12-04T13:34:30.273 に答える
0

あまり効率的ではありませんが、読みやすいです:

int i = 0;
foreach( var item1 in myList)
{
    ++i;
    foreach( var item2 in myList.Skip(i))
        DoMyStuff(item1, item2);
}
于 2012-12-04T13:33:35.410 に答える
0

IEnumerable<T>インデックス作成操作のデリゲートに対して書き込み、渡します。

public static void DoStuff<T>(IEnumerable<T> seq, Func<int, T> selector)
{
    int count = seq.Count();
    for (int i = 0; i < count - 1; ++i)
    {
        for (int j = i+1; j < count; ++j)
        {
            DoMyStuff(selector(i), selector(j));
        }
    }
}

次を使用して呼び出すことができます。

List<T> list = //whatever
DoStuff(list, i => list[i]);

コレクション引数を に制限すると、拡張メソッドを使用する代わりにプロパティをICollection<T>使用できます。CountCount()

于 2012-12-04T13:13:45.637 に答える
0

IEnumerable.Skip() を使用してかなり簡潔に行うことができ、リストが十分に短い場合は、リストを配列にコピーするよりもかなり高速になることさえあります。ただし、十分なサイズのリストのコピーよりもはるかに遅くなるはずです。

配列へのコピーがより効率的になる場所を確認するには、さまざまなサイズのリストでいくつかのタイミングを行う必要があります。

これがコードです。列挙型を 2 回繰り返していることに注意してください。これは、列挙型が正しく実装されていれば問題ありません。

static void test(IEnumerable<int> myList)
{
    int n = 0;

    foreach (int v1 in myList)
    {
        foreach (int v2 in myList.Skip(++n))
        {
            DoMyStuff(v1, v2);
        }
    }
}
于 2012-12-04T13:35:33.710 に答える