nからk要素のすべての組み合わせを返すアルゴリズム


571

文字の配列を引数として取り、それらの文字の数を選択する関数を記述したいと思います。

たとえば、8文字の配列を提供し、その中から3文字を選択するとします。それからあなたは得るべきです:

8! / ((8 - 3)! * 3!) = 56

それぞれ3文字で構成される配列(または単語)。


2
プログラミング言語の好みは?
ジョナサントラン

7
重複した文字をどのように処理しますか?
wcm

言語の好みはありません。Rubyでコーディングしますが、使用するアルゴリズムの概要は問題ありません。同じ値の2つの文字が存在する可能性がありますが、まったく同じ文字が2回存在することはありません。
Fredrik、

フラッシュAS3ソリューションstackoverflow.com/questions/4576313/...
ダニエル

PHPでは、次のようにトリックを行う必要があります。 stackoverflow.com/questions/4279722/...
ケマルDAG

回答:


413

Art of Computer Programming Volume 4:Fascicle 3には、私が説明している方法よりも特定の状況により適したものがたくさんあります。

グレイコード

あなたが遭遇する問題はもちろんメモリであり、かなりすぐに、セット内の20要素(20 C 3 = 1140)の問題が発生します。また、セットを反復処理する場合は、変更された灰色を使用するのが最善です。アルゴリズムをコード化して、それらのすべてをメモリに保持しないようにします。これらは、前の組み合わせから次の組み合わせを生成し、繰り返しを避けます。これらはさまざまな用途に使用できます。連続する組み合わせの違いを最大化したいですか?最小化?など。

灰色のコードを説明する元の論文の一部:

  1. いくつかのハミルトンパスと最小変更アルゴリズム
  2. 隣接する交換の組み合わせ生成アルゴリズム

このトピックをカバーする他のいくつかのペーパーは次のとおりです。

  1. Eades、Hickey、Read Adjacent Interchange Combination Generation Algorithm(PDF、Pascalのコード付き)の効率的な実装
  2. 組み合わせジェネレーター
  3. 組み合わせグレイコードの調査(PostScript)
  4. グレイコードのアルゴリズム

チェイスのひねり(アルゴリズム)

フィリップJチェイス、「アルゴリズム382:N個のオブジェクトうちM個の組み合わせ」(1970)

Cのアルゴリズム ...

辞書式順序の組み合わせのインデックス(バックルアルゴリズム515)

また、その組み合わせを(辞書式順序で)インデックスで参照することもできます。インデックスは、インデックスに基づいて右から左にいくらか変化する必要があることを認識し、組み合わせを回復する必要があるものを構築できます。

したがって、セット{1,2,3,4,5,6} ...があり、3つの要素が必要です。{1,2,3}としましょう。要素間の違いは1つで、順序が正しく、最小です。{1,2,4}には1つの変更があり、辞書式に番号2です。したがって、最後の場所の「変更」の数は、辞書式順序の1つの変更を説明します。2番目の場所には1つの変更があり、{1,3,4}には1つの変更がありますが、2番目の場所(元のセットの要素の数に比例)であるため、さらに多くの変更が発生します。

私が説明した方法は、セットからインデックスまで、逆に行う必要があると思われる分解です。これは、はるかにトリッキーです。これがバックルが問題を解決する方法です。マイナーな変更を加えそれらを計算するためにCをいくつか作成しました。セットを表すために番号範囲ではなくセットのインデックスを使用したため、常に0 ... nから作業しています。注意:

  1. 組み合わせは順序付けられていないため、{1,3,2} = {1,2,3}-辞書順に並べ替えます。
  2. このメソッドには、最初の違いのセットを開始する暗黙の0があります。

辞書式順序の組み合わせのインデックス(McCaffrey)

別の方法がありますその概念が把握し、プログラムに簡単ですが、それはバックルの最適化なしです:幸いにも、それは重複した組み合わせを生成しません:

x_k ... x_1 in Nを最大化するセットi = C(x_1、k)+ C(x_2、k-1)+ ... + C(x_k、1)C(n、r)= {n rを選択}

例:27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)。したがって、4つのものの27番目の辞書式組み合わせは、{1,2,5,6}です。これらは、参照するセットのインデックスです。以下の例(OCaml)、choose機能が必要、読者に任せる:

(* this will find the [x] combination of a [set] list when taking [k] elements *)
let combination_maccaffery set k x =
    (* maximize function -- maximize a that is aCb              *)
    (* return largest c where c < i and choose(c,i) <= z        *)
    let rec maximize a b x =
        if (choose a b ) <= x then a else maximize (a-1) b x
    in
    let rec iterate n x i = match i with
        | 0 -> []
        | i ->
            let max = maximize n i x in
            max :: iterate n (x - (choose max i)) (i-1)
    in
    if x < 0 then failwith "errors" else
    let idxs =  iterate (List.length set) x k in
    List.map (List.nth set) (List.sort (-) idxs)

小さくて単純な組み合わせ反復子

次の2つのアルゴリズムは、教訓的な目的で提供されています。イテレーターと(より一般的な)フォルダー全体の組み合わせを実装します。それらは可能な限り高速であり、複雑度はO(n C k)です。メモリ消費はによって制限されkます。

イテレータから始めます。イテレータは、組み合わせごとにユーザー提供の関数を呼び出します。

