메아리 저널

파이썬 코드골프 팁들

또 다시 코드골프 얘기. 파이썬은 다른 언어에 비해 코드골프하기 불리한 점이 여럿 있다. 문장과 수식의 구분이 철저하고 들여쓰기 강제 문법도 상당히 거치적거린다. 하지만 그럼에도 불구하고 골프가 불가능한 언어는 없는 법. 한동안 코드골프에 빠져 살면서 알아 낸 몇 가지 팁을 소개하고자 한다.

기본적인 것

코드골프에서 공백을 줄이는 것은 당연한 일이다. 하지만 어떤 공백을 줄일 수 있을까? 쉽게 설명하면 다음 규칙을 따르면 된다.

  • 여러 개의 공백은 무조건 하나로 일단 줄이고 본다. 문자열 리터럴 안에 있는 건 예외.
  • 공백 앞에 알파벳 또는 _가 오고, 공백 뒤에 알파벳, 숫자 또는 _가 오는 경우를 빼고 모든 공백은 지울 수 있다.
  • 들여쓰기 문법은 2단까지는 1바이트로, 4단까지는 2바이트로 줄일 수 있다. 공백과 탭을 번갈아 쓰면 같은 1바이트라도 다른 들여쓰기 수준을 만들 수 있다.

첫 두 규칙의 예를 들자면, [int(i) * 2 for i in '123'][int(i)*2for i in'123']으로 줄일 수 있다. 물론 [2*int(i)for i in'123']이라고 순서를 바꾼다고 뭐라 하는 사람은 없다.

세번째 규칙은 다소 이해가 가지 않을 것인데, 요지는 다음과 같은 코드가 가능하다는 소리다. 이는 파이썬 인터프리터가 모든 탭을 처음에 공백 여덟개로 변환하고 파싱을 시작하기 때문에 가능한 것이다. (문법 레퍼런스 참고)

if cond1:
 if cond2: # 앞에 공백 하나
    if cond3: # 앞에 탭 하나
     if cond4: # 앞에 탭 하나 공백 하나
        func() # 앞에 탭 둘

대부분의 코드 골프에서는 프로그램의 바이트 수로 순위를 결정한다. 따라서 공백 뿐만 아니라 눈에 보이지 않는 바이트들을 잡는 것도 필요하다. 보통 직접 세어 본 바이트 수랑 실제 바이트 수가 다르다면 둘 중 하나다.

  • 파일을 도스 포맷으로 저장해서 개행문자가 0D 0A로 저장되고 있다. (유닉스 포맷으로 저장할 것.)
  • 파일의 맨 끝에 개행문자가 달라 붙고 있다.

vim의 경우 :set ff=unix noeol binary를 설정해서 저장하면 이 문제들을 피해 갈 수 있다. 나 같은 경우 위에서 설명한 몇 가지 기법을 위한 스크립트를 하나 만들어 쓰고 있다.

입력

꽤 많은 골프 문제들이 사용자의 입력을 가정한다. 어떤 경우에는 실제 처리 과정보다 입력을 해석하는 과정에 많은 바이트가 소모되는 경우도 있는데, 이럴 때 많이 쓰이는 패턴들을 알아 두면 도움이 된다.

42

이렇게 숫자 하나만 달랑 나올 경우 input()으로 쉽게 입력받을 수 있다.

1 2 3 4 5 6

숫자가 여럿 있고, 공백으로 구분될 경우 위의 방법으로는 거의 불가능하다. 대부분의 경우 map(int,raw_input().split())이 최선이다.

code
golf
rocks

위와 같이 여러 줄의 문자열을 입력받는 방법은 상황에 따라 여러 가지가 있다.

  • 여러 줄을 하나의 변수에 담고 싶다면 ''.join(sys.stdin)이 쓸만하다. (물론 import sys가 어딘가에 있어야 한다.) 다만, 전체 문자열의 길이가 제한되어 있다면 os.read(0,999) 같은 것도 가능하다.
  • 각 줄을 리스트에 나눠서 담고 싶다면 list(sys.stdin)을 쓸 수 있다. 다만 각 줄의 끝에 개행문자가 들어 간다는 사실을 잊지 말 것.
  • 좀 더 일반적으로, [... for l in sys.stdin] 같은 코드를 써서 각 줄 별로 처리가 가능하다. map(..., sys.stdin) 같은 변형도 괜찮다.

사실 수식이 아니어도 된다면 다음과 같이 할 수도 있다.

try:
 while 1: # 여기서 raw_input()을 쓴다.
except: # 뒷처리.

하지만 일반적으로 위의 코드는 sys.stdin을 쓰는 것보다 효율적이지 못 하다. 다만 코드골프 채점에서 표준 에러 출력을 무시하고, 각 줄 별로 처리하고 끝내도 되는 프로그램이라면 try~except를 완전히 없애 버릴 수도 있다. 그냥 이런 게 가능하다는 정도로만 알아 두자.

당연한 최적화들

파이썬의 많은 것들은 객체 내지는 적어도 변수로 저장 가능한 값이다. 이런 것들을 잘 써 먹자. 예를 들어서,

print input()*input()+input()

이런 코드는 input 함수를 짧은 이름으로 저장해 둬서 쓸 수 있다. raw_input이나 range 같은 함수도 워낙 이름이 길어서 흔히 이렇게들 한다.

i=input;print i()*i()+i()

(참고로 뒤에서 이 코드가 좀 더 줄어들 수 있음을 보게 될 것이다. 심심하시면 뒷부분 내용을 보지 않고 맞춰 보시라.)

함수 말고 객체에 묶여 있는 메소드(bounded method)도 이런 식으로 저장할 수 있다. 특히 문자열의 경우 메소드가 워낙 많아서 묶여 있는 메소드를 저장해 두는 경우가 많다. j=''.join 같은 식으로 쓰면 되겠다.

그 다음에는…

다음 글에는 연산자와 내장 함수들의 다양한 사용에 대해서 써 보겠다. 글이 너무 길어질 것 같아서 여기까지.

이 글은 본래 http://mearie.org/journal/2007/12/python-codegolf-tips에 썼던 것을 옮겨 온 것입니다.


(rev 1d46270eb038)