1. Ping

    Posted

    Ping
Changes in title
+Ruby でフリップフロップ
Changes in tags
Changes in body
Source | HTML | Preview

Ruby でフリップフロップ

孫の手や歯科医院のカードなど、まれに必要になるけど探してもなかなか見つけられないものがある。
Ruby のフリップフロップの例などもそうかもしれない。なので、未来の自分のためにメモ。

フリップフロップとは

プログラム用語でいえば、(条件A)~(条件B)の間かどうかを検査するロジック、と言える。古くはAWKやsedなどが実装していたそうだが、PerlやRubyにも実装されている演算機能だ。

ワンライナーで入力テキストの行抽出に用いられたりするシーンが想像しやすいかもしれない。実際そういったテキスト抽出に活用できる。

shell-session
# Ruby プログラム中の =begin ~ =end のコメント部分を抽出する
$ cat yourfile.rb | perl -ne 'print if /^=begin/../^=end/'
$ cat yourfile.rb | ruby -ne 'print if /^=begin/../^=end/'
$ cat yourfile.rb | awk '/^=begin/,/^=end/'

# 特定の時間帯のログを抽出する
$ cat httpd_access.log | perl -ne 'print if /12:\d\d:\d\d/../15:\d\d:\d\d/'
$ cat httpd_access.log | ruby -ne 'print if /12:\d\d:\d\d/../15:\d\d:\d\d/'
$ cat httpd_access.log | awk '/12:[0-5][0-9]:[0-5][0-9]/,/15:[0-5][0-9]:[0-5][0-9]/' # awkの正規表現は\dが無い…
# sedの機能は何かちょっと毛色が違くて…よく分からん

実行系(プログラムを動かす側)の内部で、“(条件A)が真になったか” と “(条件B)が真になったか”、 を自動的に記憶している。それら2つの真偽値の判定条件と結果の組み合わせによって、返す値がパタパタというかギッコンバッタンというか変わるので、フリップフロップというとかなんとか。

プログラマ側からしてとっつきにくいのか、情報や活用事例があまりない。たかが式なのに、以前の状態を記憶しながら真偽判定するという、風変わりで分かりにくい演算式なためだろうか。別な言い方をすれば(Rubyのマニュアルが分かりやすいが式自身が状態を記憶している、といっても良いだろう。この「書いてもないのに誰か勝手に状態を記憶していて判定処理をしてくれる」というもやもやさが、今一つ活用されていない理由かもしれない。

実例コード

ワンライナーじゃない場合のサンプルコード。やってることはそんな難しくない。

Ruby
# (ex 1) html ドキュメント中の <body> タグの中身を行単位で抜き出す
response_body.split(/\r\n|\n/).each do |line|
  bodylines.push(line) if (line=~/<body/)..(line=~/<\/body>/)
end

# (ex 2) メールの生データ(SMTP)から本文部分を抜き出し(Multipartは
File.open('sample.txt') do |file|
  file.each_line do |line|
    print line if (line=~/^$/)..(line=~/^\.$/)
  end
end

ちなみに、Perlだとこう。

Perl
# (ex 1)
foreach ( split /\r\n|\n/, $response_body ){
    push @bodyline, $_ if /<body/../<\/body>/;
}

# (ex 2) *utf8処理は割愛
open(FILE, '< sample.txt');
while (<FILE>){
  print if /^$/../^\.$/;
}
close(FILE);

.. と ... の違い

Rubyのマニュアルにサンプルがある。ややこしいが、(条件A)と(条件B)が異なった条件の場合は.....の振る舞いに違いはなく、同じ条件式であった場合に、違いが生じる。
(Ruby も Perl も同じ)

  • (条件X)..(条件X)の場合は、該当値だけが真値になる。
  • (条件X)...(条件X)の場合は、該当値以降次の(条件X)までが真になる。(該当箇所が1か所しかなければ、そこから最後まで)

前者は他の判断式で簡単に記述できるので使われることはないだろう。

後者では、フラグ値などを変数に持たずして特定の行以降を取得する、といったことができそうだ。
また、特定の区切り子に囲まれた部分を抽出する、といったことが可能になるので、Qiitaのようなマークダウン中の```に囲まれたコードの部分だけ抽出する、といったことが可能だ。

sample.md
This is print sample.
---
print "The quick brown fox jumps over the lazy dog"
---

And this is Hear-Document sample.
---
print(<<EOT) if no == 42
the Answer to the Ultimate Question of Life, the Universe, and Everything
EOT
---
shell-session
$ ruby -ne 'print if /^---/.../^---/' sample.md
---
print "The quick brown fox jumps over the lazy dog"
---
---
print(<<EOT) if no == 42
the Answer to the Ultimate Question of Life, the Universe, and Everything
EOT
---

※ Qiitaのブロックコード中の中に、``` は書けないので、--- で代用した。

おわりに

…と、説明してみたものの、やっぱり小難しいのと知名度が低いため、あまり使いどころは無いかもしれないなぁ。他の人が知らなければコードインスペクションでも問題になりそうだ。