let iter_combs n k f =
  let rec iter v s j =
    if j = k then f v
    else for i = s to n - 1 do iter (i::v) (i+1) (j+1) done in
  iter [] 0 0

より一般的なバージョンでは、初期状態から開始して、状態変数とともにユーザー提供の関数を呼び出します。異なる状態間で状態を渡す必要があるため、forループは使用しませんが、代わりに再帰を使用します。

let fold_combs n k f x =
  let rec loop i s c x =
    if i < n then
      loop (i+1) s c @@
      let c = i::c and s = s + 1 and i = i + 1 in
      if s < k then loop i s c x else f c x
    else x in
  loop 0 0 [] x

1
セットに等しい要素が含まれている場合、重複した組み合わせが生成されますか?
Thomas Ahle

2
はい、トーマスになります。配列内のデータに依存しません。それが望ましい効果である場合、または別のアルゴリズムを選択する場合は、常に最初に重複を除外で​​きます。
nlucaroni

19
素晴らしい答え。各アルゴリズムの実行時間とメモリ分析の概要を教えてください。
uncaught_exceptions

2
かなり良い答えです。20C3は1140であり、感嘆符は階乗のように見えるため、ここでは感嘆符が混乱しています。階乗は、組み合わせを見つけるための数式を入力します。したがって、感嘆符を編集します。
CashCow 2013年

3
それは、引用の多くがペイウォールの背後にあることを吸い込みます。ペイウォール以外のリンクまたはソースからの引用可能なスニペットを含める可能性はありますか?
Terrance

195

C#の場合:

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
{
  return k == 0 ? new[] { new T[0] } :
    elements.SelectMany((e, i) =>
      elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] {e}).Concat(c)));
}

使用法:

var result = Combinations(new[] { 1, 2, 3, 4, 5 }, 3);

結果:

123
124
125
134
135
145
234
235
245
345

2
このソリューションは、「小さな」セットではうまく機能しますが、大きなセットでは少しのメモリを使用します。
Artur Carvalho

1
直接関係はありませんが、コードは非常に興味深い/読みやすく、C#のどのバージョンにこの構成/メソッドがあるのでしょうか?(私はc#v1.0のみを使用しており、それほど多くは使用していません)。
LBarret

確かにエレガントな、しかしIEnumerableを、列挙される多くの回のを。これが何らかの重要な操作に裏付けられている場合...
Drew Noakes

2
それは拡張メソッドなので、あなたの用法・ラインは読むことができます:var result = new[] { 1, 2, 3, 4, 5 }.Combinations(3);
デイブCousineau

1
再帰ループを使用して、このクエリの正確な非linqバージョンを提供できますか
irfandar

81

短いJavaソリューション:

import java.util.Arrays;

public class Combination {
    public static void main(String[] args){
        String[] arr = {"A","B","C","D","E","F"};
        combinations2(arr, 3, 0, new String[3]);
    }

    static void combinations2(String[] arr, int len, int startPosition, String[] result){
        if (len == 0){
            System.out.println(Arrays.toString(result));
            return;
        }       
        for (int i = startPosition; i <= arr.length-len; i++){
            result[result.length - len] = arr[i];
            combinations2(arr, len-1, i+1, result);
        }
    }       
}

結果は

[A, B, C]
[A, B, D]
[A, B, E]
[A, B, F]
[A, C, D]
[A, C, E]
[A, C, F]
[A, D, E]
[A, D, F]
[A, E, F]
[B, C, D]
[B, C, E]
[B, C, F]
[B, D, E]
[B, D, F]
[B, E, F]
[C, D, E]
[C, D, F]
[C, E, F]
[D, E, F]

これはO(n ^ 3)のようですよね?これを行うには、より高速なアルゴリズムがあるのでしょうか。
LZH 2014年

私は20選択10で作業していますが、これは私にとって十分高速(1秒未満)のようです
デーモン5

4
@NanoHeadあなたは間違っている。これは繰り返しのない組み合わせです。そしてあなたの場合は繰り返しです。
Jack The Ripper

このコードはウェブ上で見つけやすいはずです...これがまさに私が探していたものです!
Manuel S.

私はこれと他の7つのJava実装をテストしました-これは断然最速でした。2番目に速いのは、桁違いに遅いものでした。
スチュアート

77

この問題に対する再帰的なPythonソリューションを提示できますか?

def choose_iter(elements, length):
    for i in xrange(len(elements)):
        if length == 1:
            yield (elements[i],)
        else:
            for next in choose_iter(elements[i+1:len(elements)], length-1):
                yield (elements[i],) + next
def choose(l, k):
    return list(choose_iter(l, k))

使用例:

>>> len(list(choose_iter("abcdefgh",3)))
56

シンプルなので気に入っています。


16
len(tuple(itertools.combinations('abcdefgh',3)))少ないコードでPythonで同じことを実現します。
hgus1294 2012

59
@ hgus1294本当ですが、それは不正行為です。Opは、特定のプログラミング言語に関連付けられている「魔法の」メソッドではなく、アルゴリズムを要求しました。
MestreLion 2012年

1
厳密に言えば、最初のループの範囲はfor i in xrange(len(elements) - length + 1):?Pythonではスライスインデックスの外への処理は適切に行われるため問題ではありませんが、これは正しいアルゴリズムです。
ステファンドールバーグ、2017年

