코드를 빠르게 짜는 방법에 관한 고찰 (+ 구데기컵 카페 후기)

코드를 빠르게 짜기 위해서는 어떻게 해야 할까요. 정말 많은 PS러들이 하고 있는 고민일 것이라고 생각합니다. 마침 구데기컵 카페에서 시프트님보다 브론즈5 빠르게 풀기 대결이 있었고, 제가 승리 (!!) 했기에, 이렇게 카페 후기를 빌미로 글을 써보려 합니다.

코드를 빠르게 짜는 것은, 물론, PS에서는 꽤나 보조적인 고민이 될 것입니다. 그러나, 결국 언젠가는 코드를 빠르게 짜는 것이 큰 도움이 됩니다. 예를 들어, 저는 ABC 기준으로 A~D번 기준으로 코드를 짜는 시간보다 생각을 하는 시간이 더 많이 걸린 적이 많지 않습니다. 이러한 경우에는, 코드를 빠르게 짜는 것도 매우 중요합니다. 등수의 기준에 점수뿐만이 아니라 페널티도 있기 때문입니다. Codeforces는 심지어 문제를 푸는데 시간이 더 걸릴수록 점수가 감점되는 시스템이다 보니 이러한 중요성이 더 부각된다고 생각합니다. 때때로 Speedforces라는 이야기도 돌고 있고요.

이러한 상황에서 우리는 어떻게 코드를 짜야, 코드를 "빠르게" 짤 수 있을까요? 이에 저는 여러 가지 가설들을 세우고 그 가설들이 실제로 코드를 짜는 속도에 얼마나 기여하는지 생각해 보는 방식으로 이야기를 하려 합니다.

  • 타자 속도가 빠를수록 코드를 빠르게 짤 수 있는가?

솔직히, 이 부분에 대해 말하지 않고 넘어가기는 어려울 것 같습니다. 결국 "코드를 짜는 속도"에는 타자 속도가 포함되어 있을 것입니다. (물론, ChatGPT한테 시켜서 10초 만에 코드를 뽑아내는 것이 아니라는 전제 하에) 우리는 결국 키보드로 코드를 짜야만 하니까요. 다만, 이것도 어느 정도까지만 맞는 이야기라고 생각하였습니다. 생각하는 속도보다 타자 속도가 빠르다고 타자가 앞서가서 코드를 먼저 만들어주는 것이 아니기 때문입니다. 보통 제가 ABC에서 시작 시점부터 A번의 코드를 짜서 제출하는 시점까지 대략 30초 내지는 1분이 걸립니다. 타자 속도를 개선한다고 이게 더 빨라지지는 않을 것이라고 생각했습니다. (참고를 위해 말하자면, 제 타자 속도는 영타 기준 60WPM이 나옵니다. 영타 70~80WPM이 된다고 하더라도 더 큰 개선을 느낄 수 있을 거라고 생각이 들지는 않는다는 이야기입니다.)

  • 코드를 깔끔하게 짤수록 코드를 빠르게 짤 수 있는가?

이 부분이 코드를 짜는 속도에 100% 기여하지는 않습니다. 그럼에도, 이 부분을 신경쓸 수밖에 없습니다. 왜냐하면, "코드를 깔끔하게 짠다"에는 "코드를 짧게 짠다"도 어느 정도 포함되어 있기 때문입니다. 코드를 빠르게 짜는 것에는, 분명히 코드의 길이가 굉장히 많은 부분 기여하고 있습니다. 물론, 코드를 깔끔하게 짜는 것을 일부러 의식하다가는 코드를 짜는 시간이 배가 되는 폐해도 있기도 합니다. 그럼에도, 코드를 깔끔하고 이쁘게 짜는 것을 습관화하고, 여러 가지 Syntactic Sugar (코드 작성을 간편하게 하기 위해 문법 차원에서 제공하는 기능. e.g. 파이썬의 unpacking, C++의 structured binding declaration 등)를 알아두는 것은 코드를 빠르게 짜는데 많은 도움을 줍니다. 왜냐고요? 이 두 줄의 코드를 생각해 봅시다. 다음 두 줄의 코드는 실행의 결과가 같습니다.

