Go で AND, OR 条件付きのテキストマッチを行う
AND, OR 条件付きでテキストがマッチしてるか確認する関数を作る。
Perl や PHP 等の PCRE をサポートしてる正規表現を使う場合は、こんな感じでいい。
/(?=.*(hello|world))(?=.*(foo|bar))/
こうすると
hello, foo hello, bar world, foo world, bar
といったそれぞれの文字列にマッチする。「hello または world」かつ「foo または bar」にマッチするかどうかという関数を作りたい。
それで、この正規表現を組み立てるようなコードを書いてみたけど、Go ではこの (?=re)
といった肯定先読みの表現がサポートされておらず、困ってしまった。
そこで、自力でロジックを書くことにしたが...
全然ロジックが思いつかず、ついにはご飯を食べてしまった。そしたら思いついた。
まずはこんな感じのスライスのスライスを用意する。
dicts := [][]string{ []string{ "hello", "world", }, []string{ "foo", "bar", }, }
これで表現しているのは 「hello または world」かつ「foo または bar」である。
この辞書を基に単語がマッチするか確認する関数がこんな感じ。
func ContainsWord(dicts [][]string, field string) bool { matchCount := len(dicts) if matchCount == 0 { return false } for _, dict := range dicts { for _, v := range dict { if strings.Contains(field, v) { matchCount-- break } } } return matchCount == 0 }
https://play.golang.org/p/rEPO-xykZ9_L
この関数がやってることは
- マッチすべき個数(スライスの個数。上では 2 個)を取得する
- 辞書が空っぽであれば false を返す
- 「A または B」がマッチすれば、カウンタを 1 つ減らし、マッチすべき個数へ近づけていく。次の「C または D」を確認する...というのを繰り返す
- マッチすべき個数、マッチされてれば true を返す
とてもシンプル。最初は難しく考えすぎていて、AND, OR の構文木を struct で表現しようかと思ってた...
追記
sters さんからこうするともっと早くなると教えてもらった。
for _, dict := range dicts { found := false for _, v := range dict { if strings.Contains(field, v) { found = true break } } if !found { return false } } return true
私が書いたコードだと AND 条件をすべて探索するが、"A AND B" の A にマッチしない場合は即座に false を返すようになっている。