6

特定の列挙型の順列をできるだけ単純に返すメソッドを作成しようとしました。コード:

using System.Collections.Generic;

public static partial class Permutable {
    static IEnumerable<IEnumerable<T>> PermuteIterator<T>(
        IEnumerable<T> source, int offset) {
        var count=0;

        foreach(var dummy in source)
            if(++count>offset)
                foreach(
                    var sequence in
                        Permutable.PermuteIterator(
                            source.Exchange(offset, count-1), 1+offset)
                    )
                    yield return sequence;

        if(offset==count-1)
            yield return source;
    }

    public static IEnumerable<IEnumerable<T>> AsPermutable<T>(
        this IEnumerable<T> source) {
        return Permutable.PermuteIterator(source, 0);
    }

    public static IEnumerable<T> Exchange<T>(
        this IEnumerable<T> source, int index1, int index2) {
        // exchange elements at index1 and index2
    }
}

Iterator ブロック内のコードが簡素化されたので、単純に LINQ の単一のクエリ式にしようとしています。

このコードでネストされた には再帰があり、foreach別の可能性としてはforeach;の外側でも生成されます。これは、クエリ構文で書き直すのが難しい部分です。

私はこの答えを読みました:

C# 文字列順列

しかし、それは私にとっての解決策ではないと思います..

いろいろやってみましたが、そう簡単にはいかないと思います。どうすればそれを成し遂げることができますか?

Exchange方法は別の問題であり、私は質問をしました:

1回だけ繰り返して列挙項目を交換する方法は?

しかし、ここでは問題ではないと思います..)

4

3 に答える 3

3

編集1:

再帰のない単一行のソリューション

コア メソッドを再作成したため (この回答の下の以前のソリューションから)、再帰的ではなくなりました。これから単一行のソリューションを作成するのは簡単です。

Enumerableただし、それを行うにはメソッドと拡張メソッドを使用する必要がありました。これらがなければ、これを行うことは不可能だと思います。

class Permutator
{
    private static IEnumerable<IEnumerable<int>> CreateIndices(int length)
    {
        var factorial = Enumerable.Range(2, length - 1)
            .Aggregate((a, b) => a * b);

        return (from p in Enumerable.Range(0, factorial)
                // creating module values from 2 up to length
                // e.g. length = 3: mods = [ p%2, p%3 ]
                // e.g. length = 4: mods = [ p%2, p%3, p%4 ]
                let mods = Enumerable.Range(2, length - 1)
                    .Select(m => p % m).ToArray()
                select (
                    // creating indices for each permutation
                    mods.Aggregate(
                        new[] { 0 },
                        (s, i) =>
                            s.Take(i)
                            .Concat(new[] { s.Length })
                            .Concat(s.Skip(i)).ToArray())
                    ));
    }

    public static IEnumerable<IEnumerable<T>> Get<T>(IEnumerable<T> items)
    {
        var array = items.ToArray();
        return from indices in CreateIndices(array.Length)
                select (from i in indices select array[i]);
    }
}

これで最終的な解決策

その結果がこの怪物です。

class Permutator
{
    public static IEnumerable<IEnumerable<T>> Get<T>(IEnumerable<T> items)
    {
        return
            from p in Enumerable.Range(0,
                Enumerable.Range(2, items.Count() - 1)
                    .Aggregate((a, b) => a * b))
            let mods = Enumerable.Range(2, items.Count() - 1)
                .Select(m => p % m).ToArray()
            select mods.Aggregate(
                items.Take(1).ToArray(),
                (s, i) =>
                    s.Take(i)
                    .Concat(items.Skip(s.Length).Take(1))
                    .Concat(s.Skip(i)).ToArray());
    }
}

以前のソリューション

私はあなたが探しているものかもしれないものを作成しました:

class Permutator
{
    private static IEnumerable<IEnumerable<int>> CreateIndices(int length)
    {
        return (from p in Enumerable.Range(0, length)
                select (
                    from s in Permutator.CreateIndices(length - 1)
                              .DefaultIfEmpty(Enumerable.Empty<int>())
                    select s.Take(p)
                           .Concat(new[] { length - 1 })
                           .Concat(s.Skip(p))
                    ))
                    .SelectMany(i => i);
    }

    public static IEnumerable<IEnumerable<T>> Get<T>(IEnumerable<T> items)
    {
        var array = items.ToArray();
        return from indices in CreateIndices(array.Length)
                select (from i in indices select array[i]);
    }
}

使用方法の例:

var items = new[] { "0", "1", "2" };
var p = Permutator.Get(items);
var result = p.Select(a=>a.ToArray()).ToArray();

使い方

核となるのはCreateIndices方法です。順列ごとに、ソース要素のインデックスを含むシーケンスを作成します。

