n에서 k 요소의 모든 조합을 반환하는 알고리즘


571

문자 배열을 인수로 사용하고 선택할 수있는 많은 문자를 취하는 함수를 작성하고 싶습니다.

8 문자의 배열을 제공하고 그 중 3 문자를 선택한다고 가정하십시오. 그럼 당신은 얻을 것이다 :

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

3 개의 문자로 구성된 배열 (또는 단어).


2
프로그래밍 언어를 선호하십니까?
Jonathan Tran

7
중복 문자를 어떻게 처리 하시겠습니까?
wcm

언어를 선호하지 않고 루비로 코드를 작성하지만 사용할 알고리즘에 대한 일반적인 아이디어는 좋습니다. 값이 같은 두 글자가 존재할 수 있지만 정확히 같은 글자가 두 번있을 수는 없습니다.
Fredrik


PHP에서 다음과 같은 트릭을 수행해야합니다. stackoverflow.com/questions/4279722/…
Kemal Dağ

답변:


413

컴퓨터 프로그래밍 기술 4 권 : Fascicle 3 에는 내가 설명하는 것보다 특정 상황에 더 잘 맞는 톤이 많이 있습니다.

회색 코드

당신이 겪게 될 문제는 물론 메모리이며 꽤 빨리, 당신은 20 C 3 = 1140 의 세트에 20 개의 요소에 의해 문제를 겪을 것입니다. 그리고 세트를 반복하고 싶다면 수정 된 회색을 사용하는 것이 가장 좋습니다 코드 알고리즘을 사용하므로 메모리에 모두 포함되어 있지 않습니다. 이것들은 이전의 다음 조합을 생성하고 반복을 피합니다. 다양한 용도로 사용할 수있는 것들이 많이 있습니다. 연속 조합의 차이를 극대화하고 싶습니까? 최소화? 등등.

회색 코드를 설명하는 원본 논문 중 일부 :

  1. 일부 해밀턴 경로 및 최소 변경 알고리즘
  2. 인접 교환 조합 생성 알고리즘

이 주제를 다루는 다른 논문들도 있습니다 :

  1. Eades, Hickey, Adjacent Interchange Combination Generation 알고리즘의 효율적인 구현 (PDF, Pascal의 코드)
  2. 조합 생성기
  3. 조합 회색 코드 조사 (PostScript)
  4. 그레이 코드 알고리즘

체이스 트위들 (알고리즘)

