またもや変わったタイトルだけど。
星田さんが凄く勉強が出来る人だ、ってのはとにかくメモ魔で学習記録を細かく取っている事だ。
これはなかなかやろうとして出来ない。
ぶっちゃけ、こういう習慣があるから上達が早い、と思っている。
こういう「記録を取る」事をしない人は「プログラミングが上達」するわけがないんだよな。
ノートを取ったり、記録を録って何がいいのか、と言うと過去の自分と比べて進捗具合が実感出来るから、だ。
「あ、俺って進歩してんだな」
って実感出来るのがどれだけ重要か。
迂闊なヤツはそこに気づかない。
マジで初代「ドラゴン桜」に描いてる事には嘘が無いと思っている。
さて、たまには昔やった問題をやってみてもいいだろう。
やってみたら「なんでこんな問題で悩んでたんだろ?」とか思うだろう。
それがまた自信を付ける。
今回のお題は、まずはPAIZAのこの問題だ。
3次元リストでドット絵を表示するここでは、複数のドット絵を表示するために、3次元リストを使ってみます。ドット絵のパターンごとに、リストを切り替えて表示してみましょう。
これはPythonでのお題だが、Lispを学ぶとまた違った視点から見れるだろう。
大体、
「なんでこんなにforばっか使ってたんだろ?」
って意味が分からない筈だ(笑)。
今やるならmapやfor-each、そしてPythonだったらリスト内包表記がまず最初に「使わなアカン」機能の候補、となる。
単純には、1を#で置き換え、0をスペースで置き換えて出力せよ、と言う問題だ。
データはSchemeで作ると次のようになる。
(define *enemy-img* '(((0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0)
(1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
(1 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1)
(1 1 0 0 0 0 1 1 0 0 0 0 0 0 1 1)
(0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0)
(0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0)
(0 0 0 0 1 1 1 0 0 0 0 0 0 1 1 1))
((0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0)
(1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
(1 0 0 1 1 1 0 0 0 0 1 1 1 0 0 1)
(1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 1)
(0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0)
(0 0 0 1 1 1 0 0 0 0 1 1 1 0 0 0)
(0 0 0 1 1 1 0 0 0 0 1 1 1 0 0 0))
((0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0)
(1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1)
(1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 1)
(1 1 0 0 0 0 0 0 1 1 0 0 0 0 1 1)
(0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0)
(0 0 1 1 1 0 0 0 0 0 0 1 1 1 0 0)
(1 1 1 0 0 0 0 0 0 0 1 1 1 0 0 0))))
Pythonの出題を直球勝負でScheme/Racketで解くと以下のようになるだろう。
(for-each (lambda (img)
(for-each (lambda (line)
(for-each (lambda (dot)
(display (if (= dot 1)
#\#
#\space))) line)
(newline)) img)
(newline))
*enemy-img*)
結果は以下のようになる。
ただ、もうちょっとLispっぽく考えてみる。
実際問題、何度も言ってるけど「出力はどーでもいい」。
この問題の本質は、リストに詰め込まれたデータに従って「文字列のリストを作れ」だ。「何でもかんでも出力せな」ってのはC言語に脳を侵された考え方だ、ってのは何度も言っている。
従って、Lispっぽく考えると・・・いや、本当の事を言うと「モダンな言語の考え方」から言うと、データ変換、そしてデータ生成の問題なんだ。
そしてデータ変換とデータ生成さえしておけば、変数にでも代入しておいて「使い回し」が可能だ。出力と言う副作用は「消えてしまう」ので、あとで出力しとけばエエや、的な考え方の方がプログラミング上はマシなんだ。
従って、「本当の解」はこうなってた方が良い。
(map (lambda (img)
(apply string-append
(append
(map (lambda (line)
(string-append
(list->string (map (lambda (dot)
(if (= dot 1)
#\#
#\space)) line)) "\n"))
img) '("\n"))))
*enemy-img*)
これはこういう解を返すが、
あるいはSRFI-13のstring-concatenateで文字列を連結しちまってdisplayしてもいいだろう。
言えるのは、データ生成さえ性交成功しちまえば煮るなり焼くなり好きなようにしろって事になるわけだ。
次は演習問題なんだけど、ネタは同じだ。
データはこんなカンジ。
(define *letters* '(((0 0 1 1 0 0)
(0 1 0 0 1 0)
(1 0 0 0 0 1)
(1 1 1 1 1 1)
(1 0 0 0 0 1)
(1 0 0 0 0 1))
((1 1 1 1 1 0)
(1 0 0 0 0 1)
(1 1 1 1 1 0)
(1 0 0 0 0 1)
(1 0 0 0 0 1)
(1 1 1 1 1 0))
((0 1 1 1 1 0)
(1 0 0 0 0 1)
(1 0 0 0 0 0)
(1 0 0 0 0 0)
(1 0 0 0 0 1)
(0 1 1 1 1 0))))
直球勝負の解はこれ。
(for-each (lambda (line)
(for-each (lambda (dot)
(for-each (lambda (x)
(display (if (= x 1)
#\@
#\space))) dot)
(newline)) line)
(newline)) *letters*)
モダンな言語のティピカルな方針では次のようになる。
(map (lambda (line)
(apply string-append
(append (map (lambda (dot)
(string-append
(list->string
(map (lambda (x)
(if (= x 1)
#\@
#\space)) dot)) "\n"))
line) '("\n"))))
*letters*)
最後は次の演習問題だ。
これはデータ生成としては単にenumerateを使え、ってだけの話であって、実はそこで本質的には終わってる。出力するのはあくまでオマケなだけ、だ。
と言うか、「何でもかんでもオチは出力」と言う形態である以上、Paizaの問題作成者は明らかにC言語脳だろう。Pythonでの本質的な「使い方」が分かってるとはちと思えない。
さて、何度も書くが、Scheme/Racketだと、基本的にはenumerateは次のようにして実装出来る。
(define enumerate
(case-lambda
((iterable start)
(map cons (range start (+ start (length iterable))) iterable))
((iterable) (enumerate iterable 0))))
むしろ、Scheme的には「enumerateを書け」って言った方が問題っぽくなる(笑)。
繰り返すが、これは便利な関数なんで自作ユーティリティに是非とも付け加えて欲しい関数だ。
これを利用すると解は以下のようになる。
(define *text* '("吾輩は猫である"
"名前はまだ無い"
"どこで生まれたか"
"とんと見当がつかぬ"))
(for-each (match-lambda
((cons i line)
(display (format "~a:~a~%" (add1 i) line))))
(enumerate *text*))
match-lambdaを使えばシンプルに書ける、と言う典型例だろう。
以上。
たまにはこうやって、過去やった問題を新しい視点で振り返ってみたら良いだろう、と言うお話でした。