62

文字の配列が「ABCDEFGH」のようになっているとします。現在の単語に使用する文字を示す3つのインデックス(i、j、k)があります。

ABCDEFGH
^ ^ ^
ijk

最初にkを変化させるので、次のステップは次のようになります。

ABCDEFGH
^ ^ ^
ijk

最後に達したら、jとkを変更します。

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

jがGに到達すると、iも変化し始めます。

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...

コードで書かれたこれはそのようなものです

void print_combinations(const char *string)
{
    int i, j, k;
    int len = strlen(string);

    for (i = 0; i < len - 2; i++)
    {
        for (j = i + 1; j < len - 1; j++)
        {
            for (k = j + 1; k < len; k++)
                printf("%c%c%c\n", string[i], string[j], string[k]);
        }
    }
}

115
このアプローチの問題は、パラメーター3をコードに固定化していることです。(4文字が必要な場合はどうなりますか?)私が質問を理解したように、文字の配列と選択する文字数の両方が提供されます。もちろん、この問題を回避する1つの方法は、明示的にネストされたループを再帰に置き換えることです。
joel.neely 2009

10
@ Dr.PersonPersonIIそして、なぜOPに関連する三角形があるのですか?
MestreLion 2012年

7
このソリューションは、いつでも任意のパラメーターを使用した再帰的なソリューションに変換できます。
Rok Kralj 14

5
@RokKralj、「このソリューションを任意のパラメータを持つ再帰的なソリューションに変換」するにはどうすればよいですか?私には不可能のようです。
アーロン・マクデイド、2015

3
それを行う方法についての直感的な説明
Yonatan Simson '27

53

次の再帰アルゴリズムは、順序付けられたセットからすべてのk要素の組み合わせを選択します。

  • iあなたの組み合わせの最初の要素を選択してください
  • より大きい要素のセットから再帰的に選択された要素のi各組み合わせと組み合わせる。k-1i

iセットのそれぞれについて上記を繰り返します。

i繰り返しを避けるために、残りの要素をより大きい値にすることが重要です。この方法では、[3,5]が2回ではなく[5]と組み合わされて[3,5]が1回だけ選択されます(この条件では[5] + [3]が削除されます)。この条件がないと、組み合わせではなくバリエーションが得られます。


12
多くの回答で使用されているアルゴリズムの英語での非常に優れた説明
MestreLion

2番目の上記; 特に、これはユーザー935714によって提示されたソリューションを理解するのに役立ちました。どちらも素晴らしいです。
jacoblambert 2017年

25

C ++では、次のルーチンは、範囲[first、last)間の長さdistance(first、k)のすべての組み合わせを生成します。

#include <algorithm>

template <typename Iterator>
bool next_combination(const Iterator first, Iterator k, const Iterator last)
{
   /* Credits: Mark Nelson http://marknelson.us */
   if ((first == last) || (first == k) || (last == k))
      return false;
   Iterator i1 = first;
   Iterator i2 = last;
   ++i1;
   if (last == i1)
      return false;
   i1 = last;
   --i1;
   i1 = k;
   --i2;
   while (first != i1)
   {
      if (*--i1 < *i2)
      {
         Iterator j = k;
         while (!(*i1 < *j)) ++j;
         std::iter_swap(i1,j);
         ++i1;
         ++j;
         i2 = k;
         std::rotate(i1,j,last);
         while (last != j)
         {
            ++j;
            ++i2;
         }
         std::rotate(k,i2,last);
         return true;
      }
   }
   std::rotate(first,k,last);
   return false;
}

次のように使用できます。

#include <string>
#include <iostream>

int main()
{
    std::string s = "12345";
    std::size_t comb_size = 3;
    do
    {
        std::cout << std::string(s.begin(), s.begin() + comb_size) << std::endl;
    } while (next_combination(s.begin(), s.begin() + comb_size, s.end()));

    return 0;
}

これは以下を出力します:

123
124
125
134
135
145
234
235
245
345

1
この場合、何が始まり、何が終わりますか?この関数に渡されるすべての変数が値で渡される場合、実際に何かを返すにはどうすればよいですか?
Sergej Andrejev

6
@Sergej Andrejev:交換するbeingbeginしてs.begin()、そしてend持ちますs.end()。コードは、STLのnext_permutationアルゴリズムに厳密に従っています。詳細については、ここで説明します
Anthony Labarre

5
何が起こっている?i1 =最後; --i1; i1 = k;
Manoj R 2012

24

私はこのスレッドが便利だと思ったので、FirebugにポップインできるJavascriptソリューションを追加すると思いました。JSエンジンによっては、開始文字列が大きい場合、少し時間がかかる場合があります。

function string_recurse(active, rest) {
    if (rest.length == 0) {
        console.log(active);
    } else {
        string_recurse(active + rest.charAt(0), rest.substring(1, rest.length));
        string_recurse(active, rest.substring(1, rest.length));
    }
}
string_recurse("", "abc");

出力は次のようになります。

abc
ab
ac
a
bc
b
c

4
@NanoHeadこれは間違いではありません。出力にはすでに「ac」が表示されており、「ca」は「ac」と同じ組み合わせです。あなたは「数学」において「ac」が「ca」と同じではない置換について話している。
Jakob Jenkov 2017

