perlのパターンマッチでAND条件を指定するには?

[上に] [前に] [次に]
ベン 2000/03/01(水) 13:30:00
パターンマッチで

if ($a =~ /abc|123/) {

はできるんですけど、

if ($a =~ /abc&123/) {

が失敗します。
一個の =~ /〜/ でAND条件のパターンマッチは無理なんでしょうか?

ラウォッチ 2000/03/01(水) 13:46:46
よく考えてみましょう〜
この場合の & を...。

ベン 2000/03/01(水) 13:52:11
??意味が分からないのですが・・・??
質問の仕方が悪かったでしょうか?
「一個の =~ /〜/ でAND条件のパターンマッチは無理なんでしょうか?」
上記の件について質問しています。

無責任官庁 2000/03/01(水) 14:02:42
え〜と。
要するに、"12345abc" と、"abcdef123"の、どちらも
引っかけたいって事ですかね。でしたら、

=~ /(123.*abc)|(abc.*123)/

こんな感じ?(未確認ですけど)
無理に“AND”にこだわる必要は無いかと。

EMI 2000/03/01(水) 14:27:32
どうも勘違いしてらっしゃる様ですが、正規表現中の"|"は論理演算子ではありません。
選択を意味する正規表現の記号です。
つまり、正規表現にはAND条件はもちろん、OR条件も存在しません。
("|"記号の働きはORっぽいですが)

ラウォッチ 2000/03/01(水) 14:45:36
ベンさん、ごめんなさい。
私がよく考えてなかったです〜 ^^;

if (($a =~ /abc/) && ($a =~ /123/)){

>無責任官庁さんの =~ /(123.*abc)|(abc.*123)/

のだと、$a 内に複数行あって各々別々の時って大丈夫?
(ちなみに、今、正規表現勉強中...。)

無責任官庁 2000/03/01(水) 15:23:44
う〜んと…
「何かやりたいコト」があって、それを実現する為に、
「ANDで指定できたらなぁ」って事だろうと思うんですけど。
だから、AND条件・OR条件ってのが根本的な問題ではないのでは?
で、正規表現については、EMIさんのおっしゃる通りです。
なので、ベンさんが「何かやりたいこと」の方を提示していただけると、
それを実現する方法が皆さんからもいくつか出てくるのではないでしょうか。

> $a 内に複数行あって各々別々の時って大丈夫?
そういうパターンでは多分だめだとは思いますけども、
ベンさんの「やりたいこと」が実現できるならば、
それはそれとして特に問題は無いかと思います。

ベン 2000/03/01(水) 15:33:34
すいません。やりたいことを具体的に書きます。

例えば
$data = "赤い高級な車";
があって、ユーザーは検索条件を

$condition = "(赤|青)&(車|テレビ)";
というフォーマットで入力します。(赤または青を含んでいて、なおかつ車かテレビを含んでいる)
これで$dataを引っかけたいんです。

$conditionを適当に変換して

if ($a =~ /$conditon2/) { ・・・
みたいなことができないかなぁ・・・と思いました。

EMI 2000/03/01(水) 15:49:33
つまり、平たく言うと正規表現を拡張して&記号を使えるようにしたいと言うことでしょうか?
独自の構文解析ルーチンを作る必要がありますからかなり難しいと思いますが‥‥‥

EMI 2000/03/01(水) 16:25:23
試作してみました。
ちゃんと動くかどうかはわかりませんが。

とりあえず、以下の制限があります。
・括弧の外に2つ以上の"&"をおくことができない。
・二重以上の括弧の内側に"&"をおくことができない。
・1つの括弧の中に2つ以上の"&"をおくことができない。
・"&"を無効化することができない。従って、文字クラス中に"&"を含めることもできない。

while($condition =~ /(\([^()]*&[^()]\))/){
  $from = $to = $1;
  $to =~ s/\((.*)&(.*)\)/((\1.*\2)|(\2.*\1))/;
  $condition s/$from/$to/;
}
$condition =~ s/^(.*)&(.*)$/($1.*$2)|($2.*$1)/;

if($a =~ /$condition/){...

EMI 2000/03/01(水) 16:33:32
あ、制限もう一つありました。
・括弧が入った括弧の中に"&"をおくことができない。

一応具体例を示すと、
・括弧の外に2つ以上の"&"をおくことができない。
  aaa&bbb&cccはNG。
・二重以上の括弧の中に"&"をおくことができない。
  ((aaa&bbb)|ccc)はNG。
・1つの括弧の中に2つ以上の"&"をおくことができない。
  (aaa&bbb&ccc)はNG。
・"&"を無効化することができない。従って、文字クラス中に"&"を含めることもできない。
  [!#%\&']*はNG。
・括弧が入った括弧の中に"&"をおくことができない。
  ((aaa|bbb)&ccc)はNG。

S-pore [HomePage] 2000/03/01(水) 17:22:16
EMIさんのように自前で解析するのもおもしろいのですが,
完璧にやろうとすると面倒なので,それがいやなら
evalで逃げるのも一つの手かもしれません。

たとえば,

$condition = "(ab&cd)|(ef&gh&(ij|k))";

とします。こいつに,

$condition =~ s/([\&\|])/ $1$1 /g;
$condition =~ s|([^\&\|\(\) ]+)|\$a \=\~ /$1/|g;

とかいう置換をかましてやると,$conditionの中身は

"($a =~ /ab/ && $a =~ /cd/) || ($a =~ /ef/ && $a =~ /gh/ && ($a =~ /ij/ || $a =~ /k/))"

になるので,あとは,

if (eval($condition)){・・・

で,$aがパターンにマッチするかどうか調べられると思います。たぶん。(を)

# evalってかなりこわいのでセキュリティーホールを作らないように十分注意してください。
# $condition に変な命令とか入れられるとやばそうなのでちゃんとチェック/置換しましょう。
# $condition =~ s|/|\\/|g; などなど・・・。

コウノトリ 2000/03/01(水) 18:06:00
S-poreさんと同じような方法で書いてみたんですけど、バグが1つとれない・・・
splitで ")&(" みたいにデリミターが連続してる部分を分割したときに、間に挟まって出てくる空文字のようなものは何なんでしょう?
こいつを除去できればベンさんの思ってることはできるのですが・・・

--
{
    $keyword = '(青|赤)&(テレビ|車)&(安い|高級)';
    $data = '赤い高級な車';
    $ret = searchString($data, $keyword);
    if ($ret) {
        print "OK\n";
    } else {
        print "NG\n";
    }
    exit;
}

sub searchString
{
    $result = '';
    local($data, $keyword) = @_;
    @tmp = split(/([\)\(&\|])/, $keyword);
    foreach (@tmp) {
        if ($_ eq "???") {    /* ここ!!! */
            next;
        }
        if ($_ eq '(' || $_ eq ')') {
            $result .= $_;
            next;
        }
        if ($_ eq '&') {
            $result .= '*';
            next;
        }
        if ($_ eq '|') {
            $result .= '+';
            next;
        }
        if (index($data, $_) >= 0) {
            $result .= '1';
        } else {
            $result .= '0';
        }
    }
    return eval($result);
}

コウノトリ 2000/03/01(水) 18:10:36
下のスクリプトを実行すると [] が出てくるのですが、"" ではないみたいなんです。これはなに??


    $keyword = '(青|赤)&(冷蔵庫|車)&(安い|高級)';
    @tmp = split(/([\)\(&\|])/, $keyword);
foreach (@tmp) {
        print "[$_]";
}
    print "\n";

S-pore [HomePage] 2000/03/01(水) 18:23:21
eq "" だと思いますけど・・・。(^^;

S-pore [HomePage] 2000/03/01(水) 18:26:09
$keyword = '(青|赤)&(冷蔵庫|車)&(安い|高級)';
@tmp = split(/([\)\(&\|])/, $keyword);
foreach (@tmp) {
  if ($_ eq ""){ print "[nazo]"; }
  else{ print "[$_]"; }
}
print "\n";

に変更して実行してみたら,

[nazo][(][青][|][赤][)][nazo][&][nazo][(][冷蔵庫][|][車][)][nazo][&][nazo][(][安い][|][高級][)]

とでましたが・・・。

コウノトリ 2000/03/01(水) 18:34:53
あれ??さっきダメだったのに、今やってみたらできた・・・(^^;
お騒がせしました・・・。

18:06:00の発言のプログラムの "???" → "" にすればOKですね。

正規初心者 2000/03/01(水) 18:43:38
あなた達、何者...。仕事してますか〜

EMI 2000/03/01(水) 18:46:43
何者かと問われれば、プログラマーと答えますが。
仕事は‥‥‥一応やってます、はい。(^^;

B-Cus 2000/03/01(水) 18:47:56
> コウノトリ 2000/03/01(水) 18:06:00
> S-poreさんと同じような方法で書いてみたんですけど、
プログラムの意図がわからんのですが…。

コウノトリ 2000/03/01(水) 19:08:04
>プログラムの意図がわからんのですが…。

すいません。
ようするに、論理式を使ってパターンマッチさせたいわけです(よね?>ベンさん)

$keyword にパターンマッチの条件式を入れて、$dataと一緒に searchStringをコールすると OK/NG で帰ってくるという関数です。

>if($a =~ /$condition/){...

でやりたいという意図からは完全に逸脱してしまいました。(^^;

EMI 2000/03/01(水) 19:09:35
???
あれ?splitに使ったデリミタって、結果のリストからは消滅しませんでしたっけ?
俺の勘違い?

B-Cus 2000/03/01(水) 19:14:55
> あれ?splitに使ったデリミタって
http://www.att.or.jp/perl/man/perlfunc.1.html
  split   文字列を文字列の配列に分割して、それを返します。
          PATTERN に括弧が含まれていると、デリミタ内の部分文字
          列にマッチするものも、配列要素に含まれるようになります。
                split(/([,-])/, "1-10,20");
          は、リスト値
                (1, '-', 10, ',', 20)
          を生成します。

> $keyword にパターンマッチの条件式を入れて、
> $dataと一緒に searchStringをコールすると OK/NG で
> 帰ってくるという関数です。
今のままでは動作しませんよね? $result に検索対象を追加してないので。
これって完成版ではちゃんと検索できるんですか?

S-pore [HomePage] 2000/03/01(水) 19:18:39
> あれ?splitに使ったデリミタって、結果のリストからは消滅しませんでしたっけ?

()でくるめば残ります。

いまのところコウノトリさんのが最適解っぽいですね。
これなら安全にevalを使えますし。
(私のやり方は,いろいろチェックしても穴が残りそうで恐いです^^;)

EMI 2000/03/01(水) 19:20:09
なるほど、理解できました。
ありがとうございます。>B-Cusさん

この例の場合だと、$resultの中身は以下のようになるわけですね。
"(0+1)*(0+1)*(0+1)"
ふ〜む、とりあえず納得しました。

B-Cus 2000/03/01(水) 19:31:03
> いまのところコウノトリさんのが最適解っぽいですね。
僕は大抵の場合 S-pore さんのがベストだと思います。
# 速度の改善については後程。

というか、コウノトリさんのは完成形が想像できないので、
なんとも言いようがないのですが、どなたか解説して
いただけませんか?

コウノトリ 2000/03/01(水) 19:38:07
>これって完成版ではちゃんと検索できるんですか?
あれ?できますよね??私のところではちゃんと動いてますが・・・
BBSの検索機能とかに応用できそうです。
ただ、Shift_JISで動くようにするのは厄介ですね。

EMI 2000/03/01(水) 19:40:50
B-Cusさん
つまり、こういうことみたいです。
()…そのまま出力
& …"*"に置き換え
| …"+"に置き換え
その他…indexで検索し、見つかれば1、見つからなければ0

これで、1と0を括弧でくるんだり+や*でつないだりした数式が文字列として帰ってくるので、これをevalに書ければ0か1(あるいはそれ以上)の数字が返って来るって寸法のようです。
後の課題は、文字クラスへの対応とメタ文字の無効化ですかね。(後は速度改善とか)

コウノトリ 2000/03/01(水) 19:43:56
解説

(1) $keyword を論理式に使う記号 '(' , ')' , '&' , '|' と、実際のキーワード に分解します。
(2) & は * に、| は + に置換します。
(3) キーワードは検索対象文字列に含まれていれば 1 なければ0に置換します。

この時点で、

$keyword = '(青|赤)&(テレビ|車)&(安い|高級)'
$data = '赤い高級な車'

に対して、

$result = "(0+1)*(0+1)*(0+1)"

が生成されます。
あとはevalして0でなければヒット。
こんな感じです。

EMI 2000/03/01(水) 19:45:45
まぁ、文字クラスへの対応は簡単にできますかね。
if($data =~ /$_/){
  $result .= '1';
}else{
  $result .= '0';
}
というか、むしろ何ではじめからこうしなかったのかが気になるんですが。
ただ、ブラケットの中にメタ文字が入ってると面倒なことになりそうですが。

EMI 2000/03/01(水) 19:53:01
文字クラス対応版を考えてみました。
@tmp = split(/(\[(.*?\\|)\]|[\)\(&\|])/, $keyword);

これで、ブラケットの中にメタ文字が含まれててもOKかな?
[a-z\\]などと指定されると困りそうですが。(^^;

EMI 2000/03/01(水) 19:56:44
間違えた。(^^;;;;;
@tmp = split(/(\[(.*?[^\\]|)\]|[\)\(&\|])/, $keyword);
ですね。

S-pore [HomePage] 2000/03/01(水) 20:01:03
コウノトリさんのやり方を参考にして,
私のやり方をセキュリティーホールができないように改良してみました。

$condition =~ s/[^\&\|\(\) ]+/1 + index($a, $&)/eg;
$condition =~ tr/\&\|/\*\+/;
if (eval($condition)){・・・

これでどうでしょう?
こうすれば,「*+() 0123456789」以外の文字をevalに渡すことは
なくなるので,安全だと思います。
(あとは括弧の対応エラーチェックとかをすればOK)

# C言語とかではこんな手抜きできないのでPerlはいいですね(^^;

B-Cus 2000/03/01(水) 20:03:49
> つまり、こういうことみたいです。
ああ、なるほど。$result に正規表現を生成するものだと
ばかり思い込んでいたもので、失礼しました。

さて、僕が S-pore さんのを推す理由ですが、要は速度です。
正規表現に何でも押し込むと、はっきり言って遅くなります。

9.4MB のテキストデータ (中身はファイルの一覧、18万行) を
(a&b)|(c&d) という条件で、検索時間を計測しました。結果は、

  EMIさんの正規表現生成
     00:24
  S-pore さんの m// を分ける方法
     01:46
  コウノトリさんの index
     02:01
となりました。

しかし S-pore さんのが遅いのは、ループごとに
eval しているからで、
   $eval_str = <<END;
   while (<IN>){
       if ( $condition ){
           print;
       }
   }
   END
   eval $eval_str;
と、ループを含む形で eval したら、4秒でした。

というわけで、S-pore さんのが最も高速で、他のを使う意味は
あまりないと思います。
# それぞれ、正しく検索できていることは確認済です。

> $condition に変な命令とか入れられるとやばそうなので
> ちゃんとチェック/置換しましょう。
正規表現を OFF にするなら、quotemeta で一括置換。正規表現を
許すなら、各々の検索文字列に対し、eval "m{$cond}" でエラー
チェック。もちろん事前に {} は \{\} に置換しておくこと、
ってなところでしょうか。
# 他に穴ありましたっけ? (何かあったような気がするんだよな)

EMI 2000/03/01(水) 20:13:26
どの方法を使っても、メタ文字が検索できないのは問題かもしれません。
たとえば、
R\&B|J-POP|ROCK
みたいなものを入力しても&が無効化されてくれないのでR&Bが検索できません。

EMI 2000/03/01(水) 20:18:12
あ、そうか。
コウノトリさんの方法に
@tmp = split(/(\[(.*?[^\\]|)\]|[\)\(&\|])/, $keyword);
をかませた場合、ブラケットの中のメタ文字が無効化されるから、
R[&]B|J-POP|ROCK
で、R&Bが検索できるかな?

B-Cus 2000/03/01(水) 20:21:12
> R\&B|J-POP|ROCK
それは入力を調べるとき (カッコが閉じているか、& や | が
先頭や末尾に来ていないかなど) にチェックすればいいと思います。
# いずれの方法でも入力チェックは必須なわけで。

例えば、正規表現の先読み言明 (だっけ?) で、& の前に
\ が来ているかどうかを調べる方法もあるでしょう。

コウノトリ 2000/03/01(水) 20:23:01
そうですね。私のは必ず全部のキーワードで舐めてますね。S-poreさんのは(a&b)|(c&d) の場合、aがヒットすればそこで終わりだから早いんですね。なるほど。

コウノトリ 2000/03/01(水) 20:26:07
じゃない、aとbだ・・・

EMI 2000/03/01(水) 20:27:22
うまくいきました。ブラケットで囲むことでメタ文字が無効化できます。(^_^)
これを、S-poreさんのやり方に応用すれば、早くてほぼ完璧なのができるのでは?
#ただし、[a-z\\]のように指定した場合におかしくなるのが欠点。(^^;

EMI 2000/03/01(水) 20:34:43
あ、テストしてる間にお二人の書き込みが‥‥‥

>それは入力を調べるとき (カッコが閉じているか、& や | が
>先頭や末尾に来ていないかなど) にチェックすればいいと思います。
># いずれの方法でも入力チェックは必須なわけで。
チェックしたとして、どう処理しましょう?
というか、入力チェックルーチンも作らなきゃならないわけですね。(^^;

S-pore [HomePage] 2000/03/01(水) 21:15:49
速度の点でいくと,私の改良版の方(2000/03/01(水) 20:01:03)は,
もとの(2000/03/01(水) 17:22:16)と比べると遅くなるでしょうね。
eオプションでevalしまくるわけなので・・・。
でも私はeval恐怖症(笑)なので,(2000/03/01(水) 20:01:03)みたいに
evalに渡す文字を明確に制限しないと安心して使えません。(^^;

入力チェックですが,evalは,渡された文字列の評価中にエラーが発生するとundefを返すので,
たとえば私の改良版の方をちょっと変更して,

$condition =~ s/[^\&\|\(\) ]+/1 + index($a, $&)/eg;
$condition =~ tr/\&\|/\*\+/;
if (($result = eval($condition)) eq ''){ print "エラー:conditionが謎。"; }
elsif ($result){ print "マッチしました。"; }
else{ print "マッチしませんでした。"; }

としてはどうでしょう?
これで括弧の不対応や「|&」の連続などが一気に検挙されます。(笑)

別に,
if (($condition =~ tr/\(/\(/) != ($condition =~ tr/\)/\)/))
{ print "括弧がおかしいです。"; }
みたいに一個一個チェックしていってもいいと思いますが,
私がPerlで書くときは,いつも手を抜くことしか頭にありません。(笑)
# 普段C言語を使っているのでよけいに(^^;

あと,メタ文字やら文字クラスやらは,質問者さんがそういう処理を必要とするなら
適当に改造して使うでしょうし,
$condition = "(赤|青)&(車|テレビ)";
という例を見る限りではそういう処理は必要としていないようにも見えるので,
とりあえず質問に対する回答としてはこの時点で十分なのではないかと。(^^;

# 今日はこれでいい暇つぶしになりました。(謎)

andi 2000/03/02(木) 00:49:13
if(/(?=.*abc)(?=.*123/)
ってだめですか?
Perl4だとダメみたいですが。

とほほ 2000/03/02(木) 01:00:41
すみませんが、関係者同士でメールでやりとりして、その結果だけを
報告していただけませんでしょうか?

B-Cus 2000/03/02(木) 01:18:01
Web 作成に関してやりとりする場を提供してくれるのなら、
そのお礼として (コミュニティに) フィードバックしますけど、
長くなるなら他で議論してくれ、というならここを使う意味が
ないですね。

それに、公開して話すからいろいろな人が発言してくれるわけで、
それをメールという閉鎖した場でやっては全く意味がない。

「あのときメールに移行してればログが短くて済んだのに…」と
いうのは、議論が収束した今だから言える結果論でしょう。
メールに移行したおかげで、どこからともなく詳しい人が現れて、
正解を教えてくれるという機会を逃すかもしれない。

ログが肥大化する、ということなら、
 - wwwlng のトップページにアクセスするたびに毎回全ファイルを
  なめるという仕様
 - 激遅な wwwsrch の実装
を まずは修正すべきでは。

[上に] [前に] [次に]