中学生と Perl プログラムを作ってみた
プログラミングなんかやったことない中学生の息子に、ゲームプログラムはどうやって作るんだと訊かれたので、くどくど説明するよりやってみた方が早いだろうと一緒に作ってみた。
最初は乱数を発生して上か下かを選ぶプログラムだったんけど、リアリティがないようだったので対戦形式にしてみた。
売ってるゲームも、これを複雑にしたようなもんだと説明したが、簡略化しすぎだろうかw?
(https://gist.github.com/8420481)
設問
- 実はこのゲームには必勝法がある。必ず全問正解するにはどうすればいいか?
- その必勝法を無効にするようにプログラムを修正してくれ。
- 対戦成績を表示するようにしてみよう。
- 相手が同じモンスターだったら、半分の確率で勝つようにしてみよう。
実は greple で PDF ファイルを検索できる
cdif v2.6 --mecab オプション
cdif v2.5
昨年末に更新した cdif を修正しました。
未修正部分の着色に対応
自分では必要ないと思っているのですが、未修整部分の文字にも色を付けられるようにしました。
また --colormap
というオプションで、それぞれのフィールドの色を指定できるようになっています。
デフォルトの設定はこう。
cdif --cm 'COMMAND=SE,OMARK=CS,NMARK=MS' \ --cm '*TEXT=,*CHANGE=BDw,DELETE=APPEND=RDw'
--commandcolor
, --markcolor
, --textcolor
オプションで、フィールドに色を付けるかどうかを指示することもできます。
--cc
, --mc
, --tc
とも省略可。
デフォルトでは --notc
になっています。
あ、DELETE
がなかった…
diff -t, -T に対応
その他、若干のバグ修正
ところで
今回、--colormap
オプションをテーブルに反映する部分のコードはこんな風になっています。
if (@opt_colormap) { map { my $c = pop @$_; map { $colormap{$_} = $c } map { match_glob $_, keys %colormap } @$_; } map { [ split /=/, $_, -1 ] } map { split /,/ } @opt_colormap; }
自分で書いといて、なんだか Perl っぽくないコードだなあと時代を感じたりするわけですが、3行目だけ古風な感じなので List::Util の reduce
を使って書き直そうかと思うと、これがどうもうまくいかないのですよ。
次のプログラムを実行すると、$hash{k1}
, $hash{k2}
に v が入ってほしいのだけど、2番目は undef
になってしまいます。
my %hash; reduce { $hash{$b} = $a; $a; } qw(v k1 k2); print Dumper \%hash;
$a
は一度代入すると値を失うようで、$a;
だけの行を削除すると動くし(この行は本来不必要)、代入する部分を "$a"
にしても大丈夫。
どうも perl5.12 に含まれる reduce
のバグではないかと。
List::Util::PP
を使えば動作するし。
なんとか使う方法はわかり、map と reduce だけのそれなりに趣のあるコードになりますが、動いたところで変な warning は出るし、無駄に可読性を下げるだけなので不採用でした。 参考のために一応見せるとこんなです。
use List::Util qw(reduce); no warnings; if (@opt_colormap) { map { reduce { map { $colormap{$_} = "$a" } match_glob $b, keys %colormap; } reverse @$_; } map { [ split /=/, $_, -1 ] } map { split /,/ } @opt_colormap; }
cdif アップデートしました
久しぶりに cdif コマンドを使おうとしたら $*
なんか使ったらいかんよと動いてくれなかった。
なにしろ、作ったのは1992年で、最後に更新したのが2003年という骨董品のコマンドなので、perl4 の仕様で書いてある。
翻訳作業などでは、文章の変更点を確認したいことがよくある。
diff を使えば、行単位での違いを表示してくれるのだが、1行が長くてその一部だけが変更されているような場合には、変更点を探すのが大変だ。
cdif を使うと、行の中で異なる部分をハイライトしてくれる。
フィルタとして動作するので、diff の出力をパイプで流し込めばよくて、通常の形式以外に -c
や -u
形式にも対応している。
元はと言えば、FrameMaker だったか何かで書かれた英文マニュアルの更新作業のためにやっつけで作ったものだ。
ちょうど、UCB の CSRG と 4.4BSD の開発をしている時で、Berkeley から Cupertino に帰るフリーウェイを走りながら実装方法を考えて、帰ってから深夜に作った記憶がある。
滅多に使うわけではないがないと困るので、まずは、今時、他にもっといいツールがあるんじゃないかと思って調べてみた。
wdiff
- https://www.gnu.org/software/wdiff/
- C
- 独自形式
- Unicode 対応してないっぽい
- ワードの区切りはスペースだけで、変更もできなさそう
- 所感
- スペース区切りだけでは日本語の文章に使うのは無理
- 出力が見にくい
jwdiff (追加)
- http://fumiyas.github.io/2013/12/12/jwdiff.sh-advent-calendar.html
- シェルスクリプト
- wdiff の区切りが使えないので、mecab で分かち書きするシェルスクリプトを作ってる人がいた。
- mecab でトークンに分けたものをタブでつなぎ、wdiff にかけた後でタブを削除するというもの
- 当然元の文章にタブが入っていると困るわけだが、特定用途には使えると思われる
- トークンを1行に変換し、diff した結果を反映するという方法も考えられるが、これはまさに cdif がやっていること
colordiff
- http://www.colordiff.org/
- Perl
- diff の出力に行単位で色を付けるだけ
- wdiff の出力も受け付けるが、やはりただ色を付けるだけ
- 特に Unicode 対応はしていないようだが、行指向なので普通に動作する
- 所感
- なんでこんなもののために独自ドメイン取ったんだろう?
diffc
- https://code.google.com/p/diffc/
- Python
- Unicode 対応なし
- 単語ではなく文字単位で処理するので出力が見にくい
- デフォルトの色使いは非常に見にくい
- 遅い
- 所感
- 労作ではあるのだが、どうも実用のために作ったのではない気がする
- Python だし、改造するつもりがあれば、わりと筋がいいかもしれない
- でも、ソースがチョー長くて読む気にならない
- 動作が遅いので、大量のデータを処理するのには向いていないかも
git-diff
- --color で色分けしてくれるが、これは行単位
- --word-diff で、wdiff 形式で出力
- --word-diff-color で変更部分を色分けして出力
- Unicode は扱えるが、単語の区切りは空白
- そのため日本語は全部1ワードとして扱われてしまう
- 機能的には総じてよくできているが、通常の diff 形式には対応していないし、フィルタとしても動作しないような気がする
- 多少手を入れればコマンド化は難しくないと思う
- wdiff 形式を diff 形式に変換することは簡単だが、それでは diff 形式の出力を処理することはできない
diff-highlight.pl
- https://github.com/git/git/tree/master/contrib/diff-highlight
- 2011年
- Perl
- git で使うために作られたもの
- フィルタとして機能する
- git diff --color の出力も処理できる
- コマンド自体は Unicode 未対応だが、perl -CSDA で実行すれば問題なく機能する
- コードは極めて短く、処理も単純
- 問題点
- diff -u 形式にしか対応していない
- 変更前後の行数が同じものだけを処理する
- プログラムの変更点を見るにはいいが、文章の変更部分をチェックするのには向いていない
- 文字単位で処理するため、単語の変更がわかりにくい (diffc と同様)
- アルゴリズムが単純なので、パターンによってはうまく処理できず、変更部分の抽出に失敗する
- 所感
- ちょっとしたコードの変更をわかりやすくすることができ、実装も簡潔かつ高速で優れた実装だが、文章の校正のような用途には向いていない。
- パラグラフが1文になっているワープロ原稿のようなもので、修正点が微細な場合には使えると思う。
Python 版 diff-highlight
diff-highlight の機能が意外にしょぼいと思った人は他にもいるようで、拡張してくれていた。
- https://pypi.python.org/pypi/diff-highlight/
- blog
- 2013年
- Python
- Unicode 対応
- diff -u 形式にのみ対応
- 所感
- 実は、cdif を更新してからこの記事に気がついた
- 文字単位の比較なので、文章に適用するのに最適ではない
- 最大の問題は出力の順序を変更してしまうことだが、後で元に戻すのは簡単にできるだろう
- 他の diff 形式に対応して、単語を認識するようにすれば汎用コマンドとして使える可能性あり
ediff.el
- emacs で動作する
- 日本語には対応している。
- ワードは句読点で区切られる感じなので、荒すぎる。
- 所感
- 昔見た時にはよくできていると関心したのだが、実用的に使ったことはなかった
- 改めて使ってみると、そんなには関心しない
- 単語の区切りが荒すぎるせいもあるのだろうが、どちらかというとウインドウのインタフェースが使いにくいのだと思う。何がいけないかを分析するためには時間が必要だが、その労力に見合うか不明。
cdif
というわけで、どうも自分の目的にちょうどいいコマンドが見当たらないので、cdif を更新することにしました。 作ったのはモノクロ時代で、アンダーラインと反転で表示するしかなかったが、近代的にカラー化することに。 基本的な機能は20年前と変わっていなくて、モダンなプログラミングスタイルに書き換えて Unicode 対応した以外は、主にコスメティックな修正です。
- https://github.com/kaz-utashiro/cdif
- 1992年
- Perl
- Unicode 対応
- ワードは alphanumeric、漢字、平仮名、片仮名の連続
- それ以外の文字は、同じ文字が連続する場合はワードとして処理
- ついでにハングルも入れてある
- ヨーロッパ系はよくわからないので未対応
- 教えてくれたら入れます
- 中国語は漢字の連続をワードで処理すると困るかも
- 文字単位で比較したい場合は
-B
オプションを使う - 色使いは OS X のターミナルでチューニングしてある
- 一応 --colormap というオプションで変更可能
- 仕様が固まっていないのでマニュアルには書いていません
- 変えたい人は、ソースを書き換えちゃった方が早いです
- そこそこ高速に動作
- テキスト全体に色を付けるのは、見た目は派手だけど実用的に必要かというとそうでもない気がしてやっていない
- ハイライトから抜ける時の処理が面倒なだけでもあったりはする
- 実装は手抜きで、バックエンドで diff コマンドを使っているので、多分 Unix 系 OS のみ対応
greple -Mdiff
さて、ついでというわけではないが、単に diff の出力に色を付けるだけなら greple のマクロだけでできちゃうぜということで、diff 用のモジュールを書いてみた。 いかが?
Greple command module for colorise diff output.
(2018-3-27 更新)
greple --word-diff-color 相当
git diff で、--word-diff-color を付けると wdiff のシンボルを削除して色づけして表示してくれるので、使い方によっては見やすくなる。
ちょっとした関数を定義すると greple でも同じことができる。
コードは短いがやっていることは、少々複雑である。
パラメータとして連想配列の要素が渡され、matched
というキーを持つ値にマッチした範囲が入っている。
テキスト全体は $_
に入っているので、これを書き換えれば出力を修正することができるのだが、位置が変わってしまうと、それに合わせて範囲の指定も修正してあげないといけない。
先頭から書き換えると後ろが狂ってきちゃうので、配列の最後からテキストと範囲の両方を修正してあげればいいという寸法だ。
ちなみに matched の要素は開始位置、終了位置以外に、パターンのインデックスも付いているので注意。
出力は通常の機能で行うので --print print_wdiff
に --continue
オプションも付けて使います。
見た目を気にしなければ wdiff -t
を使えばいいので、機能自体はご愛嬌だが、print 関数の例としてはなかなかよいのではないかと。
Greple モジュール対応
関連
モジュール
Greple は、~/.greplerc にオプションやプログラムを書くことができて、これはこれで便利なのだが、量が多くなってくるとごちゃごちゃしてわかりにくくなるのが問題だった。 また、使わない機能を毎回読み込むのも無駄なので、以前からモジュールを定義するようにしたいと思っていた。 やっとそれが実装できたのでリリースすることにする。
以下に Perl のスクリプトを検索するためのモジュールを付ける。
デモ用なので、あまり凝ったことはしていなくて、コメント、POD、それ以外というセクションを指定してパターンを探すことができる。
組み合わせることも可能で、--allsection
というオプションを使うと、すべてのセクションが指定される。
それではすべてのパターンが表示されてしまって意味がないようだが、マルチカラー環境だとそれぞれが違う色で表示されるのだ。
この程度の機能はわざわざプログラムを作らなくてもオプションだけで実装できる。
オプションの指定は、モジュールの __DATA__
以降に書くことができる。
形式は .greplerc と同じ。
参考のために、プログラム版とオプション版の両方を記述してあるが、オプションのみで実装するなら package 宣言だけあれば十分だ。
Greple module to handle perl script.
使う時には、次のように -M
オプションでモジュールを指定する。
このオプションは特別で、必ず先頭で指定しなければならない。
複数指定することもできる。
greple -Mperl --code default greple
greple -Mperl --colorful --allsection default greple
実行結果はこんな感じ。
マルチカラーは結構便利で、~/.greplerc に次のような行を入れておくと常に有効になる。 ただ、OS X のターミナルは比較的目に優しい色で表示してくれるのでいいのだが、原色で表示されるエミュレータなどを使っている場合は、何か方法を考えた方がいいだろう。
option default --colorful
デフォルトオプションは、もちろんモジュールでも定義することができる。
--chdir
や --glob
と組み合わせると、ファイルを指定しないで使用することもできる。
昔は、RFC のインデックスを検索するのにこの機能を使っていたが、最近は読まなくなっちゃったし、自分しか持っていないデータでなければ検索エンジンを使った方が早い。
--inside, --outside, --include, --exclude
--inside/outside
オプションの仕様が変更されている。
上の説明からもわかると思うが、複数の領域を指定した場合、いずれかの領域に含まれるマッチがすべて選択される。
以前はすべての条件を満たすマッチが選択されていたが、これは --include/exclude
オプションで実装するようにした。
単独で使うのであれば、どちらでも同じである。
--inside/outside
オプションで必要なマッチを選択し、--include/exclude
オプションで不必要な部分を削除するという使い方を想定している。
ところで
--include/exclude
は、grep との互換性のために使わずにおいたものだが、わかりやすいので使ってしまった。
他にもっといい考えがあれば、早めに教えて頂ければ今からでも変えちゃうかもしれない。
ちなみに mg は再帰的検索をサポートしていたが、greple では廃止してしまった。
実際に役立ったことはほとんどなかったからだ。
また greple は、テキスト検索に特化するように変更したので、いろいろなファイルが混在した環境では Encode モジュールが文句を言ったりしてイマイチ使いにくい。
どうしてもやりたいという方は、find の出力をパイプで渡して --readlist
で読み込んでください。
Greple 仕様変更と多色化
関連
--cut やめて --need にしました
新しく --need
というオプションを作って、マッチするパターンの数を指定できるようにしました。
そこで負の値を指定すると、今までの --cut
と同じ意味になります。
--cut
の方はわかりにくいので、まだ使っている人はいないだろうと思って削除しちゃいました。
ネガティブマッチを許す --allow
の方はそのままで、やはり負の値を受け付けるようにしました。
greple --need=2 --allow=1 'foo bar baz -yabba -dabba -doo'
--print 関数の仕様変更
--print
オプションで指定する関数の仕様を変えました。
今までは関数の返り値は見ていなかったのですが、今は返って来た値を出力するようになっています。 ですから、関数では出力したい文字列を返すようにします。 関数内で出力しちゃった場合には、空文字列を返してください。
また、--continue
オプションを指定すると、返り値をそのまま出力せずに、通常の出力処理を続けるようになっています。
元の文字列を変更してしまうと、うまく出ないことがあるので注意して使いましょう。
多色化
--colormode
で複数の色を指定できるようにしました。
そうすると、指定したキーワード毎に違う色を使うようになります。
また --colorful
というオプションを追加して、これは --colormode 'RD GD BD CD MD YD'
と同じ意味です。
このように出力されます。
こんな例だとあまり役に立つようには見えませんが、たとえばこんな風にするといくらか使い道はあるかもしれません。
greple --colorful --need 1 --icode=auto -e '\p{Han}+' -e '\p{InHiragana}+' -e '\p{InKatakana}+' -e '[a-zA-Z]+' -e '\d+' -e '[\pP\pS]+'
調子にのって --random
というオプションも付けてみました。
ポップな感じですが、あまり意味はありません。
greple --all --colorful --random '\S+' greple