보시는 바와 같이 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. ##
If I change 200 into ?\xc8, it's actually 22 bytes. Can't get this even shorter though.
If using $$ is allowed (like in http://golf.shinh.org), I can write an even shorter luck-based 21 bytes solution :
Works only if the process number is 1000 when this code is executed.
Even crazier 18 bytes solution is possible if extreme luck is allowed : see http://leonidblog.tistory.com/44 ### This one just doesn't work LOL. sorry ###
The
problem is to write a code that prints exactly the same numbers the code below does.