web-dev-qa-db-ja.com

可能なすべての組み合わせを生成する

2つの配列_Array1 = {a,b,c...n}_および_Array2 = {10,20,15....x}_が与えられた場合、可能なすべての組み合わせを文字列として生成するにはどうすればよいですかa(i)b(j) c(k) n(p)ここで

_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)
_
63
Amitd
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;
}
20
max

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

更新:これは 2010年6月28日月曜日の私のブログ ;の主題です。すばらしい質問をありがとう。また、私のブログのコメント者は、私が与えたものよりもさらに洗練されたクエリがあると指摘しました。ここでコードを更新して使用します。

トリッキーな部分は、任意の多くのシーケンスのデカルト積を作成することです。文字の「ジッピング」はそれと比較して簡単です。これがどのように機能するかを確実に理解するためにこれを研究する必要があります。各部分は非常にシンプルですが、それらを組み合わせる方法には多少慣れる必要があります。

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)

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

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

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

最初の累積では、アキュムレーターは{{}}で、アイテムは{1、2}です。これを行います:

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

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

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

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

そして、それらのアイテムから、2番目のアイテムを最初のアイテムに連結します。したがって、結果はシーケンス{{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);

そして今、文字列のシーケンス、行ごとに文字列のシーケンスがあります:

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

簡単!

148
Eric Lippert

代替ソリューション:

ステップ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つの配列からその文法定義文字列を簡単に生成できます。次に、指定された文法ですべての文字列を生成するコードにそれを入力します。これで完了です。すべての可能性が得られます。 (必要な順序である必要はありません、気を付けてください。)

13
Eric Lippert

比較のために、ここにPythonでそれを行う方法があります

from itertools import product
X=["a", "b", "c"]
Y=[3, 4, 2]
terms = (["%s%s"%(x,i+1) for i in range(y)] for x,y in Zip(X,Y))
for item in product(*terms):
    print " ".join(item)
3
John La Rooy

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

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

2
Felice Pollano

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

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

A=(a1, a2, ..., an)B=(b1, b2, ..., bn)(だからABはそれぞれn要素を保持していると仮定しています。

その後、再帰的に実行します! ABを取り、自分のことを行うメソッドを作成します。

AおよびBがそれぞれ1つの要素(anまたはbnと呼ばれる)のみを含む場合、1からbnに反復し、anを連結します。反復変数に。

AおよびBにそれぞれ1つ以上の要素が含まれる場合、最初の要素(_a1_ resp _b1_)を取得し、1からbnに繰り返し、それぞれに対して実行します反復ステップ:

  • 2番目の要素から始まるAおよびBのサブフィールド、つまりA'=(a2, a3, ..., an) resp B'=(b2, b3, ..., bn)でメソッドを再帰的に呼び出します。再帰呼び出しによって生成されたすべての要素について、_a1_、反復変数、および再帰呼び出しから生成された要素を連結します。

ここ C#で物事を生成する方法のサンプルを見つけることができます、あなたは「ちょうど」それをあなたのニーズに適応させる必要があります。

1
phimuemue

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

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

1
Gulshan

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


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

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

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

1
Asad Butt

これは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);
0
bob

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]++;
        }
    }
}
0
Peter Almazov