파이썬 코드 골프

이번 파이콘 2018에서 레블업에서 진행하는 코드 골프에 참가했습니다. 코드 골프를 처음 알게 되어 시도 해봤는데 그 경험을 공유 합니다.

코드 골프란?

views
출처(https://www.barrymichaeldoyle.com/code-golf/)

코드와 골프는 과연 무슨 관계가 있을까요? 골프는 가장 적은 타수로 공을 홀에 넣는 경기입니다. 코드 골프는 이와 유사하게 가장 적은 타수, 즉 가장 적은 문자수로 알고리즘을 동작 시키는 경기입니다. 무려 코드 골프를 위한 싸이트도 존재하고, 도 있습니다.

문제의 설명

이번에 제가 풀었던 문제 중 상대적으로 간단한 두번째 문제입니다. (아래 예제 그림만 봐도 대략 이해가 됩니다.)

입력

첫번째 입력으로는 그릴 다이아몬드의 개수 n(n>0)이 들어오고, 이후 n번만큼 각 다이아몬드의 크기 s가 주어집니다. s는 항상 양의 홀수임이 보장됩니다.

출력

출력 영역은 가장 큰 다이아몬드의 크기(max(s))가 행과 열 개수가 됩니다. 이 영역의 중앙을 기준으로 각 다이아몬드를 겹쳐서 그리는데, 다이아몬드의 선에 해당되는 부분은 asterisk 기호(*)로, 그 외에는 모두 공백으로 표현해야 합니다. 각 줄에서 마지막 *을 찍고 나서 오른쪽에 더 찍을 게 없더라도 공백으로 열을 모두 채워야 합니다. 만약 모든 다이아몬드의 크기가 같다면 다이아몬드는 1개만 보여야 합니다.

예제

views
이렇게 나오면 됩니다

풀이(코드 골프) 예

다음은 제가 이번에 제출한 코드 입니다. 어떤 동작을 하는 코드인지 이해 하실 수 있으신가요? 보기에는 이해하기 어렵지만 생각보다 짜는것은 어렵지 않습니다. 한번 차근 차근 따라가 보겠습니다.

r,t,c=range,'*',int(input());s=[int(input()) for _ in r(c)];a=max(s);m=[[' ']*a for j in r(a)]
for n in s:
 b=n//2;x=a//2-b
 for i in r(b+1):z=i+x;u=x-i;m[b+z][z]=t;m[b+u][z]=t;m[b+z][n-1+u]=t;m[b+u][n-1+u]=t
for l in m:print(''.join(l))

문제 풀기

코드 골프는 “짧은 코드 만들기”가 주목적이기 때문에 보통 어려운 문제가 제시 되지 않습니다. 하지만 문제를 최대한 간단한 방식으로 풀어야지만 이후에 코드를 줄였을 때에도 짧은 코드를 얻을 수 있습니다. 제가 작성한 동작하는 첫번째 코드입니다. 551자 입니다.

간단하게 공백판을 만들고 다이아몬드의 크기를 계산해서 각 사분면에 *를 표시한 후 이를 출력하는 코드입니다.

count = int(input())
sizes = [int(input()) for _ in range(count)]
# count = 3
# sizes = [11, 5, 3]
# 출력코드 작성
# print(count)
# print(sizes)

map = []
a=max(sizes)
for i in range(a):
    map.append([' ']*a)
for n in sizes:
    # 11 -> 0
    # 5 ->
    x = int(a / 2) - int(n / 2)
    m = int(n/2)
    for i in range(m+1):
        # print(m+i, i)
        # print(n, i, x)
        map[m+i+x][i+x] = '*'
        map[m-i+x][i+x] = '*'
        map[m+i+x][n-i-1+x] = '*'
        map[m-i+x][n-i-1+x] = '*'

for line in map:
    print(''.join(line))

줄여가기

주석, 공백, 변수명, 들여쓰기 줄이기

당연하게도 주석과 의미 없는 공백은 필요하지 않습니다.

모든 변수명은 한자로 변경합니다. 변수명을 줄이고 나면 가독성이 엄청나게 줄어듭니다. 변수명의 소중함을 알 수 있습니다.

우리는 당연하게 들여쓰기를 위해 4칸의 space를 사용하지만 사실 한칸만 사용해도 됩니다. 여기까지 작업하면 551->289로 줄어듭니다.

c=int(input())
s=[int(input()) for _ in range(c)]
m=[]
a=max(s)
for i in range(a):
 m.append([' '] * a)
for n in s:
 x=int(a/2)-int(n/2)
 b=int(n/2)
 for i in range(b+1):
  m[b+i+x][i+x]='*'
  m[b-i+x][i+x]='*'
  m[b+i+x][n-i-1+x]='*'
  m[b-i+x][n-i-1+x]='*'
for l in m:
 print(''.join(l))

변수로 대체하기

여러번 쓰이는 것은 변수로 선언해서 대체할 수 있습니다. 다음은 같은 방식입니다.

# 변경 전
for i in range(3):
    print(i)
for j in range(4):
    print(i)
# 변경 후 
r=range
for i in r(3):
    print(i)
for j in r(4):
    print(i)

여러번 등장하는 ‘*‘도 다음과 같이 줄일 수 있습니다. 2자가 줄어듭니다. (new line에 주의!)

# 변경 전
a='*'
b='*'
c='*'
d='*'
# 변경 후
t='*'
a=t
b=t
c=t
d=t

유사한 작업을 모두 하고 나면 다음과 같이 289->271 로 줄어듭니다. 코드를 읽고 싶은 마음이 사라집니다.

r=range
c=int(input())
s=[int(input()) for _ in r(c)]
a=max(s)
t='*'
for i in r(a):
 m.append([' '] * a)
for n in s:
 b=int(n/2)
 x=int(a/2)-b
 for i in r(b+1):
  z=i+x
  u=x-i
  m[b+z][z]=t
  m[b+u][z]=t
  m[b+z][n-1+u]=t
  m[b+u][n-1+u]=t
for l in m:
 print(''.join(l))

동일한 로직의 짧은 코드로 대체하기

이곳은 흑마술이 필요한 시기입니다. 글 가장 아래 참고에 있는 흑마술 레시피들을 보며 적용할 수 있는 것들을 찾아 봅시다. 여기에는 간단한것만 적용해 보았습니다. 271->258로 줄어듭니다.

줄바꿈 줄이기

아래와 같이 변수의 선언을 한꺼번에 할 수 있습니다.

# 변경 전
a=1
b=2
c=3
# 변경 후
a,b,c=1,2,3

아래와 같이 for문과 연속된 라인을 한줄로 줄여 쓸 수 있습니다.

# 변경 전
for i in range(3):
 j=i+1
 print(j)
# 변경 후
for i in range(3):j=i+1;print(j)

여기까지 작업하고 나면 258에서 237로 줄어듭니다.

r,t,c=range,'*',int(input());s=[int(input()) for _ in r(c)];a=max(s);m=[[' ']*a for j in r(a)]
for n in s:
 b=n//2;x=a//2-b
 for i in r(b+1):z=i+x;u=x-i;m[b+z][z]=t;m[b+u][z]=t;m[b+z][n-1+u]=t;m[b+u][n-1+u]=t
for l in m:print(''.join(l))

계속 들여다 보면 또 한자가 줄여지고, 또 한자가 줄여지고 그렇습니다. 하지만 오늘은 여기까지!

맺음말

코드골프는 회사에서는 절대 하면 안되는 여러가지 흑마술(?)을 하게 되기 때문에 하지 말라고 하는 일을 하는 쾌감이 있습니다. 후후후.

제가 파이콘 2018 도도 파이터 후기에서 이야기 한 것처럼 이런 코딩 경기에 참여하는 것은 컨퍼런스를 즐기는 또 다른 방법이자, 개발자들만 할 수 있는 놀이입니다. (비개발자 분들이 본다면 조금 변태 같겠죠.) 이 글을 통해 코드 골프라는 것이 대단하지 않은것이구나라는 것을 알게 되지 않으셨나요? 여러분도 다음기회에 도전해 보시고, 개발자임을 즐기세요.

마지막으로 재미있는 기회를 만들어 주신 레블업에도 감사의 말씀을 전합니다. 제가 받은 마우스 2개도 감사히 잘 쓰겠습니다. :)

views
어이쿠 감사합니다!

참고