1
これはnを選択するのではなくkを選択します。
しんぞう2018年

20
static IEnumerable<string> Combinations(List<string> characters, int length)
{
    for (int i = 0; i < characters.Count; i++)
    {
        // only want 1 character, just return this one
        if (length == 1)
            yield return characters[i];

        // want more than one character, return this one plus all combinations one shorter
        // only use characters after the current one for the rest of the combinations
        else
            foreach (string next in Combinations(characters.GetRange(i + 1, characters.Count - (i + 1)), length - 1))
                yield return characters[i] + next;
    }
}

素晴らしい解決策。:私はこの最近の質問応答でそれを参照しstackoverflow.com/questions/4472036/...
wageoghe

この関数の唯一の問題は、再帰性です。通常、PCで実行しているソフトウェアには問題ありませんが、リソースに制約のあるプラットフォーム(組み込みなど)を使用している場合は、うまくいきません
Padu Merloti

また、多くのリストを割り当て、多くの作業を行って、配列の項目をそれぞれの新しい項目に複製します。これらのリストは、列挙全体が完了するまで収集できないようです。
Niall Connaughton

それは滑らかです。アルゴリズムを見つけましたか、それとも最初からですか?
パパラッチ

20

Pythonでの短い例:

def comb(sofar, rest, n):
    if n == 0:
        print sofar
    else:
        for i in range(len(rest)):
            comb(sofar + rest[i], rest[i+1:], n-1)

>>> comb("", "abcde", 3)
abc
abd
abe
acd
ace
ade
bcd
bce
bde
cde

説明のために、次の例で再帰的な方法を説明します。

例:ABCDE
3のすべての組み合わせは次のようになります。

  • A残りの2のすべての組み合わせ(BCDE)
  • B残りの2のすべての組み合わせ(CDE)
  • C残りの2のすべての組み合わせ(DE)

17

Haskellの単純な再帰アルゴリズム

import Data.List

combinations 0 lst = [[]]
combinations n lst = do
    (x:xs) <- tails lst
    rest   <- combinations (n-1) xs
    return $ x : rest

最初に特別なケース、つまりゼロ要素を選択することを定義します。空のリスト(空のリストを含むリスト)である単一の結果を生成します。

n> 0の場合x、リストのすべての要素を通過し、のxs後のすべての要素xです。

restへの再帰呼び出しを使用してn - 1要素を選択xscombinationsます。機能の最終結果は、各要素がリストであるx : rest(持つリスト、すなわちxヘッドとしておよびrestのすべての異なる値の尾のように)xrest

> combinations 3 "abcde"
["abc","abd","abe","acd","ace","ade","bcd","bce","bde","cde"]

そしてもちろん、Haskellは怠惰なので、リストは必要に応じて徐々に生成されるため、指数関数的に大きな組み合わせを部分的に評価できます。

> let c = combinations 8 "abcdefghijklmnopqrstuvwxyz"
> take 10 c
["abcdefgh","abcdefgi","abcdefgj","abcdefgk","abcdefgl","abcdefgm","abcdefgn",
 "abcdefgo","abcdefgp","abcdefgq"]

13

そして、ここでは、非常に悪質な言語である祖父COBOLが登場します。

それぞれ8バイトの34要素の配列(完全に任意の選択)を想定します。アイデアは、可能なすべての4要素の組み合わせを列挙し、それらを配列にロードすることです。

4つのグループの各ポジションに1つずつ、4つのインデックスを使用します

配列は次のように処理されます:

    idx1 = 1
    idx2 = 2
    idx3 = 3
    idx4 = 4

idx4を4から最後まで変化させます。idx4ごとに、4つのグループの一意の組み合わせを取得します。idx4が配列の最後に来ると、idx3を1増やし、idx4をidx3 + 1に設定します。次に、idx4を最後まで実行します。この方法で、idx1の位置が配列の最後から4未満になるまで、idx3、idx2、idx1をそれぞれ増やします。これでアルゴリズムは終了です。

1          --- pos.1
2          --- pos 2
3          --- pos 3
4          --- pos 4
5
6
7
etc.

最初の反復:

1234
1235
1236
1237
1245
1246
1247
1256
1257
1267
etc.

COBOLの例:

01  DATA_ARAY.
    05  FILLER     PIC X(8)    VALUE  "VALUE_01".
    05  FILLER     PIC X(8)    VALUE  "VALUE_02".
  etc.
01  ARAY_DATA    OCCURS 34.
    05  ARAY_ITEM       PIC X(8).

01  OUTPUT_ARAY   OCCURS  50000   PIC X(32).

01   MAX_NUM   PIC 99 COMP VALUE 34.

01  INDEXXES  COMP.
    05  IDX1            PIC 99.
    05  IDX2            PIC 99.
    05  IDX3            PIC 99.
    05  IDX4            PIC 99.
    05  OUT_IDX   PIC 9(9).

01  WHERE_TO_STOP_SEARCH          PIC 99  COMP.

* Stop the search when IDX1 is on the third last array element:

COMPUTE WHERE_TO_STOP_SEARCH = MAX_VALUE - 3     

MOVE 1 TO IDX1