例を挙げて説明するのが最善です:

CreateIndices(0);
// returns no permutations

CreateIndices(1);
// returns 1 permutation
// [ 0 ]

CreateIndices(2);
// returns 2 permutations
// [ 1, 0 ]
// [ 0, 1 ]

CreateIndices(3);
// returns 6 permutations
// [ 2, 1, 0 ]
// [ 2, 0, 1 ]
// [ 1, 2, 0 ]
// [ 0, 2, 1 ]
// [ 1, 0, 2 ]
// [ 0, 1, 2 ]

これは、列挙可能な拡張機能と LINQ 構文クエリのみに基づく再帰的な方法です。

再帰の考え方は、各レベルが前のレベルに基づいて構築されるということです。

CreateIndices(n)n-1によって返される順列にCreateIndices(n-1)、使用可能なすべての位置に要素を追加します。

再帰のルートは、CreateIndices(0)順列の空のセットを返しています。

順を追って説明します:CreateIndices(3)


1. の結果を作成することから始めましょうCreateIndices(0):

  • 空の


2. 次に、次の結果CreateIndices(1):

  • 位置 0 [ 0 ]で、前の順列のそれぞれに要素0(n-1) を 追加します


3.次に、の結果CreateIndices(2)

  • 位置 0 [ 1, 0 ]で、前の順列のそれぞれに要素1(n-1) を 追加します
  • 位置 1 [ 0, 1 ]で、前の順列のそれぞれに要素1(n-1) を 追加します。


4. 次に、CreateIndices(3)

  • 位置 0 [ 2, 1, 0 ] [ 2, 0, 1 ] で、前の順列のそれぞれに要素2(n-1) を 追加します。

  • 位置 1 [ 1, 2, 0 ] [ 0, 2, 1 ] で、前の順列のそれぞれに要素2(n-1) を 追加します。

  • 位置 2 [ 1, 0, 2 ] [ 0, 1, 2 ] で、前の順列のそれぞれに要素2(n-1) を 追加します。

次は何が起こる

各順列のインデックスを取得したので、それらを使用して値の実際の順列を構築できます。これがジェネリックGetメソッドの機能です。

Getまた、このメソッドは、ソース シーケンスを配列に実体化する唯一のメソッドであることに注意してください。はCreateIndices単なる列挙子であり、実際のオブジェクトはありません...したがって、シーケンスを列挙するときとGet、ソース シーケンスの配列を作成するために を呼び出すときにコストのみを支払います。

これは、例を呼び出した後Get、結果を使用できるように結果を具体化する必要があった理由を説明しています。

var items = new[] { "0", "1", "2" };
var p = Permutator.Get(items); // pay to create array from source elements
var result = p.Select(a => a.ToArray() // pay to create arrays for each of the permutations
    ).ToArray(); // pay to get the permutations

これにより、順列の半分を列挙するだけで、コストの半分だけを支払うことができます。

于 2013-06-13T00:05:09.140 に答える
2

私は年をとっていますが、必要のない再帰は好きではありません。

とにかくソースを反復する必要があるため、単純に配列に格納します。何を何と交換するかをプログラムに指示する「swappings」配列を追跡します。インデックスはスワップのソース ポジションであり、インデックスはデスティネーション ポジションです。

必要に応じて、サブ順列を行うこともできます。

多くの苦痛に本当に慣れている場合は、スワッピングを複雑な Exchange IEnumerable に変更することもできると思います。これは、多くの複雑さを加えると同時に、すべてを遅くする良い方法だと思います。:-)

配列も再利用します。「src」が大きい場合、毎回すべてを繰り返すのはばかげているように思えます。これにより、データのコピーと再帰を実行する時間が大幅に節約されます。それは非常に効率的です。

public static IEnumerable<IEnumerable<T>> Permutations<T>(this IEnumerable<T> src)
{
    T[] array = src.ToArray();

    // Initialize
    int[] swappings = new int[array.Length];
    for (int j = 0; j < array.Length; ++j)
    {
        swappings[j] = j;
    }

    yield return array;

    int i = swappings.Length - 2;
    while (i >= 0)
    {
        int prev = swappings[i];
        Swap(array, i, prev);
        int next = prev + 1;
        swappings[i] = next;
        Swap(array, i, next);

        yield return array;

        // Prepare next
        i = swappings.Length - 1;
        while (i >= 0 && swappings[i] == array.Length - 1)
        {
            // Undo the swap represented by permSwappings[i]
            Swap(array, i, swappings[i]);
            swappings[i] = i;
            i--;
        }
    }
}

private static void Swap<T>(T[] array, int i, int next)
{
    var tmp = array[i];
    array[i] = array[next];
    array[next] = tmp;
}
于 2013-06-12T10:28:42.533 に答える