|
|
2008年12月04日(木)
|
はれ
|
2/ 6
|
カテゴリ: Regex
正規表現の構文を色分けブロック、フロー表示するstrfriend 。
strfriendを作ろうとすると、正規表現がスタック型の言語に見えてくる。
最初、"strfriend" という、誰かのブックマーク名だけ見たときは、
てっきり「友達リストを一発で返すAPI」かと思った。
strfriendがイケてないのは、改行やタブ、空白文字で整形した
正規表現記述 //x に対応していないこと。
関連: 正規表現実装の
処理方式の違いを説明するアニメーション。
|
2008年12月04日(木)
|
はれ
|
3/ 6
|
カテゴリ: Regex
iGoogleのガジェットとして扱われるデータが、
改行なしの理由がわかった 。JavaScriptの正規表現(RegExp)では、
//sオプションがないため、改行があると、最初の改行から先の
文字列は一切操作できないから。ただのデータ量削減のためでは
なかった。
上のような、「漢字の羅列の抽出」も、
PerlのUTF-8対応正規表現だと簡単にできる 。
/(?: [^\p{Han}]+ (\p{Han}+) )+ /x
[^\p{Han}]+は、漢字ではない文字の1文字以上の羅列。
\p{Han}+は、漢字(\p{Han})の1文字以上の羅列(+)だ。
後半の(\p{Han}+)の括弧のくくりで、この範囲の
文字列を、正規表現のキャプチャ文字列として返す。
さらに大きな外側の (?: ... ) 括弧では、このカッコ内が
繰り返しパターンとして(+)照合する形になっている。
ここで、+の特徴として、最後に照合して合致した文字列だけを
返すので、最後の漢字の羅列が取り出せる。
|
2008年10月08日(水)
|
あめ
|
7/ 7
|
カテゴリ: Perl Regex
Perlのコメント文入りの正規表現内では、
コメント文内で / を使うと文法エラーまたは divided by 0 。
Perlの正規表現で、/x オプションを指定すると、正規表現内の
スペース、タブ、改行は無視され、自由にスペースを入れたり
改行したり、インデントして記述でき、たいへんわかりやすい。
また、# に続けてコメント文も記述でき、これまたたいへん
わかりやすい正規表現コーディングが可能になる。
が、正規表現内のコメント文内に / が現れると、
文法エラーになったり、0除算例外になったりするので注意。
0除算例外になる例:
% perl -e '$a="abc";$a=~/(b) #/co
/x;'
|
2008年09月28日(日)
|
くもり
|
3/ 9
|
カテゴリ: Perl Regex
|
2008年09月28日(日)
|
くもり
|
4/ 9
|
カテゴリ: Perl Regex
日ごろ不思議に思っていることを書いてみる - 04.10.5 正規表現の先頭にある $ マークは ?
/$\d+\.txt$/
とあるのは、正規表現の最後に /ms オプションがついていればよさそうな気もしたが、
/$\d+\.txt$/ms
やはり正規表現最初の文字が ^ でないと、マッチしない。
/^\d+\.txt$/ms
これなら、マッチする
|
2008年09月28日(日)
|
くもり
|
5/ 9
|
カテゴリ: Perl Regex
正規表現の機能の覚書き (2008/9/23)は (?=...) とか (?!...)の
使い方もあってよい 。その他、Longest Match の話もあり。
|
2008年09月28日(日)
|
くもり
|
8/ 9
|
カテゴリ: Regex
|
2008年09月20日(土)
|
はれ
|
1/ 14
|
カテゴリ: Perl Regex
Fedora 9 の httpd 2.2.9 で、
httpd の access_log の末尾に 2 つの数字が追加されていた 。
httpd の集計スクリプトが動かないので確認してみたら、
そういうことで正規表現を変える必要があった。
ログファイルの正規表現って、標準的につけてもらいたいような気が。
末尾までがちがちに正規表現を書いちゃった自分も悪いのだけれど。
Perl 5.10 の Perl 正規表現の \K について 。
「このあとの文字列削除 (kill)」みたいな意味なんだろうけれども、
いまいち直観的でないような。もう、かなり言語化してしまって
いるので、 \{KillAfterHere}とかでいいんじゃない
だろうか。 man perlreには Keep the left of this
operator とあった。$& とかの変数に保管することなく、Keepする
ので、コストが低いですよ、ということか。
この一文字オペレータはどこか来た道。あ、sendmail.cf か。
|
2008年09月15日(月)
|
くもり
|
2/ 8
|
カテゴリ: Perl Regex
正規表現のグループ (...)+ を使って
同一パターンの繰り返しを全部キャプチャしようとしたけれど
よくわからず 。split() した。しかし、split() はたくさんの
配列を変えさせると重くなる。かといって小さな split() を
繰り返すと、トータルではもっと遅くなる。
|
2008年09月15日(月)
|
くもり
|
4/ 8
|
カテゴリ: Perl Regex
Perlでは、改行を含む文字列の行頭、行末に
^, $でマッチしたいときは /m を使う 。
|
2008年09月14日(日)
|
くもり
|
1/ 6
|
カテゴリ: Regex Perl
PerlのUTF-8の環境で encoding 指定したあと、
/regex/i で何秒もかかることがある 。
漢字かな混じり、先月の日記、EUC-JP の 96KB のファイルを
Perl 5.8.8 (Fedora 7) で $_ に読み込んで
10回、a または A を b に置き換えると、a だけを b に置き換えるよりも
17秒長くかかる。/i なしだと 0.25秒程度が、/i ありだと 17 秒以上。
% time perl -e '
use utf8; \
use encoding "utf-8"; \
open(I,"200804file.html"); \
binmode(I, ":encoding(euc-jp)"); \
undef($/); \
<I>; \
for($n=0;$n<100000;$n++){$_ = s/a/b/isg;}'
ただの検索 /a/i; にしても、やはり長時間がかかる。
もともと UTF-8 のファイルを読み込んで、/a/i で検索すれば、
/i ありでも /i なしの 50% 増し程度の時間で完了するので、
use encoding の問題らしい。
かかる処理時間も、10万回の /a/i で 0.3秒。
|
2008年05月04日(日)
|
はれ
|
2/ 9
|
カテゴリ: Regex Perl
Perl の encoding を使うと、UTF-8 の正規表現が異常に
遅いので、ようやく自分の日記環境も、2008年分から全部 UTF-8 にした 。
binmode(FD, ":encoding(euc-jp)")にしなければ、
正規表現の検索も遅くならない。
UTF-8 での正規表現は日本語でもかなり自由に書けるようになった。
/ ( .+? ) ( [。?] ) /sx;
が通る。何か、。でも?でもない文字列の連続 (.+?) と、。か? ([。?])
が取り出せる。基本的なことなのだけれども、UTF-8 正規表現ができるまでは
jperl やら Jcode やらを使って、別の命令か何かを使わないと
処理できなかった。そういう意味でも日本語でもプログラムは簡単になった。
|
2008年05月04日(日)
|
はれ
|
3/ 9
|
カテゴリ: Perl Regex
日記の表示方法を大幅に変えつつある 。といっても
よくあるブログと同じだけれども。
日記はいままでテキストファイルで書きためてあるのと、
DB に入れると、その DB の更新やらメモリなどのリソース調達やらで
めんどうなので、テキストのままでいく。
あと、自分の日記にリクエストが来てあふれるということもない。
まず、日記を取り出す方法を次のようにした。
- 1か月分、日記のテキストファイルを全部読み込む
- 日別に分割する (split())
- 1日ごとに、記事に分割する (split())
- 記事ごとに、カテゴリラベル、タイトル、日付を取り出す
このあと、記事ごとに整形して表示する。
昔から、決まったルールで区切りを入れているので、入力間違いが
なければ、これでなんとなく対応できる。
いまのところ、記事を単独で表示するページのみ、この方法を
使っている。
元の日記のテキストファイルは次のような構造になっている。
<font size=+1><b>2008年4月29日 (火) くもりときどきはれ</b>lt;/font>lt;br>
<blockquote>
で始まり、
<blockquote>
<br>
<br>
<br>
で終わる。行頭の空白はタブが一文字入っている。
font タグや blockquoteを使っているのは、
大昔にこう書いたほうが、ほしい表示が早くできたから。
今ではこれらの文字列はただの区切り文字の意味しかない。
このようなテキストから、日ごとの文字列を配列を、split()で
取り出すための正規表現が、これ。
最後の /s は、改行文字を含めて正規表現を適用する、という意味。
/x は、正規表現の間にスペースやタブ、改行を入れて、読みやすく
表記できるようにするオプション。コメントも書ける。
my $DAY_DELIMIT = / # 正規表現を変数に設定する
# 使うときは split(/$DAY_DELIMIT/, $string) とすればよい
# 変数に設定するときは、/ は気にせず指定できる
# (?: は、split(//) の正規表現部分に与えたときに、
# split() の結果として配列に返してほしくないために指定する
(?:
[\r\n]+ # 改行の 1 つ以上の連続
>font \s+ size=\+1> >b> # タグにマッチする
(\d{4}) \D+ (\d+) \D+ (\d+) # 年月日をとりだして配列にして返す
[^(]+ \(([^)]+)\) # (水) などの曜日をとりだす。
\s* # 曜日のあとに空白が続くかもしれない
([^<]+) # < でない文字、タグでない文字列をとりだして返す
>/b> >/font> >br> # タグの羅列。正規表現を変数に設定するので / はそのまま
\s+ # 改行を含む空白の連続
>blockquote> # blockquote タグ
\s+ # 改行を含む空白の連続
# ここまでが前半の区切り文字列のパターン
|
# ここから先が後半の区切り文字列のパターン
[\r\n]+ # 改行の 1 つ以上の連続
\t # タブ文字
# ここまで、空白 \s の連続と指定しまうと、ほかの場所で
# マッチしてしまうため、特にわかっている形式を指定する。
>/blockquote> # blockquoteタグの終了
(?: \s+ >br> )+ ) # br タグの羅列。必要ないので (?: を指定する
/sx; # 正規表現の指定終了
この正規表現を @days = split(/$DAY_DELIMIT/, $string) などと
指定して、日記文字列全体から、日付単位で分割された配列を受け取る。
ここで、日付単位の配列は1セット 6個になる。おもしろいのは、
上の正規表現の前半部分に、配列として返すグループが 6 個指定してあるが、
後半はないのに、それぞれ前半がマッチしても、後半がマッチしても、
それぞれ 6 個づつの配列を返してくる。
確かに、前半が6個で、後半が0個では、どちらにマッチしているのかわからない。
なので、どちらにマッチしても 6個の配列を返してくれれば、処理は簡単だ。
返ってくる配列は、
- HTMLヘッダなどを書いた、日記ファイルの冒頭部分
- 6個がセットになった、日ごとのデータ
前半にマッチした場合は、先頭から順に、次のような配列になっている。
- 年 - 数字4桁
- 月 - 数字1桁 (あとで使いやすくするため 2 桁に直す)
- 日 - 数字1桁 (あとで使いやすくするため 2 桁に直す)
- 曜日 - 漢字一文字
- 天候 - 複数のひらがな
- 1以上の記事の連続 - 改行を含む
後半にマッチした場合は、すべて空の配列が 6 要素で返ってくる。
- サイドバーやアドレスなどが書いてある、日記ファイルの末尾部分
次に、各記事を分割する。単独の記事は次のような構造になっている。
<img src="img/news-icon.png" align=right>
Network: Web: 会社で14:00ごろからgoogle.co.jpがつながらなくなっていた。
google.com も gmail も youtube.com もつながらなかった。
...
最初に、画像表示などのため、HTMLタグが入ることがある。
次にカテゴリを示すラベルが ": " で区切られている。
ラベルは一行で書ききることにしている。そのあと、HTMLを含む
本文が続く。本文の冒頭の一文は、記事のタイトルになる。
最初の一文とは、本文の先頭から、最初の 。 までの区間になる。
これを正規表現で書くとこうなる。
/ (?: \s* < [^>]+ >)*
\s* ( (?: [^:\r\n]+? [^\s:] : \s+ )* )
\s* ( [^。]+ )。(.*) /sx;
上と同じ正規表現をコメントを入れて説明しておく。
こちらは split() ではなく、すでに取り出した文字列に
正規表現を適用して (=~)、括弧のグループ指定を使って、
特定の部分だけを配列としてとりだす形になっている。
そのため、括弧のグループで指定されていない部分は、
読み捨てる形になる。
my @article = ($_artString =~ / # $_artStringから、記事を分割する
(?: \s* < [^>]+ >)* # 記事冒頭にある HTML タグの連続を削除
# カテゴリのラベル文字列の連続をかたまりで取り出す
\s*
( # 空白文字列の連続があるかもしれない
# ひとつの文字列で返るので、あとで split() すること
(?: ### (?: ...)* という書き方をする
[^:\r\n]+? # : でも改行文字でもない連続 (1行内だけ注目する)
# 次の、直前に空白がない、を検査するため、+? にする
[^\s:] # : の直前は空白ではない ... カテゴリ文字列は2文字以上
# "C" などのカテゴリのラベル文字列は使えない
: # コロン文字1文字
\s+ # 空白文字が 1 文字以上 (: ) のようになっているはず
)* ### (?: ...) は配列として返らないが、この複数マッチをまとめて次の括弧で返す
) ### この括弧で、カテゴリ文字列の組の複数マッチを一度に返す
# タイトル文字列の取り出し
\s* ( [^。]+ )。 # タイトル文字列は、。までの文字
(.*) # それ以後は本文
/sx);
けっこう長くて気持ちが良い。
ここで、正規表現のグループの繰り返しについて。
()* というように、グループを繰り返し指定すると、
これにマッチしたときには、マッチしたときの最後のマッチ文字列ひとつだけが返る。
そのため、"Network: IPv6: " のような文字列があると、
最後の IPv6: しか返ってこない。
そのため、マッチするための文字列は (?:...) で囲み、
複数マッチする、(?:...)* をさらに普通の括弧で囲んで、
( (?:...)* ) としている。
こういう、正規表現のどの位置で何がマッチしたので、どの変数に
積み込むか、という作業を簡単にできそうなのが、
Perlの(... (?{code}))。
|
2008年04月29日(火)
|
くもりときどきはれ
|
2/ 6
|
カテゴリ: Perl Regex
"(?{ code })" に見る「正規表現言語」の野望 。
perl 5.8 の正規表現マニュアル man perlre を見ていたら、
highly experimental な "(?{ code })" なるものを見かけた。
いままで、正規表現では、マッチした一部のパターンの結果を
$1 や $2 などの変数で参照していた。そのため、正規表現を
適用したあと、別の行でコーディングする必要があった。
この "(?{ code })" は、正規表現のパターン中に、
その合致範囲 "()" に対してどのような処理ができるか
コードを記述できる。$^N 変数から、マッチした文字列を
とりだして、別の変数にセットできる。
また、()* のような正規表現を利用して、
( a (?{ local $cnt++; } ) )*
のように書くと、$cnt に、マッチした回数が加算される。
正規表現の最初に書けば、変数を初期化できる。
しかし、読みにくいというか、perlらしいのだろうけれども、
すごい。しかしこれだとデータとコードが本当に混在してしまって、
これでいいのか?という気も。そういうわけか、「だまって
削除するかもしれない」、と警告がマニュアルに書かれている。
"(?{ code })" は深いネストがある正規表現内で、
特定部分のマッチ文字列をとりだしたいときに便利そう。
()がネストしたり、条件分岐が増えると、特定のマッチ文字列を
とりだしにくくなる。
|
2008年04月19日(土)
|
くもり
|
6/ 6
|
カテゴリ: Regex Perl
UTF-8 で正規表現を使うとき、 //i のように
大文字小文字を無視するオプションをつけると、秒単位で
時間がかかった 。 /\n+<p>\n/iのように
1文字の英文字のために //i を使っていた
ところがあったが、展開して /\s+<[pP]>\s+/
としたら、気にならない速度で処理されるようになった。
また、短い文字列に対して//を何度も呼ぶときに、
処理時間がかかるように感じられた。長い文字列(10KBほど)を正規表現で
検索しても、それほど時間がかからなかった。正規表現のコンパイルに
時間がかかるのか?>Perl 5.8.8。
sprintf("%02d", $d);という書き方ももったいないので
($d < 10) ? "0" . $d : $d;という書き方にした。
ただし、処理速度に関して、この影響はあまりなかったもよう。
なんとなく、配列の添え字($arr[$i])も遅そうだったので、
配列のエレメント (foreach $element (@arr)) を
直接扱うようにした。
年月日の文字列を作ろうとして、
$y年$m月としたが、$y と "年" が連続した変数として
みなされたようで、$y が展開されなかった。そのため、
${y}年${m}月とした。UTF-8でも変数が記述できる弊害か?
どこが遅いか調べるために、
perl のスクリプト中に時刻を表示するデバッグ文を入れまくった。
いやはや。
|
2007年09月18日(火)
|
くもり
|
3/ 3
|
カテゴリ: Perl Regex
指定した正規表現パターン中での判断状況の状態遷移をグラフで
アニメーション表示するページがおもしろい 。
文字列をキーボードで入力すると、そのときのパターンマッチング
状態をグラフ図中で示してくれる。パターンマッチが失敗状態に
なったときは、入力した文字が赤くなり、これもわかりやすい。
グラフ図は非決定論的(non-deterministic)図と、決定論的 (deterministic)図
がある。図中では二重丸がマッチ完了の状態を表し、二重丸の円のどれかに
たどりつけば、パターンマッチ完成を意味する。それ以外の一重の円は
状態遷移の経過を表し、マッチは未完成だ。
正規表現 a*b|b*a の例では、最初に a を入力したとき、
非決定論的遷移図では、正規表現前半の a*b の a* にマッチした可能性が
青い一重円で表され、正規表現後半の b*a の a にマッチして
マッチ完了した可能性が二重丸の緑色の円で示されている。
この両方の可能性が考えられるため、非決定論的遷移図では、
a*b の途中経過にある場合と b*a にマッチした場合の両方を保持する、
ということになる。このように複数の状態を保持すると、状態遷移のベクトル検索が
複雑になり、計算量が多くなり、計算時間を予測しにくくなる。
一方、決定論的な遷移図では、複数の状態は持たず、常に状態を
一つの記述として、その分岐ベクトルは正規表現全体の評価から
派生させるようにしている。そのため、遷移図を作ると、各状態からの
ベクトルの本数を計算すれば計算量が予測できる。状態を保持する数も
1でよい。
このような説明は、たしかオライリーのPerl5の本に書いてあったような
気がする。読んだ当時は何を言っているのかよくわからなかったが、
このような図にして、例が示されると非常にわかりやすい。
|
2007年03月21日(水)
|
はれのちくもり
|
2/ 3
|
カテゴリ: Regex
|