中学生と Perl プログラムを作ってみた

プログラミングなんかやったことない中学生の息子に、ゲームプログラムはどうやって作るんだと訊かれたので、くどくど説明するよりやってみた方が早いだろうと一緒に作ってみた。

最初は乱数を発生して上か下かを選ぶプログラムだったんけど、リアリティがないようだったので対戦形式にしてみた。

売ってるゲームも、これを複雑にしたようなもんだと説明したが、簡略化しすぎだろうかw?

(https://gist.github.com/8420481)

設問

  • 実はこのゲームには必勝法がある。必ず全問正解するにはどうすればいいか?
  • その必勝法を無効にするようにプログラムを修正してくれ。
  • 対戦成績を表示するようにしてみよう。
  • 相手が同じモンスターだったら、半分の確率で勝つようにしてみよう。

実は greple で PDF ファイルを検索できる

マニュアルに書いていませんが、実は greple で PDF ファイルを検索すると、自動的pdftotext コマンドが実行されて、テキストに変換した結果を検索します。

f:id:uta46:20140110145608p:plain

画像は、Ultegra Di2 のマニュアルから「フロントディレイラー」「動作」「バッテリー」のすべてを含む段落を探した例。 たまには役に立つこともあった。

探したかったのはこの文章。

バッテリー残量が少ない時、まずフロントディレイラーが動作しなくなり、続いてリアディレイラーが動作しなくなります。

--if オプションを使えば、自分で好きなフィルタを設定することもできます。

最後に空白行が出てるのはバグですねえ。 前にいじったときにエンバグしたらしい。 誰かテストスイート書いてくれないかなあ…

関連

cdif v2.6 --mecab オプション

Unicode 対応した cdif では、漢字・平仮名・片仮名の連続を1つのトークンとして処理し、その diff をとっています。 このトークン化に形態素解析ツールの mecab を使えるようにしてみました。

cdif --mecab

のように使います。

今のところ、外部コマンドとして mecab を起動しているので、効率的にはあまりよろしくはありません。

以下は、sdif と組み合わせて使っている例。 違う人の文章だから、違いが多すぎてあんまり参考にはならないか。

f:id:uta46:20140207155012j:plain

変更点

  • --mecab オプションの追加
    • Unicode 対応の mecab コマンドがインストールされている必要があります
    • mecab コマンドの仕様により、空白だけの行が無視されます
    • 日本語以外の部分のトークン化が影響を受けます
      • 具体的には、連続する記号が1つのトークンにされることがあります
  • --textcolor をデフォルトにした
    • diff -u の出力を見るのにはやはり未修整部分にも色が付いていた方がいい感じです

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 になっています。

f:id:uta46:20140111102309j:plain

あ、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 (追加)

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ワードとして扱われてしまう
    • --word-diff-regex でワードを正規表現で指定できるが使い方がよくわからない
    • とりあえず文字列を指定しただけではうまく動作しない
    • \p{Han} のような Unicode スタイルの指定は効かない
    • POSIX スタイルでの Unicode Property の指定の仕方が不明
  • 機能的には総じてよくできているが、通常の 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 の機能が意外にしょぼいと思った人は他にもいるようで、拡張してくれていた。

ediff.el

  • emacs で動作する
  • 日本語には対応している。
  • ワードは句読点で区切られる感じなので、荒すぎる。
  • 所感
    • 昔見た時にはよくできていると関心したのだが、実用的に使ったことはなかった
    • 改めて使ってみると、そんなには関心しない
      • 単語の区切りが荒すぎるせいもあるのだろうが、どちらかというとウインドウのインタフェースが使いにくいのだと思う。何がいけないかを分析するためには時間が必要だが、その労力に見合うか不明。

cdif

というわけで、どうも自分の目的にちょうどいいコマンドが見当たらないので、cdif を更新することにしました。 作ったのはモノクロ時代で、アンダーラインと反転で表示するしかなかったが、近代的にカラー化することに。 基本的な機能は20年前と変わっていなくて、モダンなプログラミングスタイルに書き換えて Unicode 対応した以外は、主にコスメティックな修正です。

  • https://github.com/kaz-utashiro/cdif
  • 1992年
  • Perl
  • Unicode 対応
  • ワードは alphanumeric、漢字、平仮名、片仮名の連続
    • それ以外の文字は、同じ文字が連続する場合はワードとして処理
    • ついでにハングルも入れてある
    • ヨーロッパ系はよくわからないので未対応
      • 教えてくれたら入れます
    • 中国語は漢字の連続をワードで処理すると困るかも
  • 文字単位で比較したい場合は -B オプションを使う
  • 色使いは OS X のターミナルでチューニングしてある
    • 一応 --colormap というオプションで変更可能
    • 仕様が固まっていないのでマニュアルには書いていません
    • 変えたい人は、ソースを書き換えちゃった方が早いです
  • そこそこ高速に動作
  • テキスト全体に色を付けるのは、見た目は派手だけど実用的に必要かというとそうでもない気がしてやっていない
    • ハイライトから抜ける時の処理が面倒なだけでもあったりはする
  • 実装は手抜きで、バックエンドで diff コマンドを使っているので、多分 Unix 系 OS のみ対応

f:id:uta46:20140110150042g:plain

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 関数の例としてはなかなかよいのではないかと。

print function to remove wdiff control sequence fo ...

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

実行結果はこんな感じ。

f:id:uta46:20140111102649p:plain

マルチカラーは結構便利で、~/.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' と同じ意味です。 このように出力されます。

f:id:uta46:20140113160441p:plain

こんな例だとあまり役に立つようには見えませんが、たとえばこんな風にするといくらか使い道はあるかもしれません。

greple --colorful --need 1 --icode=auto -e '\p{Han}+' -e '\p{InHiragana}+' -e '\p{InKatakana}+' -e '[a-zA-Z]+' -e '\d+' -e '[\pP\pS]+'

f:id:uta46:20140113160518p:plain

調子にのって --random というオプションも付けてみました。 ポップな感じですが、あまり意味はありません。

greple --all --colorful --random '\S+' greple

f:id:uta46:20140113160605p:plain