수식을 조합해서 1~40을 만들 수 있는 수 찾기

이 문제는 작년에 조카의 수행평가 관련해서 질문 받은 것인데, 네 개의 서로 다른 숫자와 기본연산자를 조합하여 1~40의 모든 정수를 만들 수 있는 숫자를 찾는 문제였다. 사실 이 문제가 코딩 문제가 아닌 수학 수행 평가라는 점에서 눈을 의심하지 않을 수 없는데, 4개의 숫자로 구성되는 순열(24개)에 대해서, 각 숫자 사이에 들어갈 수 있는 연산자의 경우는 중복을 허용하여 5가지 (사칙연산에 필요한 4개와 연산자를 쓰지 않고 숫자를 붙이는 경우 1개 더)이기 때문에, 숫자 4개가 결정되었을 때 만들 수 있는 식의 개수는 무려 3,000 개이다. 1~9의 9개 숫자 중에서 4개를 선택하는 경우는 126가지이므로 4개의 숫자를 사용해서 만들 수 있는 식의 경우는 총378,000 가지이고, 이 문제는 전수 검사 (운이 좋다면 중간에 40가지의 수를 만드는 식을 만들 수 있는 조합을 찾을 수 있겠지만)를 통해서 찾아야 하는 수 밖에 없고, 결국 가능한 경우를 찾으려면 최악의 경우 거의 38만개의 식을 검사해야 하는데 이걸 컴퓨터의 힘을 빌리지 않고 수행평가를 하라는 것인지 이해가 가지 않았기 때문이다.

컴퓨터를 사용해서 이 문제를 해결하는 방법 자체는 간단한데, 의외로 실행 시간이 오래 걸릴 수는 있다. 이 문제에서는 어떻게든 숫자 4개의 조합에서 1~40까지를 만들 수 있는 경우를 찾으면 바로 종료하여 최대한 시간을 단축하는 방법을 사용했다.

접근방법

먼저 4개의 숫자와 사칙연산 연산자로 수식을 만드는 방법을 생각해보자. 4개의 숫자에는 연산자가 들어갈 수 있는 자리가 3개 있다. (맨앞에 -가 오는 경우도 포함시킬 수 있는데, 일단 여기서는 제외하고 생각한다.) 이 3개의 자리에 대해서 각 자리에는 4개의 연산자 중 하나 혹은 빈 문자열이 올 수 있다. 가능한 모든 조합으로 숫자와 연산자의 리스트를 만들고 이를 join() 해서 수식을 만들 수 있다. 만들어진 수식은 간단하게 eval() 함수를 사용해서 평가하는 것으로 한다.

4개의 숫자는 고정된 것이 아니라 순서를 바꿔가면서 검사하도록 한다. (순서까지 고정된 조합에서 연산자만 변경해서는 1~40 범위의 값을 모두 만드는 솔루션이 존재하지 않는다.)

검사하려는 값 중에서 정수가 아니거나, 지정된 범위 (1…40)을 벗어나는 경우를 제외한다. 추후에 확인을 위해서 계산 결과와 수식을 짝지어서 내놓는 제너레이터를 작성할 것이다. 제너레이터의 코드는 아래와 같다.1참고로 여기서 나눗셈 기호 문자(÷)는 vim에서 digraph로 입력했다. 입력 모드에서 <C-k>:- 를 순서대로 누르면 된다.

from itertools import product, permutations, combinations

def generate_expr(nums: tuple(str)):
    ops = [''] + list('+-*/')
    for ns in permutations(nums, 4):
        for op in product(ops, repeat=3):
            temp = [None] * 7
            temp[::2] = ns
            temp[1::2] = op
            expr = ''.join(temp)
            val = eval(expr)
            if val == int(val) and 0 < val <= 40:
                text = expr.replace('*', '×').replace('/', '÷')
                yield (int(val), text)

이제 위 제너레이터를 숫자 4개의 조합으로 호출하면, 해당 숫자로 만들 수 있는 1~40 사이의 값을 만드는 모든 수식을 얻을 수 있다. 그러면 각 조합에 대해서 위 제너레이터를 통해서 수식을 체크하고 1~40 사이의 값에서 빠짐없이 값을 얻을 수 있는지 체크한다. 결과의 수식을 표시하기 위해서 결과는 사전에 담아 체크하도록 했다.

