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->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時間半でこれだけできれば上出来だろう。

コマンドライン引数のワイルドカードを展開する

今日も Python ネタ。

とかいいつつ Ruby の話から入るけど、Ruby はコマンドラインのワイルドカードを展開してくれる。

^o^ > type argv.rb
ARGV.each {|a| puts a}

^o^ > ruby argv.rb *.py
argv.py
binary_search.py
bubble_sort.py
counting_sort.py
count_char.py
count_files.py
csv_read.py
csv_write.py
default_arg.py
dict.py
dictionary.py
dict_keys.py
difference.py
difference2.py
enumerate.py
exception1.py
exception2.py
exception3.py
expand_num.py
(以下略)

でも、Python はそんな気の利いたことしてくれない。

^o^ > type argv.py
import sys

print sys.argv


^o^ > python argv.py *.py
['argv.py', '*.py']

そこで、glob モジュールの出番になる。

 cf. 10.7. glob — Unix 形式のパス名のパターン展開

import sys
import glob

files = glob.glob(sys.argv[1])
for file in files:
    print file

実行:

^o^ > python argv2.py *.py
argv.py
argv2.py
binary_search.py
bubble_sort.py
counting_sort.py
count_char.py
count_files.py
csv_read.py
csv_write.py
default_arg.py
dict.py
dictionary.py
dict_keys.py
difference.py
difference2.py
enumerate.py
exception1.py
exception2.py
exception3.py
expand_num.py
(以下略)

辞書を値でソートする

Python で辞書をソートしようとすると、

>>> h = {'a' : 200, 'b' : 300, 'c' : 500, 'd' : 100, 'e' : 400}
>>> for k, v in sorted(h):
...    print k, v
...
Traceback (most recent call last):
  File "", line 1, in 
ValueError: need more than 1 value to unpack

エラーになった。これは、sorted(h) がキーしか返さないからだ。

>>> for k in sorted(h):
...     print k
...
a
b
c
d
e

キーと値がほしいときにはこうする。

>>> for k, v in sorted(h.items()):
...     print k, v
...
a 200
b 300
c 500
d 100
e 400

辞書の items メソッドは、キーと値からなるタプルのリストを返してくれる。

>>> h.items()
[('a', 200), ('c', 500), ('b', 300), ('e', 400), ('d', 100)]

で、そのタプルの第1要素でソートされる。つまり辞書のキーでソートされるわけだ。

でも、今日やりたいのは値でソートすること。そのためには、sorted に key 引数を渡してやればいい。具体的にはこうする。

>>> for k, v in sorted(h.items(), key=lambda x: x[1]):
...     print k, v
...
d 100
a 200
b 300
e 400
c 500

key 引数に渡しているのは、タプルの第2要素を返す関数だ。つまりこの関数の返り値でソートされるわけだな。
ちなみに、降順にするには、reverse=True を渡してやる。

>>> for k, v in sorted(h.items(), key=lambda x: x[1], reverse=True):
...     print k, v
...
c 500
e 400
b 300
a 200
d 100

__main__.py

今日は Python。

ディレクトリやzipファイルに、__main__.py という名前のスクリプトを含めておくと、そのディレクトリやzipファイルを Python の引数に指定することで、__main__.py が実行される。

例を示そう。

print "Hello, Python."

これを、hello フォルダの中においておく。で、次のように実行すると:

^o^ > ls hello
__main__.py

^o^ > python hello
Hello, Python.

このとおり、__main__.py が実行された。

zipファイルにした場合も同じように動く。

^o^ > cd hello

^o^ > zip hello.zip __main__.py
  adding: __main__.py (164 bytes security) (stored 0%)

^o^ > python hello.zip
Hello, Python.

こんな機能があったとは。でも、どういうときに使うんだろ。

トリボナッチ数をSchemeで

昨日は Python でやったので、今日は Scheme で。

(define tribonacci
  (lambda (n)
    (let loop ((m n) (l '(0 0 1)))
      (if (= m 0)
          '()
          (cons (car l) (loop (- m 1) (append (cdr l) (list (apply + l)))))))))

(print (tribonacci 20))

今回は名前つきletで再帰。
実行:

^o^ > gosh tribonacci.scm
(0 0 1 1 2 4 7 13 24 44 81 149 274 504 927 1705 3136 5768 10609 19513)

トリボナッチ数

トリボナッチ数というのを知った。

 cf. フィボナッチ数,トリボナッチ数 – arik_egaのノート

要するに、フィボナッチ数が前2項の和であるのにたいして、トリボナッチ数は前3項の和である数。最初の3項は 0,0,1。詳しくは Wikipedia で。

Python のジェネレータで書いてみた。

def tribonacci(n):
    a, b, c = 0, 0, 1
    m = 0
    while m < n:
        yield a
        a, b, c = b, c, (a + b + c)
        m += 1

for t in tribonacci(20):
    print t [/crayon]

この例では、最初の20項を出力している。 実行例:

^o^ > python tribonacci.py
0
0
1
1
2
4
7
13
24
44
81
149
274
504
927
1705
3136
5768
10609
19513