영어 번역본을 여기서 읽을 수 있고 이 포스트를 트위터에서도 볼 수 있다.
이번 달 2021년의 Advent of Code (AoC) 코딩 챌린지가 시작되었다. AoC에서 출제되는 퍼즐들은 재미있고 쉽게 풀 수 있어서 해마다 수많은 사람들이 새로운 프로그래밍언어를 배우거나 이미 사용중인 프로그래밍 언어를 연습하기 위해 참가한다.
첫날의 퍼즐을 해결하다가 아희 언어로 어떻게 해결할 수 있을지 방법을 떠올리게 되었다. 또한 Manim 애니메이션 라이브러리로 나의 아희 코드의 실행에 대한 애니메이션들을 만들었다!
이 포스트에서는, 먼저 아희에 대해서 소개한 이후에, 나의 해결법에 대해서 설명해 보겠다.
아희는 한글로 쓰는 난해한 프로그래밍 언어이다. 아래와 같이, 아희 언어 프로그램들은 한글 낱자를 2차원 배열로 나열한 형태인데, 각 낱자는 컴퓨터 명령을 의미한다. 예시로, 이 프로그램은 “Hello, world!”를 출력한다:
밤밣따빠밣밟따뿌
빠맣파빨받밤뚜뭏
돋밬탕빠맣붏두붇
볻뫃박발뚷투뭏붖
뫃도뫃희멓뭏뭏붘
뫃봌토범더벌뿌뚜
뽑뽀멓멓더벓뻐뚠
뽀덩벐멓뻐덕더벅
전체적인 아희 언어에 대한 설명은 여기에서 살펴볼 수 있지만, 아희에 대해 간략하게 소개해본다.
첫번째로, 각 낱자마다, 초성 중성 그리고 종성은 다른 쓰임새로 사용된다. 초성은 명령, 중성은 움직임, 그리고 종성은 parameter를 의미한다. 초성은 add, pop, compare, store 등과 같은 함수이고, 중성은 어떻게 grid에서 움직일지 (예를 들면 중성이 ㅏ
이면 다음 셀은 바로 오른쪽에 있는 셀일 것임) 그리고 종성은 함수에 주어질 값이 무엇인지를 알려준다.
처음으로 최상단 제일 왼쪽 셀(예시에서는 밤
)에서 프로그램이 시작하고 낱자의 초성이 ㅎ
인 셀에 이를 때까지, 명령에 따라 함수를 실행시키고, 다음 위치로 이동하는 행동을 반복하는데, 모든 iteration에서는 위에 설명한 것과 같이 각 낱말의 초성(명령), 종성(변수)에 따라 동작이 실행된다.
아희 언어에서는 stack과 queue들을 사용할 수 있는데, 현재 사용중인 자료구조에 저장된 값들과 각 셀의 종성 parameter를 이용해서 함수를 실행한다.
하지만 parameter의 수가 부족하면 다른 행동을 하게 된다. 예를 들어 명령이 add 였고, 이 함수를 사용하기 위해서는, 변수를 2개 주어야 하는데, parameter 값에 한 개만 주어졌다면, 셀의 명령을 실행하지 않고 중성의 방향 값이 뒤바뀌게 된다. 만약 중성이 ㅏ
였다면, ㅓ
로 뒤바뀌어서 오른쪽이 아닌 왼쪽으로 움직인다.
아희의 설명서를 살펴보면 standard-in/out으로 읽고 쓰는 방법에 대한 내용이 나와 있지만, 파일을 어떻게 읽을 수 있는지에 대해서는 언급하지 않고있다. AoC 퍼즐을 해결하기 위해서, 파일에서 input을 읽어와야 하므로 input을 읽을 수 있도록 아희 interpreter 코드를 수정해 주었다. 이후, 프로그램에서 input 파일의 end-of-file까지 모두 다 읽은 후 프로그램이 다시 읽도록 시도할 때 위에서 설명했던 "parameter가 모자란 경우"처럼 코드의 진행 방향이 뒤바뀐다.
AoC 첫날의 퍼즐은 다음과 같은 질문이었다: “수의 목록에서 숫자 바로 뒤에 더 큰 숫자가 오는 경우가 몇 번 발생되었니?”
예를 들면 숫자 목록이 1, 4, 2, 5, 7
이면 1->4
, 2->5
, 5->6
과 같이 세 차례 등장하므로 정답은 3이다.
나는 아희를 이용해서 아래와 같이 해결했다:
삼바상뱡숨방파빠파주
마르코하멍송더섬썸퍼
이 프로그램에서는 여러가지 명령어를 사용했다. 아래는 사용된 아희 명령어들에 대한 설명이다 (초성 지수에 따라):
더 -> add
현재 자료구조의 1번째 값을 pop해서 더한 후에 넣음
종성은 영향이 없음
ㅁ -> print
현재 자료구조의 1번째 값을 pop해서 출력함
종성은 ㅇ
이면 숫자로 출력하지만 “ㅎ”이면 unicode로 출력함
ㅂ -> push
종성이 ㅇ
/ㅁ
이면 파일을 읽어서 현재의 자료구조에 넣음
종성이 없으면 0
을 넣음
다른 종성일 경우, 종성의 획 수에 해당하는 숫자를 자료구조에 넣음
ㄱ -> 2
, ㅃ -> 8
, 등
1
을 만들 종성이 없음
ㅃ -> duplicate
현재 자료구조의 첫번째 값을 복사해서 해당 자료구조에 넣음
종성은 영향이 없음
ㅅ -> 자료구조 선택함
종성은 무슨 자료구조로 변화함
ㅁ -> queue
ㅁ 아닌 종성 -> stack
종성에 따라 특정한 stack자료구조를 선택함
ㅆ -> transfer
현재 자료구조의 1번째 값을 다른 자료구조로 보냄
종성은 보내는 자료구조의 정보를 알려줌
ㅈ -> compare
현재 자료구조의 1, 2번째 값들을 비교하고 1번째 값이 더 크면 1
을 넣고 아니면 0
을 넣음
종성은 영향이 없음
ㅍ -> swap
현재 자료구조의 1, 2번째 값들을 바꿈
종성은 영향이 없음
ㅎ -> terminate
프로그램이 종료됨
삼바상뱡숨방파빠파주
마르코하멍송더섬썸퍼
나의 프로그램은 4부분으로 나누어져 있다. 첫 번째는 삼바상뱡
이다. 이 부분에서는, 프로그램을 초기화하기 위해 파일의 첫 숫자를 읽어서 queue에 넣는다. Stack-counter 값을 초기화하기 위해, 0을 stack에 미리 넣어준다. 이 후에 숫자를 읽을 때마다 각 숫자들을 비교한 결과에 따라 0
이나 1
을 그 stack에 넣고 stack에 저장된 숫자들을 add 명령을 이용해서 더한다. 이 부분의 마지막 낱자 뱡은 ㅑ
중성을 갖고 있다. 이 중성은 셀을 2번 오른쪽으로 움직이라는 의미가 있어서 실행한 후 숨
이 아닌 방
셀에 도달할 것이고 여기서부터 프로그램의 두 번째 부분이 시작된다.
두 번째 부분은 아래와 같다:
방파빠파주
송더섬썸퍼
이부분에서는, 반복해서 숫자를 읽고 그 전 숫자과 비교하고 stack counter를 갱신한다. 중성들은 cycle을 (방 -> 파 -> 빠 -> 파 -> 주 -> 퍼 -> 썸 -> 섬 -> 더 -> 송 -> 방 -> ...
) 만든다. 그래서 숫자가 모두 읽혀져서 없어질 때까지 이 cycle의 명령들이 반복해서 실행된다.
더이상 읽을 숫자가 없어질 때 방
명령이 실행되지 못하고 방향이 뒤바껴서 바로 다음 셀은 숨
이 되고 그 다음 셀은 멍
이 되고 마지막으로 하
에서 프로그램이 종료된다. 즉, 이 숨/방/하
낱자들은 이 프로그램의 세번째 부분이다.
마지막 네 번째 부분은 마르코
이다. 이 부분은 프로그램이 절대 도달할 수가 없는 부분이고, 프로그램을 직사각형 모양으로 유지하기 위해 나의 이름을 넣었다.
이 두 애니메이션으로 위에서 설명한 아희 프로그램이 어떻게 실행하고 종료되는지 알 수 있다.
첫번째 날의 두 번째 퍼즐도 아희를 이용해서 해결했다. 각 숫자를 비교하는 대신, 이어진 세 숫자를 더한 합을 비교하기 때문에 첫 번째 퍼즐과 조금 다르다. Python을 이용하면 이렇게 해결할 수 있다:
if __name__ == '__main__':
count = 0
f = open('input.txt', 'r')
x, y, z = int(f.readline()), int(f.readline()), int(f.readline())
for line in f:
a = int(line)
if a > x: count += 1
x, y, z = y, z, a
print(count)
아래와 같이 아희로 해결했다:
삼바상방방방샨숨방빠쌍상싼산반분
마르코코그넷허멍손더섬썸저어더너
이 프로그램도 4부분으로 나누어져 있다. 처음으로, 삼바상방방방샨
부분에서는, 프로그램을 초기화 하기 위해 input에서 첫 3개의 숫자들을 읽어서 queue에 넣고 stack을 초기화 한다.
방빠쌍상싼산반분
손더섬썸저어더너
위에 있는 코드는 반복해서 다음 숫자를 읽고, 그 숫자와 3회 전에 등장한 숫자를 비교한다는 뜻이다.
비교를 의미하는 ㅈ
명령은 >
이 아닌 ≥
를 의미해서, 최신 숫자와 3회 전에 등장한 숫자를 비교하기 위해서는 최신 숫자에 1을 더해야 한다.
아희에서 ㅂ
(push) 명령어를 이용할 때, 1
을 parameter로 줄 수 있는 방법이 없기 때문에 (자음 중에 한 획으로 이루어진 자음이 없고, ㅇ
은 input 을 읽는데 쓰이기 때문에), 1
을 만들기 위해 반/분/너
명령어들을 이용한다. 반/분
은 숫자 2
를 스택에, 총 2회 넣는 동작을 하고, 너
는 ㄴ
(divide) 명령어를 이용해, 스택에서 pop을 2회 하고, 리턴된 숫자 두 개를 서로 나누는 방식으로 1
을 생성한다.
다음 부분은:
....숨...
..허멍..
이고 프로그램의 결과를 출력한 후 프로그램을 종료시킨다.
마지막 부분인 마르코코그넷허
는, 다시 나의 이름이다. 보통 내 이름을 한글로 쓸 때, 마르코 코그넷*터*
로 쓰지만 이 프로그램의 모양을 유지하기 위해, 그리고 넷터
와 넷허
의 발음이 비슷하기 때문에 나의 이름을 조금 변형시켜서 코드에 적용했다.
이 애니메이션으로 나의 프로그램이 어떻게 실행하는지 알 수 있다.