PERFORM UNTIL IDX1 > WHERE_TO_STOP_SEARCH
   COMPUTE IDX2 = IDX1 + 1
   PERFORM UNTIL IDX2 > MAX_NUM
      COMPUTE IDX3 = IDX2 + 1
      PERFORM UNTIL IDX3 > MAX_NUM
         COMPUTE IDX4 = IDX3 + 1
         PERFORM UNTIL IDX4 > MAX_NUM
            ADD 1 TO OUT_IDX
            STRING  ARAY_ITEM(IDX1)
                    ARAY_ITEM(IDX2)
                    ARAY_ITEM(IDX3)
                    ARAY_ITEM(IDX4)
                    INTO OUTPUT_ARAY(OUT_IDX)
            ADD 1 TO IDX4
         END-PERFORM
         ADD 1 TO IDX3
      END-PERFORM
      ADD 1 TO IDX2
   END_PERFORM
   ADD 1 TO IDX1
END-PERFORM.

しかし、なぜ{} {} {} {}
信三

9

99 Scala Problemsで 説明されているように、Scalaでのエレガントで汎用的な実装は次のとおりです。

object P26 {
  def flatMapSublists[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = 
    ls match {
      case Nil => Nil
      case sublist@(_ :: tail) => f(sublist) ::: flatMapSublists(tail)(f)
    }

  def combinations[A](n: Int, ls: List[A]): List[List[A]] =
    if (n == 0) List(Nil)
    else flatMapSublists(ls) { sl =>
      combinations(n - 1, sl.tail) map {sl.head :: _}
    }
}

9

SQL構文を使用できる場合-たとえば、LINQを使用して構造体または配列のフィールドにアクセスしている場合、または「Alphabet」と呼ばれるテーブルが1つの文字フィールド「Letter」しかないデータベースに直接アクセスしている場合、次のように適応できます。コード:

SELECT A.Letter, B.Letter, C.Letter
FROM Alphabet AS A, Alphabet AS B, Alphabet AS C
WHERE A.Letter<>B.Letter AND A.Letter<>C.Letter AND B.Letter<>C.Letter
AND A.Letter<B.Letter AND B.Letter<C.Letter

これは、テーブル「Alphabet」にある文字数に関係なく、3文字のすべての組み合わせを返します(3、8、10、27などになる場合があります)。

必要なものが組み合わせではなくすべての順列である場合(つまり、「ACB」と「ABC」を1回だけ表示するのではなく、異なるものとしてカウントする場合)、最後の行(AND 1)を削除するだけで完了です。

編集後:質問をもう一度読んだ後、3つの項目を選択する場合の特定のアルゴリズムではなく、一般的なアルゴリズムが必要であることに気付きました。Adam Hughesの答えは完全なものですが、残念ながら私は(まだ)投票することはできません。この答えは簡単ですが、ちょうど3つのアイテムが必要な場合にのみ機能します。


7

組み合わせインデックスの遅延生成を備えた別のC#バージョン。このバージョンは、インデックスの単一配列を維持して、すべての値のリストと現在の組み合わせの値の間のマッピングを定義します。つまり、ランタイム全体で常にO(k)追加スペースを使用します。コードは、最初の組み合わせを含む個々の組み合わせをO(k)時間で生成します。

public static IEnumerable<T[]> Combinations<T>(this T[] values, int k)
{
    if (k < 0 || values.Length < k)
        yield break; // invalid parameters, no combinations possible

    // generate the initial combination indices
    var combIndices = new int[k];
    for (var i = 0; i < k; i++)
    {
        combIndices[i] = i;
    }

    while (true)
    {
        // return next combination
        var combination = new T[k];
        for (var i = 0; i < k; i++)
        {
            combination[i] = values[combIndices[i]];
        }
        yield return combination;

        // find first index to update
        var indexToUpdate = k - 1;
        while (indexToUpdate >= 0 && combIndices[indexToUpdate] >= values.Length - k + indexToUpdate)
        {
            indexToUpdate--;
        }

        if (indexToUpdate < 0)
            yield break; // done

        // update combination indices
        for (var combIndex = combIndices[indexToUpdate] + 1; indexToUpdate < k; indexToUpdate++, combIndex++)
        {
            combIndices[indexToUpdate] = combIndex;
        }
    }
}

テストコード:

foreach (var combination in new[] {'a', 'b', 'c', 'd', 'e'}.Combinations(3))
{
    System.Console.WriteLine(String.Join(" ", combination));
}

出力:

a b c
a b d
a b e
a c d
a c e
a d e
b c d
b c e
b d e
c d e

これにより、順序が保持されます。結果セットには含まc b aれていないものも含まれていると思います。
Dmitri Nesteruk、2016年

タスクは、k以上のnを満たすすべての組み合わせを生成することです。二項係数は、n個の要素の固定セットからk個の要素の順序付けられていないサブセットを選択する方法についての質問に答えます。したがって、提案されたアルゴリズムはそれがすべきことを行います。
クリストフ

6

https://gist.github.com/3118596

JavaScriptの実装があります。これは、k組み合わせと任意のオブジェクトの配列のすべての組み合わせを取得する関数を持っています。例:

k_combinations([1,2,3], 2)
-> [[1,2], [1,3], [2,3]]

combinations([1,2,3])
-> [[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]

6

ここに、C#でコード化されたそのアルゴリズムの遅延評価バージョンがあります。

    static bool nextCombination(int[] num, int n, int k)
    {
        bool finished, changed;

        changed = finished = false;

        if (k > 0)
        {
            for (int i = k - 1; !finished && !changed; i--)
            {
                if (num[i] < (n - 1) - (k - 1) + i)
                {
                    num[i]++;
                    if (i < k - 1)
                    {
                        for (int j = i + 1; j < k; j++)
                        {
                            num[j] = num[j - 1] + 1;
                        }
                    }
                    changed = true;
                }
                finished = (i == 0);
            }
        }

        return changed;
    }

    static IEnumerable Combinations<T>(IEnumerable<T> elements, int k)
    {
        T[] elem = elements.ToArray();
        int size = elem.Length;

        if (k <= size)
        {
            int[] numbers = new int[k];
            for (int i = 0; i < k; i++)
            {
                numbers[i] = i;
            }

            do
            {
                yield return numbers.Select(n => elem[n]);
            }
            while (nextCombination(numbers, size, k));
        }
    }

そしてテスト部分:

    static void Main(string[] args)
    {
        int k = 3;
        var t = new[] { "dog", "cat", "mouse", "zebra"};

        foreach (IEnumerable<string> i in Combinations(t, k))
        {
            Console.WriteLine(string.Join(",", i));
        }
    }

これがお役に立てば幸いです!


6

私はプロジェクトオイラーに使用した順列アルゴリズムをPythonで持っていました:

def missing(miss,src):
    "Returns the list of items in src not present in miss"
    return [i for i in src if i not in miss]


def permutation_gen(n,l):
    "Generates all the permutations of n items of the l list"
    for i in l:
        if n<=1: yield [i]
        r = [i]
        for j in permutation_gen(n-1,missing([i],l)):  yield r+j

もし

n<len(l) 

繰り返しなしで必要なすべての組み合わせが必要ですが、必要ですか?

これはジェネレーターなので、次のように使用します。

for comb in permutation_gen(3,list("ABCDEFGH")):
    print comb 

5
Array.prototype.combs = function(num) {

    var str = this,
        length = str.length,
        of = Math.pow(2, length) - 1,
        out, combinations = [];

    while(of) {

        out = [];

        for(var i = 0, y; i < length; i++) {

            y = (1 << i);

            if(y & of && (y !== of))
                out.push(str[i]);

        }

        if (out.length >= num) {
           combinations.push(out);
        }

        of--;
    }

    return combinations;
}

5

Clojureバージョン:

(defn comb [k l]
  (if (= 1 k) (map vector l)
      (apply concat
             (map-indexed
              #(map (fn [x] (conj x %2))
                    (comb (dec k) (drop (inc %1) l)))
              l))))

5

文字の配列が「ABCDEFGH」のようになっているとします。現在の単語に使用する文字を示す3つのインデックス(i、j、k)があります。

ABCDEFGH
^ ^ ^
ijk

最初にkを変化させるので、次のステップは次のようになります。

ABCDEFGH
^ ^ ^
ijk

最後に達したら、jとkを変更します。

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

jがGに到達すると、iも変化し始めます。

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...
function initializePointers($cnt) {
    $pointers = [];

    for($i=0; $i<$cnt; $i++) {
        $pointers[] = $i;
    }

    return $pointers;     
}

function incrementPointers(&$pointers, &$arrLength) {
    for($i=0; $i<count($pointers); $i++) {
        $currentPointerIndex = count($pointers) - $i - 1;
        $currentPointer = $pointers[$currentPointerIndex];

        if($currentPointer < $arrLength - $i - 1) {
           ++$pointers[$currentPointerIndex];

           for($j=1; ($currentPointerIndex+$j)<count($pointers); $j++) {
                $pointers[$currentPointerIndex+$j] = $pointers[$currentPointerIndex]+$j;
           }

           return true;
        }
    }

    return false;
}

function getDataByPointers(&$arr, &$pointers) {
    $data = [];

    for($i=0; $i<count($pointers); $i++) {
        $data[] = $arr[$pointers[$i]];
    }

    return $data;
}

function getCombinations($arr, $cnt)
{
    $len = count($arr);
    $result = [];
    $pointers = initializePointers($cnt);

    do {
        $result[] = getDataByPointers($arr, $pointers);
    } while(incrementPointers($pointers, count($arr)));

    return $result;
}

$result = getCombinations([0, 1, 2, 3, 4, 5], 3);
print_r($result);

https://stackoverflow.com/a/127898/2628125に基づいていますが、ポインタのサイズに関係なく、より抽象的です。


このひどい言語は何ですか?バッシュ?
しんぞう2018年

1
phpですが、ここでは言語は関係ありません。アルゴリズムは重要です
Oleksandr Knyga

私はこの言語を学ぶことを拒否してとてもうれしいです。インタープリター/コンパイラーが変数の認識を支援する必要がある言語は、2018
。– shinzou

4

ここで述べられ、行われたのは、そのためのO'camlコードです。アルゴリズムはコードから明らかです。

let combi n lst =
    let rec comb l c =
        if( List.length c = n) then [c] else
        match l with
        [] -> []
        | (h::t) -> (combi t (h::c))@(combi t c)
    in
        combi lst []
;;

4

これは、ランダムな長さの文字列から指定されたサイズのすべての組み合わせを提供する方法です。quinmarsのソリューションに似ていますが、さまざまな入力とkに対して機能します。

コードを折り返すように変更できます。つまり、入力 'abcd' wk = 3から 'dab'を変更できます。

public void run(String data, int howMany){
    choose(data, howMany, new StringBuffer(), 0);
}


//n choose k
private void choose(String data, int k, StringBuffer result, int startIndex){
    if (result.length()==k){
        System.out.println(result.toString());
        return;
    }

    for (int i=startIndex; i<data.length(); i++){
        result.append(data.charAt(i));
        choose(data,k,result, i+1);
        result.setLength(result.length()-1);
    }
}

「abcde」の出力:

abc abd abe acd ace ade bcd bce bde cde


4

短いPythonコード、インデックス位置を生成

def yield_combos(n,k):
    # n is set size, k is combo size