a=list(map(int,input().split()))
a=[*map(int,input().split())]

코드의 실행 결과는 같은데, 3B가 줄어들었고, 가독성이 개선되었습니다! 이러한 코드 길이의 절약은 C++에서도 더 재밌는 예시로 다가옵니다.

// ...
auto p=Q.front();
Q.pop();
for(int nxt:adj[p.first])Q.push(pair<int,int>{nxt,p.second+1});

auto[v,w]=Q.front();
Q.pop();
for(int nxt:adj[v])Q.push({nxt,w+1});
// ...

이전 예시보다는 더 확 다가오지 않나요? 저는 그렇다고 생각했습니다. 이러한 수준으로 코드의 양을 절약할 수 있다면, 이는 긴 코드에서는 특히 더 누적되어 코드를 짜는 속도에도 기여할 것이라고 생각합니다.

  • 언어를 바꾸면 코드를 빠르게 짤 수 있는가?

꽤 일리 있는 가설이라고 생각했습니다. 현 시점에 저는 대부분의 문제를 Ruby로 풀(려고 시도하)고 있고, 저에게는 Ruby의 사용이 코드를 짜는 속도에 크게 기여했기에, 딱히 반박하기도 어려운 가설입니다. 최근 있었던 대회: 제1회 와쿠(AGCU)컵에서는 전체 문제에서 2문제를 제외한 총 13문제를 Ruby로 해결하였고, 그 결과 59분에 All solve를 하고, 남는 시간에 질문 탭에서 데추주 요청도 했습니다. (진짜인지는 대회 운영자 중 아무나 물어보셔도 됩니다. 제가 이렇게 말해도 딱히 설득력이 있을지는 모르겠습니다.)

실제로, Ruby를 사용하기 시작한 이래로, 정말로 코드 길이가 많이 짧아졌음을 체감하였고, 코드 작성의 방식도 함수형의 스타일을 많이 빌려온다는 점에서, 생각하는 시간과 동시에 코드를 짤 수 있어서 좋았습니다. Ruby 튜토리얼은 아니지만, 예를 들어서, 1000번: A+B에 대한 Python과 Ruby의 코드를 비교해 봅시다.

a,b=map(int,input().split())
print(a+b)

위 코드는 Python으로 작성한 코드입니다. 이미 충분히 짧아 보입니다. 그러나 Ruby는 어떨까요?

a,b=gets.split.map &:to_i
p a+b

코드의 양이 상당히 줄어들었습니다! 물론, 이 정도의 절약을 위해서는 어느 정도 언어에 대해서 깊이 있게 이해할 필요가 있습니다. (정수의 출력에서는 pputs와 동일하다는 것을 이해한다거나...)

하지만, 문제는 이겁니다. 우리 모두가 심심하면 언어를 하나 더 배울 수 있을 정도로 시간이 남아도는 것은 아닙니다. 그렇기 때문에, 새로운 언어를 배우는 것은 PS에서는 특히 신중한 결정이 되어야 합니다. 특히 이미 사용 중인 언어가 잘 알려지고 자료가 많은 언어라면 더욱 그렇습니다. 코드를 빠르게 짜기 위해서 언어를 바꾸는 선택은 스스로 생각해 보았을 때 언어를 배우는 시간의 크기를 그로 인해 얻는 메리트와 비교해서 신중하게 결정하기 바랍니다. 무턱대고 "Java 코드 너무 길어요 엉엉" 하고 언어를 새롭게 배우려다가 그 언어가 자신에게 잘 맞지 않는다는 사실을 너무 늦게 발견해버린다면 그 때는 너무 많은 시간을 버린 것이 됩니다.

  • 코드를 짧게 짜면 코드를 빠르게 짤 수 있는가?

