숫자 안에 포함된 3, 6 또는 9의 개수만큼 박수를 치거나, 만약 3,6,9가 없을 경우 해당 숫자를 리턴하면 됩니다.
예를 들어, 307은 박수 한번, 9936은 박수 네번, 400은 "400"이라고 말하면 됩니다.
이걸 구현할 알고리즘을 연구하다가 다음과 같은 코드를 생각해봤습니다.
여기서 n이 주어진 숫자입니다. 숫자를 문자열로 바꾼 후, tr 명령을 사용해서 '3', '6', 그리고 '9'를 제외한 다른 문자를 지운 후, 남은 문자열의 길이를 구하는 겁니다. 이 길이가 바로 필요한 박수 횟수입니다.
그 다음, "clap "이라는 문자열을 방금 구해낸 박수 횟수만큼 복사합니다. 1이면 "clap ", 2이면 "clap clap ", 3이면 "clap clap clap ", 0이면 "" 이 되겠죠.
그 다음 max 함수를 사용하여 마지막으로 n을 문자열로 바꾼 스트링(num_s)과 비교하여 더 우선순위가 높은 문자열을 리턴하도록 합니다. 만약 박수 횟수가 0이면 왼쪽의 스트링은 ""이 되고, 빈 문자열은 무엇이든 들어있는 문자열보다 무조건 우선순위가 낮습니다. 따라서 우측의 숫자가 리턴됩니다. 만약 박수 횟수가 1 이상이면, 왼쪽의 스트링은 "clap clap ..."이 되는데, 문자열 끼리의 비교 과정에서 왼쪽 스트링의 첫번째 문자는 무조건 'c'가 되고, 오른쪽 스트링의 첫번째 문자는 무조건 숫자입니다. 'c'가 '1'~'9'보다 더 아스키코드 값이 크기 때문에 max 함수를 통하여 왼쪽의 스트링이 리턴됩니다.
루비 언어를 쓰고 있기 때문에 이런 복잡해보이는 알고리즘을 간단한 코드로 구현할 수 있었습니다. 만약 C나 C++을 쓰고 있었더라면 코드가 굉장히 길어지겠죠. ㅎㅎ
이외에도 사용자의 입력에 대한 예외처리, 채팅방에서 369게임을 하는 것처럼 보이기 위한 꾸미기, 컴퓨터와 같이 하기, 어설픈 OOP(-_-;) 등 여러 자질구레한 사항들을 추가하다보니 코드가 좀 길어졌습니다. 간단한 게임도 생각보다 만만치 않네요. -.-;
소스코드는 아래와 같이 생겼습니다. 좀 눈아프게 생기긴 했지만 그래도 저 나름대로 예쁜 코드, 버그 없는 코드를 만들어보려고 노력해봤습니다. -.-;
아래는 소스코드 파일이고, 혹시 루비 인터프리터가 설치되어있지 않은 분들을 위해서 exe버전(rubyscript2exe 사용)도 같이 첨부합니다. ㅎㅎ
대부분 컴퓨터에는 루비 인터프리터가 설치되어있지 않기 때문에 자신의 루비 코드를 배포하려면 인터프리터 까지 다운 받도록 해야 합니다.
하지만 몇 kb 되지 않는 루비 코드를 실행시키려고 20메가 가량 되는 인터프리터를 추가로 기꺼이 다운받으려는 분들이 그리 많지는 않을 겁니다. 특히, qtruby 처럼 외부 라이브러리가 필요한 경우는 문제가 더욱 심각해집니다. 용량의 문제보다도, 이들을 설치하기에는 너무 바쁘고 귀찮습니다.
다행히도 이러한 문제를 해결해주는 두 프로그램이 있습니다. tar2rubyscript와 rubyscript2exe 라는 프로그램인데요, tar2rubyscript는 여러개의 루비 코드를 하나로 묶어주고, rubyscript2exe는 루비 코드를 완전히 독립된 응용프로그램 (윈도우용 exe, 리눅스용 또는 맥용 바이너리) 으로 만들어줍니다.
두 프로그램 모두 하나의 루비 코드로 되어있고, 별 다른 설치 필요없이 바로 작동합니다. 물론 루비 인터프리터는 있어야 겠죠
사용법은 간단합니다. 커맨드라인 창에서 ruby rubyscript2exe.rb application.rb 를 입력하시면 해당 폴더에 application.exe 라는 파일이 생기고, 이를 곧바로 배포할 수 있습니다. 이렇게 생성된 프로그램은 시작하기 전에 동작에 필요한 파일들을 수집합니다. 이 때문에 프로그램 실행 시 약 5초 정도의 딜레이가 있습니다.
커맨드라인 창에서 ruby tar2rubyscript.rb program/ 를 입력하시면 해당 폴더에 program.rb 가 생성됩니다. 이 파일 하나가 program/ 폴더 안의 여러 루비 코드 들을 모두 포함하고 있습니다. 메인으로 실행 시켜야 할 코드의 이름을 init.rb 로 해주셔야 잘 작동합니다.
간혹 init.rb 를 폴더 안에 넣었음에도 program 폴더로부터 생성한 program.rb를 실행 할 시 init.rb가 없다면서 오류가 나는 경우가 있습니다. 왜인지는 잘 모르겠지만, 이럴 경우에는 폴더를 지우고 ruby program.rb --tar2rubyscript-justextract 를 실행하여 압축을 풀어서 program 폴더를 얻습니다. 이 폴더에 다시 ruby tar2rubyscript.rb program/ 명령을 실행시키면 이 때 만들어지는 program.rb는 잘 작동하게 됩니다.
rubyscript2exe와 tar2rubyscript는 윈도우, 리눅스, 맥에서 모두 작동합니다.
rubyscript2exe의 경우 맥에서 이용하려면 eee_darwin 이라는 파일을 자신이 직접 컴파일 해야 하는데, 제작자 홈페이지에서 지시하는 대로 따라서 하면 됩니다. 그런데 최신 버전 맥에서는 제대로 작동하지 않더군요.
이 프로그램 들은 각각 다음 주소에서 다운 받으실 수 있습니다. 루비 코드 뿐 아니라 gem 형태로도 배포하고 있습니다. 여러가지 옵션들에 대한 설명들도 있으니 들어가서 읽어보시면 도움이 됩니다.
최근 주목받고 있는 프로그래밍 방법인 테스트 주도 개발, 즉 TDD를 설명한 최초의 책이자, 가장 권위적인 책. 테스트 주도 개발을 퍼뜨린 장본인이며 객체 지향 프로그래밍의 선구자 중 한 사람인 켄트 벡이 직접 썼다. '테스트 주도 개발(Test-Driven Development)'은 테스트가 개발을 주도하는 방법이다. 테스트가 개발을 주도한다는 것은 테스트가 코딩의 방향을 이끌어 간다는 말이다. 테스트를 실패하는 코드가 없으면 코딩
qt-mac-opensource-src-4.3.3.tar.gz : Qt 라이브러리가 있어야 합니다. 그런데 최근에 나온 4.3.4나 4.4.0beta1 로는 QtRuby 1.4.9가 빌드되지 않더군요. Qt를 컴파일 할 때 -no-framework 플래그도 넣어보고 여러가지 조합을 시도해봤지만 실패했습니다. QtRuby 포럼에서 보니 버그 비슷한거라더군요. -_-; Qt 4.3.3 버전으로는 플래그를 따로 지정을 하지 않아도 빌드가 잘 됐습니다. 사실 이걸 알아내느라 고생을 한겁니다. Qt가 빌드 한번 하는데 시간이 오래걸리니까 한번 시행착오 할때마다 시간낭비가 심하죠..
cmake-2.4.8-Darwin-universal.dmg : cross-platform make 라고 하는 프로그램인데 윈도우, 맥, 리눅스에서 모두 동작하는 소프트웨어인 것 같습니다. QtRuby 1.4.9를 컴파일 하려면 이게 있어야 한다네요.
이 파일들을 ~/Desktop 에 받았다고 가정하고 계속 진행합니다.
먼저, cmake를 설치합니다. dmg파일을 열고 패키지를 열면 알아서 다 해줍니다.
다음으로 Qt 4.3.3을 설치해야 하는데, C++ 컴파일러가 있어야합니다. 뭐 다들 있으시겠지만, 전 설치 당시에 그게 없어서 -_-; 맥에서 제공해주는 Xcode 유틸리티를 설치했습니다. 터미널을 켜고 다음 명령들을 순서대로 실행합니다.
빌드 되는데 오랜 시간이 걸릴 것이므로 잠시 다른일을 하고 계시다가 Qt 설치가 끝나면 다음 명령을 해줍니다.
그리고 위 두줄을 ~/.profile 파일에 넣어줍니다.
echo $PATH 를 해서 나오는 주소 중에 /usr/local/Trolltech/Qt-4.3.3/bin 이 있는지 체크해봅시다. 없으면 좀 있다가 QtRuby를 설치 할 때 Qt가 있는 폴더를 찾을 수 없어서 설치 할 수가 없습니다.
보시는 바와 같이 input에서 각 라인마다 주어지는 연도가 윤년인지를 파악하여 알려주는 간단한 문제입니다.
위의 입력/출력 예시만 보고 몇가지 승부의 관건을 생각해낼 수 있습니다.
1. 윤년 계산하는 알고리즘 2. 입출력 방식
주어진 연도가 윤년인지를 판단하는 알고리즘은 구글을 대충 검색하시면 나옵니다.
1. 4로 나누어 떨어지면 윤년 2. 100으로 나누어 떨어지면 윤년이 아님 3. 400으로 나누어 떨어지면 윤년
그런데 이번 문제의 경우 윤년이 아닌지를 판단해서 해당 경우 스트링 "not"을 붙여줘야 하므로 조건을 뒤집어야 합니다.
1. 4로 나누어 떨어지지 않으면 윤년이 아님 2. 100으로 나누어 떨어지면 윤년이 아님 3. 400으로 나누어 떨어지지 않으면 윤년이 아님
조건을 좀더 간단하게 하면
1. 4로 나누어 떨어지지 않거나 2. 100으로 나누어 떨어지면서 400으로는 나누어 떨어지지 않으면 윤년이 아님
코드로 작성하면
이 됩니다. 여기서 y가 연도.
여기서 좀 더 줄여보면 으로 할 수 있습니다.
100과 400에서도 각각 1바이트씩 줄일 수 있네요. 여기서 ?d는 'd'의 ASCII 코드 (100이죠)를 뜻하고, $$는 프로세스 넘버를 뜻합니다. 프로세스 넘버는 코드를 실행시킬 때마다 달라지므로 $$가 400이 되려면 운이 필요하겠죠.
input을 받아들이는 방식은 여러가지가 있지만
이번 문제에서는 커맨드라인 옵션을 쓰는 것이 바람직해보입니다.
stdin으로 들어오는 문자열을 한 줄씩 읽어들이는 옵션에는 n과 p가 있는데
p의 경우 코드가 실행된 후 $_ (읽어온 해당 라인) 를 print로 출력하기 때문에 더 유용합니다.
왜냐 하면 sample output을 보았을 때 input으로 들어온 연도의 뒷부분에 스트링이 붙어서 출력되게 하면 되므로 와 같은 식의 코드가 가능해지기 때문입니다.
만약 n 옵션을 사용했더라면 같은 식의 코드가 되겠죠. 3 bytes 손해입니다.
아 그리고 $_ 의 마지막 character는 "\n" 일 것이기 떄문에 l 옵션을 같이 붙여줘야 할 것입니다.
이제까지 생각해본 것들을 바탕으로 코드가 완성되었습니다.
76 bytes 입니다.
1 byte 더 줄일 수 있네요. if와 y 사이에 공백이 하나 들어있던걸 절약한 겁니다.
이 방식으로는 75B 가 한계인것 같네요.
그런데 제가 이 코드를 생각해냈을 당시 1위가 64B 였습니다. 다른 방법을 찾아봅시다.
Ruby의 Standard Library에는 Date라는 클래스가 있습니다.
이 클래스에는 Date.leap? 이라는 함수가 있죠. 인자로 정수를 받아서 이게 윤년인지를 계산하여 true나 false를 리턴해줍니다. 이 함수를 사용하려면 require'date' 를 해야하지만 커맨드라인 옵션 #!ruby -rdate 로도 할 수 있습니다. 마침 다른 커맨드라인 옵션도 사용하고 있으니 후자의 방식으로 작성해야 더 유리하겠지요.
그리하여 다음과 같은 코드가 가능해집니다.
길이가 훨씬 줄었습니다. 하지만 여전히 68B 네요. 아직도 4바이트나 더 줄여야 합니다.
그런데 이 방식으로는 68B가 한계인것 같습니다. 다른 방법을 찾아봅시다.
처음의 알고리즘을 자세히 살펴보면 뭔가 4 라는 숫자가 많이 나오는걸 볼 수 있습니다.
연도가 100으로 나누어 떨어지는지, 떨어지지 않는지에 따라 400으로 나눠보느냐, 4로 나눠보느냐의 차이가 있는 겁니다.
따라서 100으로 나누어 떨어지는 경우 미리 100으로 나눠버리고 나면 이 경우도 마찬가지로 4로 나눈 나머지를 따라 윤년인지 아닌지를 판단할 수 있습니다.
미스테리가 슬슬 풀리기 시작합니다.
input으로 각 라인마다 주어지는 연도 $_는 처음부터 정수가 아니라 String 이라는 걸 잘 생각해보세요.
코드골프의 꽃이라 할 수 있는 정규 표현식(Regular Expression / regexp)을 쓰는겁니다.
100으로 나누어 떨어지는지를 검사하려면 $_의 끝부분에 '00' 이 있는지를 알아내면 됩니다.
정규표현식으로 쓰면 /00$/ 가 되겠네요. 여기서 $는 스트링의 끝부분이라는 뜻입니다.
그 다음 string.sub 함수를 사용해서 그 부분을 없애버립시다. 정규표현식이나 문자열을 받아서 그 부분을 스트링 내에서 찾아낸 다음 바꿔주는 함수입니다.
정도가 되겠네요.
그런데 Kernel.sub 라는 함수가 있습니다. 이 함수는 $_.sub 를 실행시킵니다.
따라서 로 줄일 수 있습니다.
그런데 여기서 정규표현식 /00$/ 를 /00/로 줄이면 안될까요?
뒤의 $를 제외시키면 "2002"나 "2008" 같이 연도의 가운데에 "00"가 있는 경우도 찾아내서 없애버립니다.
그렇게 되어 1008, 3008, 3002 등등 몇몇 연도의 윤년 여부를 제대로 계산하지 못해버리고 맙니다.
그런데 이 문제의 경우 연도의 가운데에 "00"가 들어가는 경우는 "2008"과 "2000" 밖에 없으며,
다행스럽게도 얘네들은 /00$/에서 $를 제외시킨 상태로 코드를 실행시켜도 윤년 여부가 제대로 계산됩니다.
따라서
로 더 줄일 수 있게 됩니다.
이를 바탕으로 코드를 작성하면
로 65B가 됩니다. 이제 한 바이트 남았군요.
하지만 이 방법으로는 65B가 한계인것 같습니다.
이번에는 $`를 사용해봅시다. $`는 가장 최근의 정규표현식에 매치된 부분의 앞부분이 저장되는 변수입니다.
다시 말해, $_="2000" 을 $_=~/00$/로 매치하고 나면 $`=="20"이 되는거죠.
그렇다면, 만약 매치가 안될 경우에는?
아쉽게도 $`==nil이 되어버리는군요. 정규표현식을 약간 수정해야 할 것 같습니다.
/00$|$/ 로 바꾸면 잘 동작할것입니다. '스트링의 끝부분의 "00"을 매치하거나, 만약 "00"가 없으면 그냥 스트링의 끝부분을 매치한다' 는 의미입니다.
이렇게 하면 $_="1234" 를 $_=~/00$|$/로 매치하고 나면 $`=="1234"가 됩니다.
이를 바탕으로 코드를 작성해보면
로 67B가 됩니다.
그런데 Regexp 클래스에는 ~ 라는 함수가 있는데, 이 함수는 $_ 와 해당 표현식을 매치합니다.
다시말해, $_=~/00$|$/ 라고 했던 부분을 ~/00$|$/ 으로 줄일 수 있다는 겁니다. 3 bytes 절약인 셈입니다.
다시 코드를 작성해보면
로, 드디어 64B가 되었습니다. 마침내 공동 1위네요.
그런데 랭킹에 보니 1위가 이제는 62B 로 더 줄어버렸습니다. 2바이트를 더 줄여야됩니다.
-_-;
하지만 전 이 64B 코드에서 마감시간 이전까지 더이상 줄일 수가 없었고, 결국 4위 (공동 3위)로 마무리하고 말았습니다.
마감시간이 지난 지금, 모든 코드가 공개되어있으므로 하나씩 살펴봅시다.
먼저, 저와 같이 64B 코드를 내놓은 kt3k 님의 코드를 봅시다.
이분은 chomp 함수를 써서 "00"을 없앴군요. 이런 방법도 있었다는걸 몰랐네요.
다음은 63B 코드로 2위를 차지한 ksk님의 코드입니다.
이분 코드에는 l 옵션이 없네요.
$_에서 /00$|$/ 와 매치되는 부분을 찾아서 그 부분의 끝에 뒤의 문자열을 붙이는 방식을 사용했습니다. 정규표현식은 문자열 내의 \n을 무시하기 때문에 l 옵션을 제외시킬 수 있었던 거였군요.
제 코드와 비교해보면 2 바이트의 []를 사용하는 대신 l 옵션과 ~ 함수와 \n의 3바이트를 절약하면서 결과적으로 1바이트를 단축 시킬 수 있게 되었습니다.
마지막으로, 62B 코드로 1위를 차지한 flagitious님의 코드입니다.
엥? 뭔가 완전히 다른 코드입니다.
이 분은 $_ 내의 "00"을 없애기 위해 split을 사용했네요.
커맨드라인 옵션들 중 a는 $F에 $_.split을 저장하는 옵션이고, F00은 a 옵션을 사용할 때에 $F에 $_.split 대신 $_.split('00')을 저장하라는 옵션입니다.
예를 들어, $_="1600" 의 경우 $F=["16"]이 되고,
$_="2008"의 경우 $F=["2","8"]이 됩니다.
그리고 이를 숫자로 바꾸기 위해서 "#$F"로 스트링으로 만든 후에 (["2","8"]이 "28"로 변환 됩니다.) eval 함수를 사용했습니다. eval은 argument로 들어온 스트링을 코드로 인식하여 실행시키는 함수입니다.
대단합니다. 스트링의 한 부분을 "없앤다" 의 개념을 사용하는데에 split 함수를 생각해내다니요.
2007/12/20 - [Programming/Ruby] - [Golf] 22 bytes -2000 to -10000
Works only if the process number is 801 and pid is 1989. ----------------------------------------------------------------- ## Unfortunately, this code doesn't work, since id is a method and assigning a value to a method is impossible. I thought this code is valid because when I was testing my code with
, this one worked properly. Seems that id=1989 creates a variable named id and it overrides the same-named method afterwards, which makes id+=10 valid. On the other hand, if I run id+=10 from the beginning, the interpreter thinks there must be a variable named id but cannot find one except the Kernel.id method, and of course raises an error. ##