이번 디브온에서 미니대안언어축제가 진행되던 M2 밖에 텍스트큐브 부스에서 재미있는 코드골프 문제 풀기가 있었습니다. 150자 이하로 푸신 분들에게는 즉석에서 제공되는 원두커피와 텀블러가 상으로 주어졌다고 합니다. 문제는 아래와 같습니다. 이 결과가 나와야 하는데 언어 제약은 없답니다.


*
*
* *
* *
* *
** **
* *
* *
* *
*
*


어제 1등은 강성훈님이 gzip으로 출력문자를 압축해서 쉘 스크립트 뒤에 덧붙이는 식으로 하여 73바이트까지 줄인 것이라고 합니다.

저는 집에 와서 J로 잠깐 풀어봤습니다. 30바이트입니다.

' *'{~(,{:)@|:@|.^:4]4=+/~|i:4


일단 이 코드를 이해하려면 J의 이디엄들을 이해해야 합니다. 몇 가지 덩어리가 눈에 띄는데요.

' *'{~(,{:)@|:@|.^:4]4=+/~|i:4


이 부분은 0과 1을 각기 빈칸과 *로 바꾸는 코드입니다. 뒤에 달린 ~는 능동형을 수동형으로 바꿔주는 것인데, 예를 들어 3-1은 2인데, 3-~1은 -2가 됩니다. 즉, 좌측과 우측 피연산자(operand)의 순서를 바꿔주는 부사(동사를 꾸며주는)입니다. 원래 형태는 0 1 3 2 1 {'ABCDEFG' 하면 오른쪽에서 0번째, 1번째, 3번째, 2번째, 1번째로 이루어진 스트링(ABDCB)을 반환하는 것이죠. 따라서 오른쪽에서 0과 1로 이루어진 행렬이 나오는데 그걸 빈칸과 *로 바꿔주는 역할을 합니다.

' *'{~(,{:)@|:@|.^:4]4=+/~|i:4


그 다음 부분은 J에서 포크라고 부르는 것인데, g와 h가 동사일 때 (g h) y는 y g (h y)와 같습니다. h한 걸 g한다라고 이해하면 쉽습니다. ,는 스트링이나 숫자를 추가(append)하는 것이고, {:는 마지막 원소를 가져오는(take last) 것입니다. 따라서 마지막 원소를 뒤에 추가한다고 보시면 됩니다.

' *'{~(,{:)@|:@|.^:4]4=+/~|i:4


이 부분은 행렬을 시계방향으로 90도 회전시킵니다. |:는 행렬의 행과 열을 바꿔주고(Axy를 Ayx로), |.는 행렬의 원소 순서(예컨대 행의 순서)를 거꾸로 뒤집습니다. 순서가 |.한 다음 |:이므로 결과적으로는 시계방향으로 90도 회전이 됩니다.

예컨대(아래 예에서 처음 세 칸 띄어쓴 것은 사용자가 입력하고 붙어나오는 건 컴퓨터가 보여주는 결과이며, NB에서 해당줄 끝까지는 주석)


i.3 3
0 1 2
3 4 5
6 7 8
|. i.3 3
6 7 8
3 4 5
0 1 2
|: i.3 3
0 3 6
1 4 7
2 5 8
|:@|.i.3 3
6 3 0
7 4 1
8 5 2
|:@|. |:@|.i.3 3 NB. 두 번 90도 회전하면 180도 회전과 같음
8 7 6
5 4 3
2 1 0


' *'{~(,{:)@|:@|.^:4]4=+/~|i:4


이 부분은 앞의 동사를 4번 연속 적용하게 됩니다. 우리가 수학에서 아는 지수승(거듭 곱하기)의 외연을 확장한 겁니다. 예를 들어


+: 3 NB. +:는 인자를 두 배 하는(double) 동사
6
+: +: 3 NB. 두 배 한 것에 다시 두 배 하면 네 배
12
+: +: +: 3
24
+:^:(3) 3
24
+:^:(4) 3
48
+:^:(0 1 2 3 4) 3
3 6 12 24 48


앞 동사가 90도 회전한 다음에 맨 아래 원소(2차원 행렬에서 원소는 1차원 배열)를 복사해 붙이기였으므로, 이걸 4번 연속하면 원래 행렬의 네 테두리줄을 돌아가며 복사하는 셈이 됩니다. (마름모 꼭지점에서 하나씩 튀어나온 부분 처리)

' *'{~(,{:)@|:@|.^:4]4=+/~|i:4


이 부분은 마름모를 만드는 코드입니다. 아래를 보시면 이해가 되실 겁니다.

i:3 NB. i: y는 -y에서 y까지의 정수 배열을 만듭니다(steps)
_3 _2 _1 0 1 2 3
|i:3 NB. 절대값
3 2 1 0 1 2 3
+/~|i:3 NB. 3 2 1 0 1 2 3을 서로 더하되(+) 한 원소와 전체 배열을(/) 더해서 테이블을 만듦
6 5 4 3 4 5 6
5 4 3 2 3 4 5
4 3 2 1 2 3 4
3 2 1 0 1 2 3
4 3 2 1 2 3 4
5 4 3 2 3 4 5
6 5 4 3 4 5 6
3=+/~|i:3 NB. 3과 같으면 1 아니면 0
0 0 0 1 0 0 0
0 0 1 0 1 0 0
0 1 0 0 0 1 0
1 0 0 0 0 0 1
0 1 0 0 0 1 0
0 0 1 0 1 0 0
0 0 0 1 0 0 0


전체 코드의 실행순서는 위에서 설명한 것의 역순으로 됩니다. 따라서 마름모를 만들고, 그걸 90도씩 돌려가면서 꼭지점에 하나씩 점을 찍고(사실은 가장 바깥 라인을 복사), 그 행렬을 문자로 변환해주면 결과가 나옵니다.