def main():
    for digits in combinations("1234567890", 4):
        result = dict(zip(range(1, 41), [None] * 40))
        for v, expr in generate_expr(digits):
            result[v] = expr
        if all(v is not None for v in result.values()):
            for k, v in result.items():
                print(f"{v} = {k}")
            # 첫 결과를 출력한 후에 바로 종료
            result

if __name__ = "__main__":
    main()

가능한 조합은 (2, 3, 4, 6)이며, 찾아낸 식은 다음과 같다. 참고로 전수 검사를 했을 때 가능한 숫자 조합은 하나가 더 있었다. (실제로 전수조사를 해도 그리 시간이 오래 걸리지는 않는다.)

 1 = 6÷4÷3×2
 2 = 64÷32  
 3 = 6÷4+3÷2
 4 = 6×4÷3÷2
 5 = 6+4-3-2
 6 = 6×4÷3-2
 7 = 6-4+3+2
 8 = 6-4+3×2
 9 = 6÷4×3×2
10 = 6×4÷3+2
11 = 6+4+3-2
12 = 6+4×3÷2
13 = 6×3÷2+4
14 = 6+32÷4 
15 = 6+4+3+2
16 = 6×4÷3×2
17 = 6+4×2+3
18 = 6×4-3×2
19 = 6×4-3-2
20 = 6+4×3+2
21 = 63-42  
22 = 4+36÷2 
23 = 6×4-3+2
24 = 6×3+4+2
25 = 6×4+3-2
26 = 6×3+4×2
27 = 6-3+24
28 = 62-34
29 = 6×4+3+2
30 = 6×4+3×2
31 = 43-6×2
32 = 64-32
33 = 6+4+23
34 = 6-4+32
35 = 64÷2+3
36 = 6×4×3÷2
37 = 6÷2+34
38 = 6+34-2
39 = 63-24
40 = 6×3×2+4

Read more

워드프레스에서 고스트로 이전

워드프레스에서 고스트로 이전

이 글을 쓰면서도 믿기 힘든 사실인데, 블로그라는 걸 처음 시작한지가 20년이 되었습니다. 이글루스에서 처음 시작했다가, SK컴즈가 인수한다고 발표함과 동시에 워드프레스로 플랫폼을 옮겼죠. 워드프레스오 옮긴 이후에는 호스팅 환경을 이리 저리 옮기긴 했지만 거의 18년 가까이 워드프레스를 사용해온 것 같습니다. 그 동안 워드프레스는 블로깅 툴에서 명실상부한 범용CMS로 발전했습니다. 사실 웬만한 홈페이지들은 이제

By sooop
띄어쓰기에 대한 생각

띄어쓰기에 대한 생각

업무 메일을 쓸 때 가장 많이 쓰는 말 중에 하나가 메일 말미에 ‘업무에 참고 부탁 드립니다.‘인데요, 어느 날부터 아웃룩에서 이 ‘부탁 드립니다’가 틀렸다고 맞춤법 지적을 하기 시작했습니다. 맞는 말은 ‘부탁드립니다’라고 붙여 쓰는 거라고. 사실 아래아한글 시절부터 이전의 MS워드까지, 워드프로세서들의 한국어 맞춤법 검사 실력은 거의 있으나 마나 한

By sooop

구글 포토에서 아이클라우드로 탈출한 후기

한 때 구글 포토가 백업 용량을 무제한으로 제공해 주겠다고해서, 구글 포토를 사용해서 사진을 백업해왔습니다. 물론 이 이야기의 결말은 저나 이 글을 읽고 있는 여러분이나 모두 알고 있습니다. 사실 AI에게 학습 시킬 이미지 데이터를 모으기 위한 것일 뿐이라거나 하는 이야기는 그 당시에도 있었습니다만, 에이 그래도 구글인데 용량은 넉넉하게 주겠지…하는 순진한

By sooop

Julia의 함수 사용팁

연산자의 함수적 표기 Julia의 연산자는 기본적으로 함수이며, 함수 호출 표기와 같은 방식으로 호출하는 것이 가능합니다. 또한 그 자체로 함수이기 때문에 filter(), map() 과 같이 함수를 인자로 받는 함수에도 연산자를 그대로 적용하는 것이 가능합니다. 특히 + 연산자는 sum() 함수와 같이 여러 인자를 받아 인자들의 합을 구할 수 있습니다. 2 + 3 # = 5 +(2,

By sooop