[groonga-dev,03820] Re: rroonga / 複数キーワードによる検索

Back to archive index

Kouhei Sutou kou****@clear*****
2015年 12月 30日 (水) 12:38:23 JST


須藤です。

In <AD096****@mva*****>
  "[groonga-dev,03817] rroonga / 複数キーワードによる検索" on Tue, 29 Dec 2015 22:15:58 +0900,
  Masatoshi SEKI <m_seki****@mva*****> wrote:

> rroongaで複数のキーワードからなる検索を書く方法を調べていて次のblogを読みました。
> 
> http://www.clear-code.com/blog/2009/7/31.html
> 
> injectとexpressionを使って次のようなコード片が出ています。
> 
> records = documents.select do |record|
>   words.inject(nil) do |expression, word|
>     sub_expression = record["content"] =~ word
>     if expression.nil?
>       sub_expression
>     else
>       expression & sub_expression
>     end
>   end
> end
> 
> 
> 
> sub_expressionのクラスはExpressionBuilderでしたっけ?

はい、そんな感じのやつです。

> これの & や | などがnilを引数に取るとき自分自身を返すようにしたら
> 次のように書けないでしょうか?
> 
> records = documents.select do |record|
>   words.inject(nil) do |expression, word|
>     (record["content"] =~ word) & expression
>   end
> end
> 
> &,|の対称性がなくなるのがイマイチならメソッド名を変えてもかまいませんし、
> nilが不自然ならExpressionBuilder::NopとかSentinelとかTailとかでも…
> 
> 
> 
> 複数キーワードの検索はよくありそうな処理なので、もっと簡単に書く方法はありますか?

これはですね、selectで条件が入った配列を返す書き方があるので、
それを使うのがよいのです。(たぶん、この記事を書いた後に入っ
た機能です。)

コードにするとこんな感じです。

records = documents.select do |record|
  words.collect do |word|
    record["content"] =~ word
  end
end

その後、カラムにはメソッドでもアクセスできるようになったので、
Hashっぽくじゃなく、オブジェクトっぽく、こうも書けます。

records = documents.select do |record|
  words.collect do |word|
    record.content =~ word
  end
end

また、通常は自分でクエリーをパースして単語に直したくはないと
思うので、Groongaにクエリーをパースしてもらうのがおすすめで
す。

query = "dRuby Rinda" # 「dRuby」と「Rinda」を両方含んでいればマッチ
records = documents.select do |record|
  record.content.match(query)
end

queryにユーザーからの入力をそのまま入れるならシンタックスエ
ラーも考慮します。Groongaにパースしてもらうと「(A OR B) C」
のような書き方もできるようになりますが、「(A」とかはエラーに
なってしまうのです。

records = documents.select do |record|
  conditions = []
  begin
    conditions << record.content.match(query)
  rescue Groonga::SyntaxError
    p $!
  end
  conditions
end

デフォルトではGoogleで使えるような「カラム:クエリー」という
シンタックスも使えて、「name:dRuby」のようにすると
「record.content.match」としていても「record.name =~ "dRuby"」
のようなクエリーを書けるのですが、必要のないカラムまで検索さ
れると困る場合はそれを無効にできます。

records = documents.select do |record|
  record.content.match(query, :allow_column => false)
end


まとめると、こんな感じがいいんじゃないかと思います。

records = documents.select do |record|
  conditions = []
  begin
    conditions << record.content.match(query, :allow_column => false)
  rescue Groonga::SyntaxError
    p $!
  end
  conditions
end


-- 
須藤 功平 <kou****@clear*****>
株式会社クリアコード <http://www.clear-code.com/>

Groongaベースの全文検索システムを総合サポート:
  http://groonga.org/ja/support/
パッチ採用 - プログラミングが楽しい人向けの採用プロセス:
  http://www.clear-code.com/recruitment/
リーダブルコードワークショップ:
  http://www.clear-code.com/services/code-reader/readable-code-workshop.html




groonga-dev メーリングリストの案内
Back to archive index