Phillip J Chase,` Algorithm 382 : N Objects에서 M의 조합 '(1970)

C의 알고리즘 ...

사전 순서의 조합 색인 (버클 알고리즘 515)

색인으로 사전 조합을 참조 할 수도 있습니다. 인덱스가 인덱스를 기준으로 오른쪽에서 왼쪽으로 어느 정도 변경되어야한다는 것을 인식하고 조합을 복구해야하는 것을 구성 할 수 있습니다.

따라서 {1,2,3,4,5,6} 세트가 있으며 세 가지 요소가 필요합니다. {1,2,3}이라고 말하면 요소들 사이의 차이는 하나이며 순서는 최소입니다. {1,2,4}는 하나의 변경 사항이 있고 사전 식적으로 번호 2입니다. 따라서 마지막 위치의 '변경 사항'수는 사전 순서에서 하나의 변경을 설명합니다. 한 번의 변경 {1,3,4}가있는 두 번째 장소는 한 번의 변경이 있지만 두 번째 장소 (원래 세트의 요소 수에 비례)이기 때문에 더 많은 변화를 설명합니다.

내가 설명한 방법은 설정에서 색인에 이르기까지 해체로, 그 반대를 수행해야합니다. 이는 훨씬 까다로운 작업입니다. 이것이 버클 이 문제를 해결하는 방법입니다. 나는 약간의 변경 사항 으로 계산하기 위해 약간의 C를 작성했습니다. 나는 세트를 나타 내기 위해 숫자 범위가 아닌 세트의 인덱스를 사용했기 때문에 항상 0에서 n까지 작업하고 있습니다. 노트 :

  1. 조합은 순서가 정해지지 않기 때문에 {1,3,2} = {1,2,3}-사전 식 순서로 정렬합니다.
  2. 이 방법에는 첫 번째 차이에 대한 세트를 시작하는 암시 적 0이 있습니다.

사전 순서 (McCaffrey)의 조합 색인

다른 방법 의 개념 이해 및 프로그램에보다 쉽게이지만, 버클의 최적화를하지 않고 다음과 같습니다. 다행히도 중복 조합을 생성하지 않습니다.

세트 N에서 x_k ... x_1극대화 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).. 따라서 네 가지를 포함하는 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)

작고 간단한 조합 반복자

교훈적인 목적으로 다음 두 알고리즘이 제공됩니다. 그들은 반복자와 (보다 일반적인) 폴더 전체 조합을 구현합니다. 그것들은 복잡성 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
세트에 동일한 요소가 포함 된 경우 중복 조합이 생성됩니까?
토마스 Ahle

2
네 토마스입니다. 배열의 데이터와 무관합니다. 원하는 효과가 있거나 다른 알고리즘을 선택하면 항상 중복을 먼저 필터링 할 수 있습니다.
nlucaroni

19
멋진 답변입니다. 각 알고리즘에 대한 런타임 및 메모리 분석 요약을 제공 할 수 있습니까?
uncaught_exceptions

2
상당히 좋은 답변입니다. 20C3은 1140이고 느낌표는 계승처럼 보이기 때문에 혼란스럽고 계승은 조합을 찾기위한 공식을 입력합니다. 따라서 느낌표를 편집하겠습니다.
CashCow

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);
Dave Cousineau

1
당신은 재귀 루프를 사용하여이 쿼리의 정확한 비 LINQ 버전을 제공 할 수
irfandar

81

짧은 자바 솔루션 :

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

나는 20을 선택하고 10을 선택하고 이것은 나를 위해 충분히 빠른 것 같습니다 (1 초 미만)
demongolem

4
@NanoHead 당신이 잘못되었습니다. 이것은 반복없이 조합입니다. 그리고 당신의 경우는 반복됩니다.
잭 더 리퍼

이 코드는 웹에서보다 쉽게 ​​찾을 수 있어야합니다. 이것이 바로 내가 찾던 것입니다!
Manuel S.

방금 이것과 7 개의 다른 Java 구현을 테스트했습니다. 이것은 훨씬 빠릅니다. 두 번째로 빠른 것은 훨씬 느린 속도입니다.
스튜어트

77

이 문제에 재귀 파이썬 솔루션을 제시해도 될까요?

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)))적은 코드로 파이썬에서 같은 것을 달성 할 것입니다.
hgus1294

59
@ hgus1294 사실이지만, 그것은 부정 행위입니다. Op는 특정 프로그래밍 언어와 관련된 "매직"방법이 아닌 알고리즘을 요청했습니다.
MestreLion

1
첫 번째 루프 범위를 엄격하게 말해서는 안 for i in xrange(len(elements) - length + 1):됩니까? 파이썬에서 조각 인덱스를 벗어나는 것은 정상적으로 처리되지만 올바른 알고리즘이므로 파이썬에서는 중요하지 않습니다.
Stephan Dollberg

62

문자 배열이 "ABCDEFGH"와 같다고 가정하겠습니다. 현재 단어에 사용할 문자를 나타내는 세 개의 색인 (i, j, k)이 있습니다.

ABCDEFGH
^ ^ ^
아이크

먼저 k를 변경하므로 다음 단계는 다음과 같습니다.

ABCDEFGH
^ ^ ^
아이크

끝에 도달하면 계속 진행하고 j를 변경 한 다음 k를 다시 변경하십시오.

ABCDEFGH
^ ^ ^
아이크

ABCDEFGH
^ ^ ^
아이크

j가 G에 도달하면 i도 변화하기 시작합니다.

ABCDEFGH
  ^ ^ ^
  아이크

ABCDEFGH
  ^ ^ ^
  아이크
...

코드로 작성하면 다음과 같이 보입니다.

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자가 필요한 경우 어떻게해야합니까?) 질문을 이해 한 것처럼 문자 배열과 선택할 문자 수가 모두 제공됩니다. 물론이 문제를 해결하는 한 가지 방법은 명시 적으로 중첩 된 루프를 재귀로 바꾸는 것입니다.
joel.neely 2009

10
@ Dr.PersonPersonII 그리고 왜 삼각형이 OP와 관련이 있습니까?
MestreLion

7
이 솔루션을 언제든지 임의의 매개 변수를 사용하여 재귀 솔루션으로 변환 할 수 있습니다.
Rok Kralj

5
@RokKralj, 어떻게 "이 솔루션을 임의의 매개 변수로 재귀 적으로 변환합니까?" 나에게는 불가능 해 보인다.
Aaron McDaid

3
사용법에 대한 직관적 인 설명
Yonatan Simson

53

다음 재귀 알고리즘은 순서가 지정된 세트에서 모든 k- 요소 조합을 선택합니다.

  • i조합 의 첫 번째 요소 를 선택하십시오
  • 보다 큰 요소 집합에서 재귀 적으로 선택된 요소의 i각 조합 과 결합 합니다.k-1i

i세트에서 각각 에 대해 위를 반복하십시오 .

i반복을 피 하려면 나머지 요소를보다 크게 선택해야합니다 . 이 방법으로 [3,5]는 두 번이 아니라 [3]과 [5]를 결합한 것처럼 한 번만 선택됩니다 (조건이 [5] + [3]을 제거함). 이 조건이 없으면 조합 대신 변형을 얻을 수 있습니다.


12
많은 답변에서 사용되는 알고리즘에 대한 영어로 매우 잘 설명되어 있습니다
MestreLion

위의 두 번째; 특히, 이것은 user935714가 제기 한 솔루션을 이해하는 데 도움이되었습니다. 둘 다 우수합니다.
jacoblambert

25

C ++에서 다음 루틴은 [first, last) 범위 사이의 길이 거리 (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 2016 년

6
@Sergej Andrejev : 교체 beingbegin함께 s.begin(), 및 end과를 s.end(). 이 코드는 STL의 next_permutation알고리즘을 밀접하게 따르며 여기 에 더 자세히 설명되어 있습니다 .
Anthony Labarre

5
무슨 일이야? i1 = 마지막; --i1; i1 = k;
Manoj R

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

1
이것은 k를 선택하지 않습니다.
shinzou

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에서 실행되는 소프트웨어에는 적합하지만 리소스 제한 플랫폼 (예 : 내장)을 사용하는 경우 운이
나빠

또한 많은 목록을 할당하고 배열의 항목을 각각의 새로운 항목으로 복제하기 위해 많은 작업을 수행합니다. 전체 열거가 완료 될 때 까지이 목록을 수집 할 수없는 것처럼 보입니다.
Niall Connaughton

매끈하다. 알고리즘을 찾았습니까? 아니면 처음부터 알고 있습니까?
paparazzo

20

파이썬의 짧은 예 :

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의 모든 조합은 다음과 같습니다.

  • 나머지에서 2의 모든 조합을 가진 A (BCDE)
  • 나머지에서 2의 모든 조합을 가진 B (CDE)
  • 나머지에서 2의 모든 조합을 갖는 C (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요소를 선택 xs합니다 combinations. 기능의 최종 결과는 각각의 요소 인리스트이다 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

그리고 여기에 매우 악의적 인 언어 인 할아버지 코볼 이옵니다.

각각 8 바이트의 34 개 요소로 구성된 배열 (순전히 임의의 선택)을 가정 해 봅시다. 가능한 모든 4 요소 조합을 열거하여 배열에로드하는 것이 좋습니다.

우리는 4 개의 그룹에서 각 위치마다 하나씩 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.

그러나 왜 {} {} {} {}
shinzou

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

LINQ를 사용하여 구조 또는 배열의 필드에 액세스하거나 문자 필드가 "Letter"인 "Alphabet"이라는 테이블이있는 데이터베이스에 직접 액세스하는 경우 SQL 구문을 사용할 수있는 경우 다음을 조정할 수 있습니다. 암호:

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

"알파벳"테이블에있는 문자 수 (3, 8, 10, 27 등)에 관계없이 3 자 조합을 모두 반환합니다.

원하는 것이 조합이 아닌 모든 순열 인 경우 (즉, "ACB"와 "ABC"가 한 번만 표시되는 것이 아니라 다른 것으로 계산되도록하려면) 마지막 행 (AND 하나)을 삭제하면됩니다.

사후 편집 : 질문을 다시 읽은 후에 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

과제는 k를 초과하는 n을 만족시키는 모든 조합을 생성하는 것입니다. 이항 계수 는 고정 된 n 개의 요소 집합에서 순서없는 k 개의 부분 요소를 선택하는 방법에 대한 질문에 답합니다 . 따라서 제안 된 알고리즘이 수행해야 할 작업을 수행합니다.
Christoph

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

파이썬에서 프로젝트 오일러에 사용한 순열 알고리즘을 사용했습니다.

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

클로저 버전 :

(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"와 같다고 가정하겠습니다. 현재 단어에 사용할 문자를 나타내는 세 개의 색인 (i, j, k)이 있습니다.

ABCDEFGH
^ ^ ^
아이크

먼저 k를 변경하므로 다음 단계는 다음과 같습니다.

ABCDEFGH
^ ^ ^
아이크

끝에 도달하면 계속 진행하고 j를 변경 한 다음 k를 다시 변경하십시오.

ABCDEFGH
^ ^ ^
아이크

ABCDEFGH
^ ^ ^
아이크

j가 G에 도달하면 i도 변화하기 시작합니다.

ABCDEFGH
  ^ ^ ^
  아이크

ABCDEFGH
  ^ ^ ^
  아이크
...
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 기반 이지만 더 추상적입니다.


이 끔찍한 언어는 무엇입니까? 세게 때리다?
shinzou

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'에서 'dab'wk = 3으로 변경할 수 있습니다.

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 에이스 ade bcd bce bde cde



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

간결한 자바 스크립트 솔루션 :

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까지 센다.
  • 각 숫자를 이진 표현으로 변환하십시오.
  • 위치에 따라 각 'on'비트를 세트의 요소로 변환하십시오.

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 비트가 'on'일 때만 인쇄하십시오.

k 길이 서브 세트 대신 모든 서브 세트를 원하면 cnt / kElement 파트를 제거하십시오.

(증거에 대해서는 Lehman et al., 컴퓨터 섹션 11.2.2의 MIT 무료 코스웨어 수학 수학 참조) https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics- 컴퓨터 과학 가을 -2010 / 판독 / )


3

짧은 파이썬 코드, 색인 위치 생성

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

2

이항 계수로 작업하기위한 일반적인 함수를 처리하는 클래스를 작성했습니다. 이는 문제의 유형입니다. 다음과 같은 작업을 수행합니다.

  1. 모든 K- 색인을 N을 선택하여 파일에 멋진 형식으로 출력합니다. K- 색인은 더 설명적인 문자열이나 문자로 대체 될 수 있습니다. 이 방법을 사용하면 이러한 유형의 문제를 해결하는 것이 매우 간단합니다.

  2. K- 색인을 정렬 된 이항 계수 테이블에서 항목의 적절한 색인으로 변환합니다. 이 기술은 반복에 의존하는 이전에 게시 된 기술보다 훨씬 빠릅니다. 파스칼의 삼각 (Pascal 's Triangle) 고유의 수학적 속성을 사용하여이를 수행합니다. 내 논문은 이것에 대해 이야기합니다. 나는이 기술을 발견하고 출판 한 최초의 사람이라고 생각하지만 틀릴 수도있다.

  3. 정렬 된 이항 계수 테이블의 인덱스를 해당 K- 인덱스로 변환합니다.

  4. 사용 마크 주인님의 오버 플로우에 훨씬 덜 가능성이 더 큰 숫자와 함께 작동 이항 계수를 계산하는 방법을.

  5. 이 클래스는 .NET C #으로 작성되었으며 일반 목록을 사용하여 문제와 관련된 개체 (있는 경우)를 관리하는 방법을 제공합니다. 이 클래스의 생성자는 InitTable이라는 부울 값을 가져옵니다. true 인 경우 관리 할 객체를 보유 할 일반 목록을 만듭니다. 이 값이 false이면 테이블이 작성되지 않습니다. 위의 4 가지 방법을 수행하기 위해 테이블을 만들 필요는 없습니다. 테이블에 액세스하기위한 액세서 메소드가 제공됩니다.

  6. 클래스와 그 메소드를 사용하는 방법을 보여주는 관련 테스트 클래스가 있습니다. 2 가지 사례로 광범위하게 테스트되었으며 알려진 버그가 없습니다.

이 클래스에 대해 읽고 코드를 다운로드하려면 이항 계수 계산을 참조하십시오 .

이 클래스를 C ++로 변환하는 것은 어렵지 않습니다.


내가 언급 한 것처럼 적어도 850 년이되어 생각하기가 어렵 기 때문에 "Mark Dominus 방법"이라고 부르는 것은 실제로 올바르지 않습니다. 그것을 Lilavati 방법 이라고 부르지 않겠습니까?
MJD
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.