    i = 0
    a = [0]*k

    while i > -1:
        for j in range(i+1, k):
            a[j] = a[j-1]+1
        i=j
        yield a
        while a[i] == i + n - k:
            i -= 1
        a[i] += 1

これは非常にエレガントで効率的で、うまく機能します。C ++に翻訳しました。
しゃがむ子猫


3

これがC ++での私の提案です

イテレータのタイプにできる限り少ない制限を課そうと試みたので、このソリューションは単にイテレータを転送することを想定しており、const_iteratorにすることができます。これは、任意の標準コンテナで機能するはずです。引数が意味をなさない場合、std :: invalid_argumnentをスローします

#include <vector>
#include <stdexcept>

template <typename Fci> // Fci - forward const iterator
std::vector<std::vector<Fci> >
enumerate_combinations(Fci begin, Fci end, unsigned int combination_size)
{
    if(begin == end && combination_size > 0u)
        throw std::invalid_argument("empty set and positive combination size!");
    std::vector<std::vector<Fci> > result; // empty set of combinations
    if(combination_size == 0u) return result; // there is exactly one combination of
                                              // size 0 - emty set
    std::vector<Fci> current_combination;
    current_combination.reserve(combination_size + 1u); // I reserve one aditional slot
                                                        // in my vector to store
                                                        // the end sentinel there.
                                                        // The code is cleaner thanks to that
    for(unsigned int i = 0u; i < combination_size && begin != end; ++i, ++begin)
    {
        current_combination.push_back(begin); // Construction of the first combination
    }
    // Since I assume the itarators support only incrementing, I have to iterate over
    // the set to get its size, which is expensive. Here I had to itrate anyway to  
    // produce the first cobination, so I use the loop to also check the size.
    if(current_combination.size() < combination_size)
        throw std::invalid_argument("combination size > set size!");
    result.push_back(current_combination); // Store the first combination in the results set
    current_combination.push_back(end); // Here I add mentioned earlier sentinel to
                                        // simplyfy rest of the code. If I did it 
                                        // earlier, previous statement would get ugly.
    while(true)
    {
        unsigned int i = combination_size;
        Fci tmp;                            // Thanks to the sentinel I can find first
        do                                  // iterator to change, simply by scaning
        {                                   // from right to left and looking for the
            tmp = current_combination[--i]; // first "bubble". The fact, that it's 
            ++tmp;                          // a forward iterator makes it ugly but I
        }                                   // can't help it.
        while(i > 0u && tmp == current_combination[i + 1u]);

        // Here is probably my most obfuscated expression.
        // Loop above looks for a "bubble". If there is no "bubble", that means, that
        // current_combination is the last combination, Expression in the if statement
        // below evaluates to true and the function exits returning result.
        // If the "bubble" is found however, the ststement below has a sideeffect of 
        // incrementing the first iterator to the left of the "bubble".
        if(++current_combination[i] == current_combination[i + 1u])
            return result;
        // Rest of the code sets posiotons of the rest of the iterstors
        // (if there are any), that are to the right of the incremented one,
        // to form next combination

        while(++i < combination_size)
        {
            current_combination[i] = current_combination[i - 1u];
            ++current_combination[i];
        }
        // Below is the ugly side of using the sentinel. Well it had to haave some 
        // disadvantage. Try without it.
        result.push_back(std::vector<Fci>(current_combination.begin(),
                                          current_combination.end() - 1));
    }
}

3

これが私が最近Javaで書いたコードで、「outOf」要素から「num」要素のすべての組み合わせを計算して返します。

// author: Sourabh Bhat (heySourabh@gmail.com)

public class Testing
{
    public static void main(String[] args)
    {

// Test case num = 5, outOf = 8.

        int num = 5;
        int outOf = 8;
        int[][] combinations = getCombinations(num, outOf);
        for (int i = 0; i < combinations.length; i++)
        {
            for (int j = 0; j < combinations[i].length; j++)
            {
                System.out.print(combinations[i][j] + " ");
            }
            System.out.println();
        }
    }