위에서 언급한 거 아니냐고요? 물론, 언급은 했습니다. 코드를 "깔끔하게" 짜는 이야기를 하면서 언급했죠. 그럼에도 다시 한번 언급하는 이유는, 짧은 게 좋다고 해서 짧은 것에 강박을 가지면 안 된다는 이야기를 하기 위해서입니다. 생각해 봅시다. 대부분 문제에서 숏코딩 탭의 꼭대기를 보면, 맨 위를 Golfscript가 차지하고 있습니다. 몇몇 브론즈 5 티어의 문제는 2B (입력, 연산 각 1B)에 해결되기도 합니다. 그러나 문제의 난이도가 올라갈수록, Golfscript와 같은 언어에서 코드의 구성을 생각하는 것은 급격히 어려워집니다. 몇몇 문제들에서 Golfscript로 숏코딩 1위를 차지한 바 있으나, 아직도 Golfscript를 쓰는 것은 어렵기만 합니다. 생각해야 할 변수의 개수가 많아질수록, 점점 코드를 작성하는 프로세스가 코딩보다는 저글링에 가깝다는 생각도 듭니다. 이러한 언어로는, 짧더라도 빠르지 않을 것이라고 생각했습니다. 이 현상은 비단 Golfscript만이 아니라, 아희나 다른 몇몇 언어들에도 적용되는 이야기일 것입니다.

예시로 든 난해한 프로그래밍 언어가 아니라도 이러한 일은 자주 있습니다. 코드를 짧게 짜는 것에 강박적으로 집중하면, 생각하는 프로세스를 다른 곳에 낭비하게 됩니다. 코드가 write once, read never이 되기도 합니다. 여러모로, 짧은 코드에만 집중하는 것은 득보다 실이 많습니다.

  • 템플릿을 사용하면 코드를 빠르게 짤 수 있는가?

글쎄요. 이 부분은 개인차가 있을 수 있다고 생각합니다. 개인적으로 저는 (제가 직접 짜기 어려운 부분을 제외하면) 템플릿을 사용해본 적이 거의 없습니다. FOR(i,0,n)같은 템플릿을 쓰는 것을 말리지는 않겠으나, 직접 써보고 메리트를 체감할 수 없으면 과감히 버리는 편을 추천합니다. 물론, 자신이 못 짜는 고난이도의 알고리즘 (보통 ICPC, UCPC 등의 대회에서 팀노트에 프린트해가는 알고리즘들)은 템플릿을 사용하는 것이 괜찮은 선택입니다. 그런 알고리즘들은 대부분 문제마다 새로 짜는 게 더 손해라고 생각됩니다.

  • 노래를 들으면서 코딩을 하면 코드를 빠르게 짤 수 있는가?

뭔 어이없는 이야기인가 싶죠? 네, 저도 더 할 이야기가 생각나지 않아서, 생각해 보다가 나온 가설입니다. 충분히 일리있...는지는 모르겠는데, 결국 저는 대회에 참가할 때마다 템포가 높은 노래를 듣고는 있었던 것 같습니다. 관련된 연구 결과가 있는지는 전혀 모르겠습니다. 관련 연구 결과나 논문이 있다면 알려주시면 감사하겠습니다.


  • 구데기컵 카페!

사실 이번 구데기컵 카페는 3 2가지만 노리고 갔습니다.

  1. 대결 이벤트 참여
  2. 구데기 티어 아크릴
  3. 함께 일한 대회 검수자, 운영자 분들과의 친목

12시에 도착하니 이미 줄이 정말 길었습니다. 사람이 정말 많았나 봅니다. hijkl2e님이 남기신 "수요조사 안 하냐" (원문은 일부 커뮤니티에 한정된 속어가 있기에 순화하였습니다.) 라는 떡메모지가 확실히 체감되었습니다. 저도 떡메모지를 몇 개 남겼습니다. 제2회 흐즈로컵이라거나, Ruby 이야기라거나, "떡메모지와 쿼리 1"이라거나, 그런 이야기들이 내용입니다. 제2회 흐즈로컵이 열릴 수 있을까요? 사실 잘 모르겠습니다.

