69

2 つの配列が与えられ、考えられるすべての組み合わせを文字列a(i) b(j) c(k) n(p) として生成するArray1 = {a,b,c...n}Array2 = {10,20,15....x}はどうすればよいですか?

1 <= i <= 10,  1 <= j <= 20 , 1 <= k <= 15,  .... 1 <= p <= x

そのような:

a1 b1 c1 .... n1  
a1 b1 c1..... n2  
......  
......  
a10 b20 c15 nx (last combination)

したがって、すべての組み合わせの総数 = の要素の積array2 = (10 X 20 X 15 X ..X x)

2 番目の配列が最初の配列の各要素の上限を定義するデカルト積に似ています。

固定数の例、

    Array x =  [a,b,c]
    Array y =  [3,2,4] 

したがって、3*2*4 = 24 通りの組み合わせがあります。結果は次のようになります。

    a1 b1 c1  
    a1 b1 c2  
    a1 b1 c3  
    a1 b1 c4  

    a1 b2 c1  
    a1 b2 c2  
    a1 b2 c3  
    a1 b2 c4


    a2 b1 c1  
    a2 b1 c2  
    a2 b1 c3  
    a2 b1 c4  

    a2 b2 c1  
    a2 b2 c2  
    a2 b2 c3  
    a2 b2 c4


    a3 b1 c1  
    a3 b1 c2  
    a3 b1 c3  
    a3 b1 c4  

    a3 b2 c1  
    a3 b2 c2  
    a3 b2 c3  
    a3 b2 c4 (last)
4

11 に答える 11

164

確実なこと。LINQ でこれを行うのは少し難しいですが、標準のクエリ演算子のみを使用することで確実に可能です。

更新: これは、2010 年 6 月 28 日月曜日の私のブログの主題です。素晴らしい質問をありがとう。また、私のブログへのコメント投稿者は、私が提供したクエリよりもさらに洗練されたクエリがあることに気付きました。ここでコードを更新して使用します。

トリッキーな部分は、任意の数のシーケンスのデカルト積を作成することです。それに比べれば、文字の「Zipping」は些細なことです。これを調べて、その仕組みを確実に理解する必要があります。各パーツは十分にシンプルですが、それらを組み合わせる方法には慣れが必要です。

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>()};
    return sequences.Aggregate(
        emptyProduct,
        (accumulator, sequence) => 
            from accseq in accumulator 
            from item in sequence 
            select accseq.Concat(new[] {item})                          
        );
 }

これがどのように機能するかを説明するには、まず「累積」操作が何をしているのかを理解してください。最も単純な累積操作は、「このシーケンス内のすべてを一緒に追加する」です。その方法は次のとおりです。ゼロから始めます。シーケンス内の各アイテムについて、アキュムレータの現在の値は、アイテムとアキュムレータの以前の値の合計に等しくなります。これまでの合計と現在のアイテムに基づいて合計を累積する代わりに、デカルト積を累積していることを除いて、同じことを行っています。

これを行う方法は、次の 2 つのもののデカルト積を計算する演算子が既に LINQ にあるという事実を利用することです。

from x in xs
from y in ys
do something with each possible (x, y)

アキュムレータのデカルト積を入力シーケンスの次の項目で繰り返し取得し、結果を少し貼り付けることで、デカルト積を生成できます。

アキュムレータの値について考えてみましょう。説明のために、アキュムレータの値を、それに含まれるシーケンス演算子の結果として示します。これは、アキュムレータに実際に含まれているものではありません。アキュムレータに実際に含まれているのは、これらの結果を生成する演算子です。ここでの操作全体は、シーケンス演算子の巨大なツリーを構築するだけであり、その結果がデカルト積です。ただし、最終的なデカルト積自体は、クエリが実行されるまで実際には計算されません。 説明のために、各段階での結果を示しますが、これには実際に演算子が含まれていることを覚えておいてください。それらの結果を生み出します。

シーケンスのシーケンスのデカルト積を取得しているとします{{1, 2}, {3, 4}, {5, 6}}。アキュムレータは、1 つの空のシーケンスを含むシーケンスとして開始します。{ { } }