    private static int[][] getCombinations(int num, int outOf)
    {
        int possibilities = get_nCr(outOf, num);
        int[][] combinations = new int[possibilities][num];
        int arrayPointer = 0;

        int[] counter = new int[num];

        for (int i = 0; i < num; i++)
        {
            counter[i] = i;
        }
        breakLoop: while (true)
        {
            // Initializing part
            for (int i = 1; i < num; i++)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i] = counter[i - 1] + 1;
            }

            // Testing part
            for (int i = 0; i < num; i++)
            {
                if (counter[i] < outOf)
                {
                    continue;
                } else
                {
                    break breakLoop;
                }
            }

            // Innermost part
            combinations[arrayPointer] = counter.clone();
            arrayPointer++;

            // Incrementing part
            counter[num - 1]++;
            for (int i = num - 1; i >= 1; i--)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i - 1]++;
            }
        }

        return combinations;
    }

    private static int get_nCr(int n, int r)
    {
        if(r > n)
        {
            throw new ArithmeticException("r is greater then n");
        }
        long numerator = 1;
        long denominator = 1;
        for (int i = n; i >= r + 1; i--)
        {
            numerator *= i;
        }
        for (int i = 2; i <= n - r; i++)
        {
            denominator *= i;
        }

        return (int) (numerator / denominator);
    }
}