줄에서 기다리다가 kbmin24님과 dhyang24님을 만났습니다. 실제로 만나기 전에 "저는 그냥 그럴 듯하게 생겼다" 하고 귀띔을 드렸습니다. 제가 누군지 못 찾으시다가, 저를 찾고 나니 "그럴 듯하게 생겼다"의 의미를 이해하셨다고 하셨습니다. GEC Cup을 운영하면서 함께 1개월가량 고생해주신 분들이시기에, 그 전부터 직접 뵙고 싶은 분들이었습니다.

hibye1217님도 만났습니다. 사실 줄 중간에서부터 핸들을 보고 언제 인사드릴까 고민하다가, 음료 (구데기 에이드였습니다.) 를 받고 조용히 다가가서 "안녕하세요, 흐즈로컵 출제자입니다" 하고 인사드렸습니다. 3시 즈음에 카페에서 대학교 기숙사로 돌아가기 전까지 hibye1217님, pani님 등 BOJ에서 유명하신 여러 분들과 이야기를 나누었습니다. 모두 만날 수 있어 정말 영광이었습니다.

1시 쯤, 특전과 아크릴이 전량 소진되었다는 이야기를 들었습니다. 제가 특전을 받고 얼마 되지 않은 시점이었습니다. dhyang24님이 특전을 받으신 직후에 1일차 특전이 전량 소진되었다고 하더라고요. 정상적으로 수요조사 프로세스가 진행되었다는 전제 하에는 이해가 어려운 일이기는 합니다만, 수요조사가 정상적으로 진행되지는 않은 모양이니, 뭐 그러려니 하기로 했습니다. 아크릴 가챠를 사러 간 게 아닐뿐더러, 아크릴 가챠를 열 개씩 살 정도로 돈이 많은 것도 아니고요. 구데기 티어 아크릴을 얻었고, 대회 검수자 분들과의 이야기를 나눌 수 있었으니, 목적 3 2가지 중 2 1가지는 완수한 것입니다.

그리고 대결 이벤트를 참여했습니다. 4가지 이벤트 중 2가지, 시프트님과의 브론즈5 대결과 하바나님과의 가위바위보 대결에 참여했습니다. 가위바위보는 단판으로 진행했고, 이겼습니다. 그리고, 대망의 브론즈5 대결입니다.

시프트님이 룰을 설명해주셨습니다. IDE를 완전히 비우고 시작해달라고 하셨는데, 처음부터 저는 IDE를 비울 필요가 없었습니다. 카페에 가기 전부터 제출창 코딩을 할 생각이었습니다. 밴한 언어는 정말 당연하게도 Golfscript였습니다. 그리고 대결이 시작되었습니다.

랜덤으로 나온 문제는 18409번: 母音を数える (Counting Vowels)였습니다. 위아래로 쓱 훑으니 무엇을 시키는 문제인지 쉽게 이해되었습니다. 여기부터 "아마 시프트님은 이런 코드를 짜고 계셨겠지"를 추측하면서 이야기하겠습니다. 실제로 짜던 코드가 어떤 방식이었는지는 알 방도가 없습니다. 시프트님이 제출도 하기 전에 제가 AC Verdict를 띄웠기 때문입니다. 혹시나 시프트님이 어떤 코드를 짜고 계셨는지 아시는 분아마도 시프트님 본인이 유일하시겠지만이 계신다면, 알려주시면 좋겠습니다.

아마, 이 문제에서 시프트님은 이런 코드를 짜고 계셨을 것입니다. 제가 아는 대로라면, 시프트님이 Ruby를 자주 사용하지는 않으십니다. 오히려 사용하시는 것을 본 적도 없다고 하는 편이 맞을 것입니다. 그렇기에, 아마 시프트님이 사용하셨을 언어는 Python 3였을 것입니다. 아래는 아마도 시프트님이 짜고 계셨을 코드의 후보입니다.

  1. 0부터 시작해 count 내장함수의 리턴값을 누적