最初の累積では、accumulator は { { } } であり、item は {1, 2} です。これを行います:

from accseq in accumulator
from item in sequence 
select accseq.Concat(new[] {item})

{ { } }したがって、 withのデカルト積を取得し、{1, 2}ペアごとに連結します: ペア({ }, 1)があるので、連結{ }{1}て を取得します{1}。ペア({ }, 2})があるので、 と を連結{ }{2}て を取得します{2}。したがって{{1}, {2}}、結果として得られます。

したがって、2 番目の累積では、accumulator は{{1}, {2}}で、item は{3, 4}です。繰り返しますが、これら 2 つのシーケンスのデカルト積を計算して、次を取得します。

 {({1}, 3), ({1}, 4), ({2}, 3), ({2}, 4)}

次に、それらの項目から、2 番目の項目を最初の項目に連結します。結果は、私たちが望むものである sequence{{1, 3}, {1, 4}, {2, 3}, {2, 4}}です。

今、私たちは再び蓄積します。アキュムレータのデカルト積{5, 6}を取得します

 {({ 1, 3}, 5), ({1, 3}, 6), ({1, 4}, 5), ...

次に、2 番目の項目を最初の項目に連結して取得します。

{{1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6} ... }

これで完了です。デカルト積を蓄積しました。

これで、任意の数のシーケンスのデカルト積を取得できるユーティリティ関数ができたので、残りは比較すると簡単です。

var arr1 = new[] {"a", "b", "c"};
var arr2 = new[] { 3, 2, 4 };
var result = from cpLine in CartesianProduct(
                 from count in arr2 select Enumerable.Range(1, count)) 
             select cpLine.Zip(arr1, (x1, x2) => x2 + x1);

これで、一連の文字列のシーケンスができました。1 行に 1 つの文字列のシーケンスです。

foreach (var line in result)
{
    foreach (var s in line)
        Console.Write(s);
    Console.WriteLine();
}

簡単!

于 2010-06-23T01:42:05.593 に答える
23
using System;
using System.Text;

public static string[] GenerateCombinations(string[] Array1, int[] Array2)
{
    if(Array1 == null) throw new ArgumentNullException("Array1");
    if(Array2 == null) throw new ArgumentNullException("Array2");
    if(Array1.Length != Array2.Length)
        throw new ArgumentException("Must be the same size as Array1.", "Array2");

    if(Array1.Length == 0)
        return new string[0];

    int outputSize = 1;
    var current = new int[Array1.Length];
    for(int i = 0; i < current.Length; ++i)
    {
        if(Array2[i] < 1)
            throw new ArgumentException("Contains invalid values.", "Array2");
        if(Array1[i] == null)
            throw new ArgumentException("Contains null values.", "Array1");
        outputSize *= Array2[i];
        current[i] = 1;
    }

    var result = new string[outputSize];
    for(int i = 0; i < outputSize; ++i)
    {
        var sb = new StringBuilder();
        for(int j = 0; j < current.Length; ++j)
        {
            sb.Append(Array1[j]);
            sb.Append(current[j].ToString());
            if(j != current.Length - 1)
                sb.Append(' ');
        }
        result[i] = sb.ToString();
        int incrementIndex = current.Length - 1;
        while(incrementIndex >= 0 && current[incrementIndex] == Array2[incrementIndex])
        {
                current[incrementIndex] = 1;
                --incrementIndex;
        }
        if(incrementIndex >= 0)
            ++current[incrementIndex];
    }
    return result;
}
于 2010-06-22T14:04:52.797 に答える
13

代替ソリューション:

ステップ 1: 文脈依存文法に一致するすべての文字列を生成する方法に関する私の一連の記事を読んでください。

http://blogs.msdn.com/b/ericlippert/archive/tags/grammars/

ステップ 2: 必要な言語を生成する文法を定義します。たとえば、文法を次のように定義できます。

S: a A b B c C
A: 1 | 2 | 3
B: 1 | 2
C: 1 | 2 | 3 | 4

明らかに、2 つの配列からその文法定義文字列を簡単に生成できます。次に、指定された文法ですべての文字列を生成するコードにそれをフィードします。これで完了です。あなたはすべての可能性を手に入れるでしょう。(必ずしもあなたが望む順序である必要はありません。念のため。)

于 2010-06-23T14:45:01.737 に答える
3

使用できるlinqベースではない別のソリューションの場合:

public class CartesianProduct<T>
    {
        int[] lengths;
        T[][] arrays;
        public CartesianProduct(params  T[][] arrays)
        {
            lengths = arrays.Select(k => k.Length).ToArray();
            if (lengths.Any(l => l == 0))
                throw new ArgumentException("Zero lenght array unhandled.");
            this.arrays = arrays;
        }
        public IEnumerable<T[]> Get()
        {
            int[] walk = new int[arrays.Length];
            int x = 0;
            yield return walk.Select(k => arrays[x++][k]).ToArray();
            while (Next(walk))
            {
                x = 0;
                yield return walk.Select(k => arrays[x++][k]).ToArray();
            }

        }
        private bool Next(int[] walk)
        {
            int whoIncrement = 0;
            while (whoIncrement < walk.Length)
            {
                if (walk[whoIncrement] < lengths[whoIncrement] - 1)
                {
                    walk[whoIncrement]++;
                    return true;
                }
                else
                {
                    walk[whoIncrement] = 0;
                    whoIncrement++;
                }
            }
            return false;
        }
    }

ここで使用方法の例を見つけることができます。

于 2011-08-12T10:51:25.430 に答える
3

.NET Framework 4.7.1 で追加されたを使用するEnumerable.Appendと、反復ごとに新しい配列を割り当てることなく、@EricLippert の回答を実装できます。

public static IEnumerable<IEnumerable<T>> CartesianProduct<T>
    (this IEnumerable<IEnumerable<T>> enumerables)
{
    IEnumerable<IEnumerable<T>> Seed() { yield return Enumerable.Empty<T>(); }

    return enumerables.Aggregate(Seed(), (accumulator, enumerable)
        => accumulator.SelectMany(x => enumerable.Select(x.Append)));
}
于 2020-09-18T14:34:49.823 に答える
2

完全なソース コードを提供するつもりはありません。これが背後にあるアイデアです。

次の方法で要素を生成できます。

A=(a1, a2, ..., an)and B=(b1, b2, ..., bn)(so Aand Beach hold nelements)と仮定します。

次に、再帰的に実行します。Aと aを取り、あなたのことをするメソッドを書きBます:

ABそれぞれに 1 つの要素 ( anresp.と呼ばれる) が含まれている場合はbn、1 から反復し、反復変数にbn連結するだけです。an

AおよびBそれぞれに複数の要素が含まれている場合、最初の要素 ( a1resp b1) を取得し、1 から 1 まで反復し、bn反復ステップごとに実行します。

  • のサブフィールドを使用してメソッドを再帰的に呼び出し、2 番目の要素、つまりrespAから開始します。再帰呼び出しによって生成されたすべての要素に対して、 concatenate 、反復変数、および再帰呼び出しから生成された要素。BA'=(a2, a3, ..., an)B'=(b2, b3, ..., bn)a1

ここでは、C# で物事を生成する方法の類似の例を見つけることができます。必要に応じて "単に" 適応させる必要があります。

于 2010-06-22T13:57:32.120 に答える
1

finalResult は目的の配列です。両方の配列が同じサイズであると仮定します。

char[] Array1 = { 'a', 'b', 'c' };
int[] Array2 = { 3, 2, 4 };

var finalResult = new List<string>();
finalResult.Add(String.Empty);
for(int i=0; i<Array1.Length; i++)
{
    var tmp = from a in finalResult
              from b in Enumerable.Range(1,Array2[i])
              select String.Format("{0} {1}{2}",a,Array1[i],b).Trim();
    finalResult = tmp.ToList();
}

これで十分だと思います。

于 2010-06-22T15:25:45.200 に答える
1

私が正しく理解しているなら、あなたはデカルト積のようなものを求めています。この場合、LINQ を使用してこれを行う方法を次に示します。正確な答えではないかもしれませんが、アイデアを得てみてください


    char[] Array1 = { 'a', 'b', 'c' };
    string[] Array2 = { "10", "20", "15" };

    var result = from i in Array1
                 from j in Array2
                   select i + j;

これらの記事が役立つかもしれません

于 2010-06-22T14:00:24.350 に答える
1

linq ベースではない、より効果的な別のソリューションの場合:

static IEnumerable<T[]> CartesianProduct<T>(T[][] arrays) {
    int[] lengths;
    lengths = arrays.Select(a => a.Length).ToArray();
    int Len = arrays.Length;
    int[] inds = new int[Len];
    int Len1 = Len - 1;
    while (inds[0] != lengths[0]) {
        T[] res = new T[Len];
        for (int i = 0; i != Len; i++) {
            res[i] = arrays[i][inds[i]];
        }
        yield return res;
        int j = Len1;
        inds[j]++;
        while (j > 0 && inds[j] == lengths[j]) {
            inds[j--] = 0;
            inds[j]++;
        }
    }
}
于 2015-10-13T15:00:40.253 に答える
0

デカルト積アルゴリズムの産業用、テスト済み、およびサポート対象の実装に関心がある場合は、すぐに使用できるGapotchenko.FX.Math.Combinatorics NuGet パッケージを使用してください。

2 つの操作モードを提供します。LINQ ベースの流暢なモード:

using Gapotchenko.FX.Math.Combinatorics;
using System;

foreach (var i in new[] { "1", "2" }.CrossJoin(new[] { "A", "B", "C" }))
    Console.WriteLine(string.Join(" ", i));

そして、より冗長な明示モード:

using Gapotchenko.FX.Math.Combinatorics;
using System;

var seq1 = new[] { "1", "2" };
var seq2 = new[] { "A", "B", "C" };

foreach (var i in CartesianProduct.Of(seq1, seq2))
    Console.WriteLine(string.Join(" ", i));

どちらのモードでも同じ結果が得られます。

1 A
2 A
1 B
2 B
1 C
2 C

しかし、これはさらに先のことです。たとえば、ValueTuple結果への射影は単純なワンライナーです。

var results = new[] { 1, 2 }.CrossJoin(new[] { "A", "B" }, ValueTuple.Create);

foreach (var (a, b) in results)
  Console.WriteLine("{0} {1}", a, b);

結果の一意性は、自然な方法で実現できます。

var results = new[] { 1, 1, 2 }.CrossJoin(new[] { "A", "B", "A" }).Distinct();

一見したところ、このようなアプローチでは組み合わせが無駄になります。だから代わりに

new[] { 1, 1, 2 }.CrossJoin(new[] { "A", "B", "A" }).Distinct()

Distinct()高価な乗算を実行する前に、シーケンスにとってより有益な場合があります。

new[] { 1, 1, 2 }.Distinct().CrossJoin(new[] { "A", "B", "A" }.Distinct())

このパッケージは、そのような特異性を最適化する自動プラン ビルダーを提供します。その結果、両方のアプローチの計算の複雑さは同じになります。

パッケージの対応するソース コードは、スニペットに含めることができるよりも少し大きくなりますが、GitHubで入手できます。

于 2021-07-28T21:28:42.830 に答える
-1

これは、誰かが変換できると確信しているjavascriptバージョンです。徹底的にテストされています。

これがフィドルです。

function combinations (Asource){

    var combos = [];
    var temp = [];

    var picker = function (arr, temp_string, collect) {
        if (temp_string.length) {
           collect.push(temp_string);
        }

        for (var i=0; i<arr.length; i++) {
            var arrcopy = arr.slice(0, arr.length);
            var elem = arrcopy.splice(i, 1);

            if (arrcopy.length > 0) {
                picker(arrcopy, temp_string.concat(elem), collect);
            } else {
                collect.push(temp_string.concat(elem));
            }   
        }   
    }

    picker(Asource, temp, combos);

    return combos;

}

var todo = ["a", "b", "c", "d"]; // 5 in this set
var resultingCombos = combinations (todo);
console.log(resultingCombos);
于 2014-03-23T10:15:03.853 に答える