3

簡潔なJavaScriptソリューション:

Array.prototype.combine=function combine(k){    
    var toCombine=this;
    var last;
    function combi(n,comb){             
        var combs=[];
        for ( var x=0,y=comb.length;x<y;x++){
            for ( var l=0,m=toCombine.length;l<m;l++){      
                combs.push(comb[x]+toCombine[l]);           
            }
        }
        if (n<k-1){
            n++;
            combi(n,combs);
        } else{last=combs;}
    }
    combi(1,toCombine);
    return last;
}
// Example:
// var toCombine=['a','b','c'];
// var results=toCombine.combine(4);

3

アルゴリズム:

  • 1から2 ^ nまで数えます。
  • 各桁をそのバイナリ表現に変換します。
  • 位置に基づいて、各「オン」ビットをセットの要素に変換します。

C#の場合:

void Main()
{
    var set = new [] {"A", "B", "C", "D" }; //, "E", "F", "G", "H", "I", "J" };

    var kElement = 2;

    for(var i = 1; i < Math.Pow(2, set.Length); i++) {
        var result = Convert.ToString(i, 2).PadLeft(set.Length, '0');
        var cnt = Regex.Matches(Regex.Escape(result),  "1").Count; 
        if (cnt == kElement) {
            for(int j = 0; j < set.Length; j++)
                if ( Char.GetNumericValue(result[j]) == 1)
                    Console.Write(set[j]);
            Console.WriteLine();
        }
    }
}

なぜ機能するのですか?

n要素セットのサブセットとnビットシーケンスの間に全単射があります。

つまり、シーケンスをカウントすることで、サブセットの数を把握できます。

たとえば、以下の4つの要素セットは、{0,1} X {0、1} X {0、1} X {0、1}(または2 ^ 4)の異なるシーケンスで表すことができます。

したがって、すべての組み合わせを見つけるには、1から2 ^ nまで数える必要があります。(空のセットは無視します。)次に、数字をバイナリ表現に変換します。次に、セットの要素を「オン」ビットに置き換えます。

k要素の結果のみが必要な場合は、kビットが「オン」の場合にのみ印刷します。

(k長のサブセットではなくすべてのサブセットが必要な場合は、cnt / kElement部分を削除してください。)

(証明については、MIT無料のコースウェアであるComputer Science for Mathematics for Lehman et al、セクション11.2.2を参照してください。https: //ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics- for-computer-science-fall-2010 / readings /


2

私は、二項係数を処理するための一般的な関数を処理するクラスを作成しました。これは、問題が該当するタイプの問題です。次のタスクを実行します。

  1. すべてのKインデックスを、N選択Kの適切な形式でファイルに出力します。Kインデックスは、より説明的な文字列または文字で置き換えることができます。この方法では、このタイプの問題を簡単に解決できます。

  2. Kインデックスを、ソートされた二項係数テーブルのエントリの適切なインデックスに変換します。この手法は、反復に依存する以前に公開された手法よりもはるかに高速です。これは、Pascalの三角形に固有の数学的特性を使用して行われます。私の論文はこれについて語っています。私はこのテクニックを最初に発見して公開したと思いますが、私は間違っている可能性があります。

  3. ソートされた二項係数テーブルのインデックスを対応するKインデックスに変換します。

  4. Mark Dominusメソッドを使用して、二項係数を計算します。二項係数は、オーバーフローする可能性がはるかに低く、より大きな数値で機能します。

  5. このクラスは.NET C#で記述され、一般的なリストを使用して、問題に関連するオブジェクト(存在する場合)を管理する方法を提供します。このクラスのコンストラクターは、InitTableと呼ばれるブール値を受け取ります。この値は、trueの場合、管理対象のオブジェクトを保持する汎用リストを作成します。この値がfalseの場合、テーブルは作成されません。上記の4つの方法を実行するために、テーブルを作成する必要はありません。テーブルにアクセスするためのアクセサメソッドが用意されています。

  6. クラスとそのメソッドの使用方法を示す関連するテストクラスがあります。2つのケースで広範囲にテストされており、既知のバグはありません。

このクラスについて読んでコードをダウンロードするには、「二項係数」の表を参照してください。

このクラスをC ++に変換することは難しくありません。


これを「マークドミナスメソッド」と呼ぶのは、実際には正しくありません。これは、すでに述べたように、少なくとも850年前のものであり、それほど難しいことではありません。それをリラバティ法と呼んでみませんか?
MJD
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.