input()
s=input()
a=0
for c in"aeiou":
 a+=s.count(c)
print(a)
  1. count 내장함수를 5번 복붙
input()
s=input()
print(s.count('a')+s.count('e')+s.count('i')+s.count('o')+s.count('u'))
  1. bool이 int로도 해석되는 점을 활용
input()
a=0
for i in input():
 a+=(i in"aeiou")
print(a)
  1. bool이 int로도 해석되는 점을 활용, 심화
input()
print(sum(i in"aeiou"for i in input()))

그러나, 위의 어떤 코드도 Ruby의 코드 길이를 따라올 수 없었습니다. 다음은 제가 당시에 Ruby로 제출창에 코딩하여 제출해 AC를 받은 코드입니다.

gets
puts gets.count "aeiou"

사기적으로 짧습니다! 그러나 Ruby의 관점에서 보면, 될 수 있는 한계까지 줄인 것도 아닙니다. 그냥 Ruby에서 문자열의 일부 메소드들이 가지는 특성을 한 가지 활용한 것뿐입니다. 바로 tr, count, delete 등이 길이 1 이상의 문자열을 받을 때, 이 메소드들은 그 문자열의 문자 각각에 대해 모두 적용한 결과를 반환한다는 것입니다. 줄이려면 더 줄일 수 있었습니다. 다음 코드는 "더 줄인 코드"의 예시입니다.

p $>.read.count "aeiou"

코드를 여기까지 줄이는 것은, 물론 "짧은 코드"가 목적이라면 의미가 있을지도 모르겠습니다. 그러나, 이 대결은 코드를 "짧게 작성하기"의 대결이 아닙니다. 코드를 "빨리 작성하기"의 대결입니다! 이것까지 생각하다가는 생각하는 시간에 너무 많은 시간이 매몰됩니다. 짧게 칠 생각을 할 시간에 1글자라도 더 치는 것이 이득입니다.

아무튼, 시프트님과의 브론즈 5 빨리 풀기 대결에서 승리했고, 목적을 모두 완수했습니다. 그리고, 3시간 정도 많은 분들과 이야기를 나누고, 문제 번호에 41이 들어간 문제도 풀고 ("머리 톡톡"을 풀었습니다.), 집에 갔습니다.

당시에 찍은 사진이 많지는 않았고, 떡메모지 롤링페이퍼 (?) 사진이나 올리고 마치겠습니다.

2nd hjroh cup, 2023 july maybe?

P.S. 그 날 주문했던 포춘쿠키는 기숙사에 도착해서야 까볼 수 있었습니다. 결과로 "시간 초과"가 나왔습니다. 그 날 ABC에서 TLE를 한 5번쯤 겪었습니다. 어우 소름.


댓글 (4개) 댓글 쓰기


odongg 11달 전

재밌는 정성글 감사합니다.


dla9944 9달 전

ps와 음악의 상관관계...확실히 흥미로운 물음이긴 하네요. https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE06557293 이런 연구도 옛날에 있었다고 합니다만ㅋㅋㅋㅋ문제해결과는 연관이 없을것 같기도 하네요. 다른 예를 들자면, 저같은 경우 운전을 할때 템포가 빠른 음악을 듣는 것 보다는 느린 음악을 듣는게 더 집중하는데 도움을 주는 것 같더군요. 미디엄 템포가 뇌를 비교적 덜 지치게 하지 않나 생각이 듭니다. 또한 음악을 바꾸는데 소요되는 시간까지 생각하면, 느린 템포가 더 낮지 않나...해요. 물론 재미삼아 입니다ㅎㅎ


vegahouse102 6달 전

감사합니다


ibm2006 4달 전

항상 코드를 짤 때 다소 느리고 오류가 많이 나타나서 고민하던게 생각나네요... 코드를 빠르게 짤 수 있는 흥미로운 팁들에 대해 잘 소개해주고 있는 것 같습니다. 빠른 노래를 들어보면서 코딩을 해보니까 더 빠른 것 같기도 하네요. 좋은 글 감사합니다!