지식iN에 "A, B, C로 만들 수 있는 경우를 전부 뽑고 싶다"는 질문이 꽤 자주 올라옵니다. 같은 글자를 여러 번 써도 되는 경우를 다 나열하는 문제인데, 글자 수가 고정이면 For 문을 글자 수만큼 겹쳐서 풀 수 있지만 입력이 3글자일지 5글자일지 모르면 그 방법이 막힙니다. 아래 매크로는 반복문을 겹치지 않고 번호 하나로 모든 경우를 계산합니다.
엄밀히 말하면 순서가 다르면 다른 결과로 치고 같은 글자도 다시 쓰니 수학 용어로는 "중복순열"입니다. 글자가 n개면 n의 n제곱 가지가 나옵니다.
매크로 코드
Sub program1472_com()
Dim str As String, T As String
str = InputBox("조합할 문자를 넣어주세요.", "문자조합", "ABCDE") '// 조합할 문자를 입력받는다
Dim i As Long, n As Integer
Dim V As Variant, D As Variant, x As Variant
Dim Max As Long, cnt As Integer
cnt = Len(str) '// 문자의 갯수
Max = cnt ^ cnt '// 모든 조합의 수를 구함
ReDim V(1 To cnt) '// 문자를 넣을 배열 생성
For i = 1 To cnt '// 문자의 길이만큼 순환
V(i) = Mid(str, i, 1) '// 배열에 문자를 담는다.
Next
ReDim x(1 To cnt + 1) '// 각 열별 조합 갯수를 구할 배열을 만듬
x(cnt + 1) = 1 '// 첫번째 문자의 Step 1
For i = cnt To 1 Step -1 '// 문자의 갯수만큼
x(i) = x(i + 1) * cnt '// 해당 열의 조합 갯수(Step)를 구함
Next
ReDim D(1 To Max) '// 전체 조합 갯수만큼 배열을 만듬
For i = 1 To Max '// 전체 조합 갯수만큼 순환하면서
T = "" '// 조합 문자를 담기위하여 비움
For n = 2 To cnt + 1 '// 조합할 문자 갯수만큼 순환하면서
If Len(T) Then T = T & "," '// 조합문자가 비어있지 않으면 ,를 삽입
T = T & V((((((i - 1) \ x(n)) + 1) - 1) Mod cnt) + 1) '// 해당 열의 문자를 생성
Next
D(i) = T '// 조합한 문자를 배열에 담음
Next
[A1].Resize(Max).value = Application.Transpose(D) '// 시트에 행열을 바꾸어 뿌려줌
End Sub
번호 하나로 각 자리 글자를 구하는 원리
핵심은 n진법입니다. 글자가 3개면 경우의 번호 0~26을 3진법으로 쓰면 000, 001, 002, 010 ... 222 가 되고, 각 자릿수를 글자 번호로 바꾸면 그게 곧 결과입니다. 코드의 x 배열은 각 자리의 자릿값(3진법이면 9, 3, 1)을 담는 용도입니다. 맨 끝 x(cnt + 1) 을 1로 두고 뒤에서부터 cnt 를 곱해 올라가니, x(2) 가 가장 큰 자리, x(cnt + 1) 이 가장 작은 자리가 됩니다. 안쪽 루프가 n = 2 부터 시작하는 이유가 이것이고, x(1) 은 전체 개수와 같은 값이라 실제로는 쓰이지 않습니다.
각 자리 글자는 (i - 1) \ x(n) 으로 그 자리의 몫을 구하고 Mod cnt 로 0부터 cnt - 1 사이 숫자를 얻은 다음, 배열이 1부터 시작하니 1을 더해 V 에서 꺼냅니다. 식 안에 + 1) - 1 이 겹쳐 있어 복잡해 보이지만 서로 상쇄되는 부분이라 계산 결과는 위 설명과 같습니다.
결과는 모두 배열 D 에 모은 다음 Application.Transpose 로 세로 방향으로 바꿔서 A1부터 한 번에 붙여 넣습니다. 셀에 한 줄씩 쓰는 것보다 훨씬 빠릅니다.
실행 결과
입력창에 ABC 를 넣으면 A열에 아래 27줄이 채워집니다.
A,A,A
A,A,B
A,A,C
A,B,A
A,B,B
A,B,C
A,C,A
A,C,B
A,C,C
B,A,A
B,A,B
B,A,C
B,B,A
B,B,B
B,B,C
B,C,A
B,C,B
B,C,C
C,A,A
C,A,B
C,A,C
C,B,A
C,B,B
C,B,C
C,C,A
C,C,B
C,C,C
글자 수를 늘릴 때 주의
경우의 수가 n의 n제곱이라 아주 빠르게 커집니다. 기본값인 ABCDE 는 3,125줄이지만, 7글자면 823,543줄이고 8글자면 1,600만 줄이 넘어 엑셀 시트의 최대 행 수(1,048,576)를 넘깁니다. 그 전에 Application.Transpose 가 버전에 따라 많은 원소에서 오류를 내기도 하니, 6~7글자 이상을 다룰 일이 있다면 결과를 2차원 배열(ReDim D(1 To Max, 1 To 1))에 바로 담아 Transpose 없이 붙여 넣는 쪽으로 바꾸는 게 좋습니다.
입력값이 비어 있으면 ReDim V(1 To 0) 에서 오류가 나니, InputBox 를 취소했을 때를 대비해 If Len(str) = 0 Then Exit Sub 한 줄을 넣어 두면 깔끔합니다. 또 같은 글자가 입력에 두 번 들어가면(예: AAB) 결과에도 똑같은 줄이 여러 번 나오니, 중복 없는 결과가 필요하면 입력 단계에서 겹치는 글자를 먼저 걸러 내세요.