[첫화면으로]CodeGolf/AsciiArt

마지막으로 [b]

[Code Golf | Bob Ross' The Joy of ASCII Art]

Perl의 경우 51글자가 최선인 듯. Diary/CodeGolf-ASCIIart홀인원

홀인원까지의 과정

[재미있는 코드 골프를 해봅시다~!! | KLDP] - 관련 글타래

위 글타래에서 코드가 줄어드는 과정을 구경하면 나름대로 재미있다.

2차원 배열

처음에 keedi 김도형님이 Perl 코드 운을 뗌[1]. 입력을 가지고 1글자짜리 스트링의 2차원 배열을 만든 후, 그 배열을 출력.
# 87바이트짜리
@a=<>;map{split/ /;$s[$_[1]][$_[0]]=chr$_[2]}@a;map{map{print$_?$_:' '}@$_;print"\n"}@s

풀어서 쓰면
@a=<>;                          # 표준입력을 배열에 저장하고
map {                           # 입력의 각 행에 대하여
   split/ /;                    # 공백으로 구분하여 3개의 숫자로 된 배열을 만들어 @_에 담고
   $s[$_[1]][$_[0]]=chr$_[2]    # 스트링의 2차원 배열에 해당 위치에 해당 문자를 담는다
}@a;
map {                           # 이제 출력
   map{print$_?$_:' '}@$_;      # 문자가 없는 좌표는 스페이스 출력
   print"\n"
}@s

굳이 @a라는 임시 배열을 쓸 필요가 없고, split은 인자를 주지 않으면 알아서 스페이스를 구분자로 사용하므로[2]
#78바이트
map{split;$s[$_[1]][$_[0]]=chr$_[2]}<>;map{map{print$_?$_:' '}@$_;print"\n"}@s

표준 입력을 split으로 나눌 필요 없이 Perl/정규표현식을 사용하여 x좌표, y좌표, 아스키코드값을 분리해낸다. jg님[3]
#72바이트
map{/ (.*) /;$s[$1][$`]=chr$'}<>;map{map{print$_?$_:' '}@$_;print"\n"}@s

두 번 등장한 print를 한 번으로 줄이고, 3항 연산자 대신에 ||를 사용하여[4]
#65바이트
map{/ (.*) /;$s[$1][$`]=$'}<>;print map{map{chr($_||32)}@$_,10}@s

생각해보니 굳이 정규식 저 자리에 괄호를 쓸 필요가 없다[5]
#63바이트
map{/ .* /;$s[$&][$`]=$'}<>;print map{map{chr($_||32)}@$_,10}@s

"어느 좌표에 저장된 문자가 있으면 그 문자를, 없으면 스페이스를"이라는 문제를 해결하기 위해서 계속 "||"연산자를 사용했는데, printf를 써서 "%1s"로 인자를 주어서 출력하게 하는 버전들[6] [7] [8]. 그러나, 결과적으로 이건 홀인원을 향한 길에 도움이 되지 못했음. 오히려 벙커에 빠진 셈이랄까.
#62바이트
map{/ .* /;$s[$&][$`]=chr$'}<>;map{printf"%1s"x@$_."\n",@$_}@s
#59바이트
$s[$&][$`]=chr$'*/ .* /for<>;printf"%1s"x@$_."\n",@$_ for@s

스트링의 1차원 배열

한 글자 스트링들의 2차원 배열 대신에 각 라인을 나타내는 스트링들의 1차원 배열로 만든 jg님의 버전 등장[9]. 이하의 진행에서는 계속 jg님이 힌트를 듣고서야 주인장도 진행할 수 있었다. ㅠ,.ㅡ

입력되는 좌표의 순서가 중구난방이기 때문에, 어떤 행을 구성한 후 다시 그 행 어느 열의 좌표가 들어오면 기존 행에 합쳐주어야 하는데, 스트링에 대하여 bitwise OR 연산을 사용.
#62바이트
/ .* /,$s[$&]|="\0"x$`.chr$' for<>;$\="\n";y/\0/ /,print for@s

이후 글타래도 조용해지고 주인장도 바쁘고 해서 두 주일 동안 진전이 없다가... 다시 생각나서 KLDP에 갔더니,

Perl의 스위치 - 이건 사기야 사기

jg님의 글[10]

이거 전에 Beginning Perl 읽으면서 Perl/디버깅에도 적어뒀던 것들인데... 명령행 인자를 사용해서 켤 수 있는 스위치들을 일일이 외우기도 싫고 해서 그냥 적어두기만 하고 잊고 있었는데 여기에 비밀이 있었다.

아래와 같이 실행하면
$ perl -n -e '코드'
Perl은 아래와 같이 가정하여 수행한다.
while (<>) {
   코드
}

명령행 인자가 아니라 shebang 라인에 적어 줄수도 있다.
#!perl -n
코드

근데 우리가 작성하는 프로그램은, 일단 표준 입력을 전부 다 읽어서 스트링을 구성하는 단계와, 완성된 스트링을 출력하는 단계로 나누어지기 때문에, 저렇게 코드 전체가 루프 안으로 들어가버리면 곤란하다...라고 생각을 했는데...

다음과 같은 사기스러운 코드가 가능하더라.
#!perl -n
코드1
}
{
코드2
이러면 이게 아래처럼 해석된다! -_-;;;
while (<>) {  # Perl이 루프의 시작을 붙여주고,
코드1
}  # 여기서 첫번째 단계의 루프가 끝나고 -_-;
{  # 새로운 블럭이 시작되면서
코드2
}  # Perl이 닫는 중괄호를 붙여준다 -_-;;;;

-n 대신에 -p를 쓰면 Perl이 알아서 print까지 한다
#!perl -p
코드

# 이것은 아래와 같이 해석
  LINE:
    while (<>) {
        코드             # your program goes here
    } continue {
        print or die "-p destination: $!\n";
    }

그리고 -l 옵션을 주면 print를 할 때마다 뒤에 $\ (the output record separator)의 값을 붙이는데 이게 기본값이 "\n"이다.

따라서 아래와 같은 게 가능해지고, 여기까지가 주인장이 알고 있는 것만으로 할 수 있는 최선의 코드였다.
#55바이트
#!perl -lp
/ .* /,$s[$&]|="\0"x$`.chr$'}for(@s){y/\0/ /

다시 jg님의 힌트[11]를 얻어 vec()이라는 함수의 존재를 알게 되어서, 스트링을 만들 때 bitwise OR 대신에 vec()을 써서 짠 게 아래와 같다.
#52바이트
#!perl -lp
/ ../;vec($s[$&],$`,8)=$'}for(@s){y/\0/ /

마지막 1바이트

이제 1바이트가 남았는데... jg님의 말을 빌자면, 위 코드에서, 두 글자로 표현된 것을 한 글자로 줄일 수 있다 이것도 사기라면 사기인데, 어쨌거나 해보니까 되더라.
#!perl -lp
비밀 :-P
힌트는, "메모장으로는 안 되고 vi나 emacs로는 됨"

이것과 별도로, vi에서 저장하니 시키지도 않았는데 꼭 끝에 "\n"을 붙여버려서 1바이트 늘어나 버린다. (윈도우 gvim에서는 "\r\n"을 붙여서 2바이트) "set noeol binary" 옵션을 주어서 저장하면 끝에 "\n"을 붙이지 않긴 하는데, 리눅스에서 그 상태로 실행하면 에러가 나더라.
$ perl ascii.pl < input
Unrecognized character \x11 at ascii.pl line 2.
그렇지만 코드골프에 업로드하면 통과한다.

아니, 코드골프 사이트 가 봤더만 50바이트짜리 1등이 새로 올라와 있다 -ㅅ-;
-- Raymundo 2008-12-10 11:02 am
이름:  
Homepage:
내용:
 


컴퓨터분류

마지막 편집일: 2024-8-5 10:23 pm (변경사항 [d])
1430 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기