SchemeでCodeEvalのPascals Triangle

↓こういうのを見つけたので Scheme でやってみよう、と思ったら CodeEval って Scheme ないんでやんの。

 cf. Python2でCodeEvalのPascals Triangle – brainstorm
 cf. Pascals Triangle – CodeEval

しょうがないから勝手にやる。

(define pascals-triangle
  (lambda (depth)
    (let loop ((n depth) (pas '((1))))
      (if (= n 1)
          (reverse pas)
          (let ((next (map + (cons 0 (car pas)) (append (car pas) '(0)))))
            (loop (- n 1) (cons next pas)))))))

(define flatten
  (lambda (list-of-list)
    (fold-left append '() list-of-list)))

(define print-pascals-triangle
  (lambda (depth)
    (print (string-join (map number->string (flatten (pascals-triangle (string->number depth)))) " "))))

(define main
  (lambda (args)
    (with-input-from-file (cadr args)
      (lambda ()
        (port-for-each print-pascals-triangle read-line)))))
^o^ > type input-pascal.txt
6

^o^ > gosh pascal.scm input-pascal.txt
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1

たぶんあってる。

Schemeでファイルから行を読み込む

cf. http://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E5%87%A6%E7%90%86#H-1d1usg4

ファイルから1行ずつ読み込んで処理をするやり方。

(define main
  (lambda (args)
    (with-input-from-file (cadr args)
      (lambda ()
        (port-for-each print read-line)))))

この例では print してるだけだけど、代わりにここに行を処理する関数を入れてやればいい。

^o^ > type foo.txt
foo
bar
baz

^o^ > gosh read-each-lines.scm foo.txt
foo
bar
baz

次は行ごとのリストにする方法。

(define main
  (lambda (args)
    (print (call-with-input-file (cadr args) port->string-list))))
^o^ > gosh read-lines-list.scm foo.txt
(foo bar baz)

最後に、ファイル全体を文字列にするには:

(define main
  (lambda (args)
    (print (call-with-input-file (cadr args) port->string))))
^o^ > gosh read-lines-string.scm foo.txt
foo
bar
baz

Sinatraで”invalid byte sequence in Windows-31J”

先週に引き続き古い Web アプリを Ruby2.0 対応(一番最近のコミットが2010年の11月だった)。今回は Sinatra。
で、スクリプトのエンコードを UTF-8 にして、require 'jcode' を消して、などと一通り修正して完了。無事動くようになったんだけど、日本語を含む URL のときにタイトルのエラーが発生した。しかも、どうもアプリじゃなくて Rack の中で起きているらしい。
こんなのどうすりゃいいんだよ、と思いながらググってみたらずばりそのもののページを見つけた。

 cf. Sinatra が ”invalid byte sequence in Windows-31J” でエラーページを表示しない(@Windows) – Qiita

どうやら、Rack は外から来る文字列のエンコーディングを Encoding.default_external の値だとして変換するらしい。で、その Encoding.default_external の値が Windows-31J なわけだ。でも実際は UTF-8 の文字列だから変換中にエラーが発生する、と。

というわけで、app.rb の中に1行追加した。

Encoding.default_external = 'utf-8'


上記のページでは config.ru の中に追加してるけど、こっちのほうがいいような気がする。気のせいかもしれないけど。

さて、これでめでたく日本語が入ってても動くようになった。

RDiscountでmarkdownをためす

昔書いた CGI にちょっと手を入れたついでに、ドキュメントを Markdown に書き直そうと思って調べてみたら、Ruby では RDiscount ってのがよさそうだったので試してみた。今日はそのメモ。

拡張ライブラリが含まれているというのでちょっと心配だったけど、インストールはあっさりすんだ。

^o^ > gem install rdiscount
Fetching: rdiscount-2.1.7.1.gem (100%)
Temporarily enhancing PATH to include DevKit...
Building native extensions.  This could take a while...
Successfully installed rdiscount-2.1.7.1
Parsing documentation for rdiscount-2.1.7.1
unable to convert "\x90" from ASCII-8BIT to UTF-8 for lib/rdiscount.so, skipping

Installing ri documentation for rdiscount-2.1.7.1
1 gem installed

Markdown の例はこんな感じ。記法についてはググればいくらでもページが見つかるので割愛。

見出し1
========

見出し2
--------

ここは本文。

### 見出し3

見出し3以上は # を行頭につける。見出し6までいけるみたい。

### 番号なしリスト

行頭に * か - で番号なしのリストになる。

- リスト1
- リスト2
- リスト3

### 番号つきリスト

番号つきリストは行頭に 1. というふうに番号をつける。

1. リスト1
2. リスト2
3. リスト3

----

3つ以上の - で水平線。

これを HTML に変換するには、rdiscount コマンドを使えばいい。標準出力に吐き出すので、適当なファイルにリダイレクトしてやる。

^o^ > rdiscount example.md > example.html

Ruby のスクリプト内で使うには、RDiscount クラスのインスタンスを作って to_html メソッドで変換する。

require 'rdiscount'

markdown = RDiscount.new(File.open(ARGV.shift, "r"){|f| f.read})
print markdown.to_html

ま、とりあえず使うのはこんなところ。

cut

昨日、あとで調べてみよう、と書いた cut について調べてみた。

 cf. 4.3 手続きを作る – Gauche ユーザリファレンス

[SRFI-26] 手続きを簡潔に書ける便利なマクロです。 いわゆる部分適用を実現するために使えます。

SRFI-26 とあるけど、Gauche では標準で使えるようになっている。

たとえば、文字列3つを引数にとって、”-” でつなげる手続きを考えよう。

gosh> (define foo
  (lambda (a b c)
    (string-join (list a b c) "-")))
foo
gosh> (foo "hoge" "fuga" "piyo")
"hoge-fuga-piyo"

この手続き foo の引数のうち、2つめだけをあとから与えるようにしたいとする。つまり、1つめと3つめだけ先に部分適用したい、と。そういうときに cut を使えば実現できる。

gosh> (cut foo "hoge" <> "piyo")
#<closure #f>

シンボル <> が後から与えられる引数の代わりになっている。実際に使ってみると:

gosh> ((cut foo "hoge" <> "piyo") "fuga")
"hoge-fuga-piyo"

確かに2つめの引数をあとから与えることができている。
シンボル <> はいくつあってもいいらしい。後から与えられる引数と対応した <> に当てはめられる。

シンボル <...> を使うと可変長引数をとることができるようになる。

gosh> (cut list <...>)
#<closure #f>
gosh> ((cut list <...>) "foo" "bar" "baz")
("foo" "bar" "baz")

なるほどねぇ。つまり昨日の (cut show-help (car args)) は手続き(ただし、引数なし)を作っていたわけだ。

Gaucheでコマンドライン引数を処理する

main 手続きでコマンドライン引数を受け取れることを覚えたので、今度は引数(オプション)を処理するやり方を調べてみた。もちろん、Gauche にも用意されている。

 cf. 9.19 gauche.parseopt – コマンドライン引数の解析 – Gauche ユーザリファレンス

この gauche.parseopt というモジュールは、Perl のコマンドライン処理にヒントを得たものらしい。とにかく例を見ながら書いてみた。

(use gauche.parseopt)

(define greeting
  (lambda (name n morning)
    (let ((message (greeting-massage morning)))
      (let loop ((m n) (l '()))
        (if (= m 0)
            l
            (let ((msg (string-append message ", " name "!")))
              (loop (- m 1) (cons msg l))))))))

(define greeting-massage
  (lambda (morning)
    (if morning
        "Good morning"
        "Hello")))

(define show-help
  (lambda (progname)
    (print (string-append "Usage: gosh " progname " [options] NAME"))
    (print "Options:")
    (print " -m --morning Good morning.")
    (print " -t --times=N N times greeting.")
    (print " -h --help Show this message.")
    (exit)))

(define main
  (lambda (args)
    (let-args (cdr args)
      ((morning "m|morning")
       (times "t|times=i" 1)
       (help "h|help" => (cut show-help (car args)))
       . restargs
      )
      (for-each print (greeting (car restargs) times morning)))))

いろいろ書いているけど、キモは33~38行目の let-args だ。ここで、スクリプトの受け付けるオプションを定義している。基本形は (morning "m|morning")。m が短いオプション名で morning が長いオプション名。-m オプションが指定されると #t に変数 morning が束縛される。
オプションは引数をとることもできる。”t|times=i” という書き方は -t(–times)オプションが整数を引数に取ることを指示している。と同時に、1 とあるのはオプションが指定されなかったときのデフォルト値だ。
-h(–help)オプションはコールバック関数を呼び出す。(cut ...)というのは例をまねただけなのでよくわからない(あとで調べてみよう)けど、これでヘルプを表示する show-help 関数を呼び出せるようだ。
で、オプションとして解析されなかった残りのコマンドライン引数は、restargs に束縛される。

それじゃ、うまく動くか試してみよう。

^o^ > gosh greeting.scm --help
Usage: gosh greeting.scm [options] NAME
Options:
  -m --morning       Good morning.
  -t --times=N       N times greeting.
  -h --help          Show this message.

^o^ > gosh greeting.scm Andy
Hello, Andy!

^o^ > gosh greeting.scm --morning Andy
Good morning, Andy!

^o^ > gosh greeting.scm --morning --times 3 Andy
Good morning, Andy!
Good morning, Andy!
Good morning, Andy!

うまくいったようだ。
もっと詳しくはリファレンスマニュアルで。

Schemeでn回の繰り返し(の返り値)

※追記あり

Scheme では繰り返しを再帰で実現する。じゃ、n 回の繰り返しはこう書けばいいだろうか。

(define hello
  (lambda (n)
    (let loop ((m n))
      (if (= m 0)
          #t
          (begin
            (print "Hello!")
            (loop (- m 1)))))))

(define main
  (lambda (args)
    (hello (string-&gt;number (cadr args)))))

このスクリプトは、引数の回数だけ “Hello!” を繰り返し出力する。

^o^ > gosh hello-loop.scm 3
Hello!
Hello!
Hello!

確かに n 回の繰り返しを行っていて、目的は達成している。けど、気になるのは hello 関数の返り値だ。ここでは便宜的に(というか苦し紛れに)#t を返しているけど、なんだか気持ちが悪い気がする。Scheme 的にはどうなんだろうか。

[追記]

Kei さんからコメントをもらった。返り値が要らないなら when か unless を使えばいいとのこと(未定義値が返ってくる)。

gosh> (define (hello n)
  (let loop ((i 0))
    (unless (= i n)
            (print "Hello!")
            (loop (+ i 1)))))
hello
gosh> (hello 3)
Hello!
Hello!
Hello!
#<undef>

確かに #<undef> が返ってきている。この方がしっくりくるな。when を使うとこうかな。

gosh> (define (hello n)
  (let loop ((i 0))
    (when (< i n)
          (print "Hello!")
          (loop (+ i 1)))))
hello
gosh> (hello 3)
Hello!
Hello!
Hello!
#<undef>

なるほど。うん、unless と when を覚えた。Kei さんありがとうございます。

と、ここまで書いといて何だけど、そもそも print を繰り返すこと自体が Scheme 的じゃない気がしてきた。なのでこう書き直してみた。

(define hello
  (lambda (n)
    (let loop ((m n) (ls '()))
      (if (= m 0)
          ls
          (loop (- m 1) (cons "Hello!" ls))))))

(define main
  (lambda (args)
    (for-each print (hello (string-&gt;number (cadr args))))))

hello 関数が返すのは “Hello!” のリストで、それを main 関数のほうで出力している。

^o^ > gosh hello-loop2.scm 3
Hello!
Hello!
Hello!

良くなった。

main手続き

今まで、Scheme のスクリプトは頭から順に実行されるもんだと思っていたけど、そうでもないらしい。main という名前の関数(Scheme では手続きというのか)が定義されていると、それがスクリプトのエントリポイントになる。

 cf. 3.3 Schemeスクリプトを書く – Gauche ユーザリファレンス

ファイルが正常にロードされたら、goshは userモジュールに ‘main’ という手続きが定義されているかどうか調べ、 定義されていればそれを呼びます。mainには、スクリプトへの引数のリストが 唯一の引数として渡されます。リストの最初の要素はスクリプトファイル名です。

試してみよう。

(define main
  (lambda (args)
    (print args)))

実行:

^o^ > gosh main.scm foo bar baz
(main.scm foo bar baz)

確かに main 手続きが実行された。

じゃあ、昨日の nend.scm を main 手続きを使って、引数をコマンドラインから渡せるように変更してみよう。

(define divmod
  (lambda (n m)
    (list (div n m) (modulo n m))))

(define mul
  (lambda (n)
    (let loop ((dm (divmod n 10)) (l '()))
      (if (= (car dm) 0)
          (apply * (cons (cadr dm) l))
          (loop (divmod (car dm) 10) (cons (cadr dm) l))))))

(define nend
  (lambda (n)
    (let loop ((x n) (i 0))
      (if (< x 10)
          i
          (loop (mul x) (+ i 1))))))

(define nend_smallest
  (lambda (n)
    (let loop ((i 1))
      (if (= (nend i) n)
          i
          (loop (+ i 1))))))

(define main
  (lambda (args)
    (print (nend_smallest (string->number (cadr args))))))
^o^ > gosh nend2.scm 2
25

^o^ > gosh nend2.scm 3
39

^o^ > gosh nend2.scm 4
77

^o^ > gosh nend2.scm 5
679

うまくいった。

ところで、今まで書いてきたみたいに手続きの外に (print ...) があるとどうなるんだろう。

(print "Hello, world.")

(define main
  (lambda (args)
    (print args)))

実行してみると:

^o^ > gosh main2.scm foo bar baz
Hello, world.
(main2.scm foo bar baz)

(print "Hello, world.") と main 手続きが両方実行されたよ。

マーティン・ガードナーの最難問

というのを見つけた。

 cf. 数学の粘度のやつ – arik_egaのノート

ある数の粘度は、すべての桁を掛けて出る答えが1桁になるまでにかかる積算の回数で表す。それぞれの桁の数を掛け算して出るのが2番目の数で、そのまた全桁の数を掛けて出るのが3番目の数…こうして1桁の数が出るまでやり、出るまでに重ねた掛け算の回数を数えるのだ。

例えば、77は粘度4だ。なぜなら1桁になるまで4回掛け算しなきゃならないからね(77-49-36-18-8)。粘度1で一番小さい数は10、粘度2で一番小さい数は25、粘度3で一番小さい数は39、粘度4の最小数は77だ。では、粘度5で一番小さい数は何?

Scheme でやってみた。特に何の工夫もなく力任せに1から順に試していって粘度が5になる数を見つけたらそれを返すだけ。

(define divmod
  (lambda (n m)
    (list (div n m) (modulo n m))))

(define mul
  (lambda (n)
    (let loop ((dm (divmod n 10)) (l '()))
      (if (= (car dm) 0)
          (apply * (cons (cadr dm) l))
          (loop (divmod (car dm) 10) (cons (cadr dm) l))))))

(define nend
  (lambda (n)
    (let loop ((x n) (i 0))
      (if (< x 10)
          i
          (loop (mul x) (+ i 1))))))

(define nend_smallest
  (lambda (n)
    (let loop ((i 1))
      (if (= (nend i) n)
          i
          (loop (+ i 1))))))

(print (nend_smallest 5))

実行結果:

^o^ > gosh nend.scm
679

ボルダリングをやってきた(4)

今週から、いつも行ってるボルダリングジムの、80度と90度の壁のホールドがレイアウト変更になった。何度か書いた、いつもできなかった水色の4番の課題は結局できないまま、なくなってしまった。無念。

で、変わってしまったものは仕方がないので、改めて最初からやることにした。結局今日はピンク(いちばん簡単)の1~8、緑の1~8、水色の1~3と、手足限定のオレンジA、Bに成功。ま、2時間半でこれだけできれば上出来だろう。