ClojureでStringSearchを使う

Clojureからあいまい検索が可能なJavaライブラリを使いたい

「自分で実装してもいいけど、まあ誰か作っているだろう」という思いから見つけた記事がこれ↓

高速な文字列検索を実現するJavaライブラリ「StringSearch」
http://news.mynavi.jp/column/tool/044/

どうやらやりたいことはバッチリできそう。そして、ページを進むとサンプルコードまで書いてる優しさ。

私は決めました。Clojureからこれを使おうと。

本題

project.clj を編集

ありがたいことに、Clojars に StringSearch 1.2 があるので準備は簡単。
project.cljdependenciesを追加するだけ

https://clojars.org/org.clojars.nmurray.com.eaio/stringsearch

project.clj

:dependencies [[org.clojure/clojure "1.6.0"]
               [org.clojars.nmurray.com.eaio/stringsearch "1.2"]]

追加したあとは、lein depsとかでライブラリのダウンロードを行ってください。

使ってみた

1からStringSearchのドキュメントを読むのもしんどかったので、先ほど記事のJavaコードをClojureに変換してみました。

以下のコードは参考程度に見てください。もっといい書き方がたくさんあるはず...
特にloopを含んでるものは、元のJavaコードよりも変数が増えてしまっていてかなり残念...

; http://news.mynavi.jp/column/tool/045/
; list 1

(def text (str "こんにちは世界。新世界へようこそ。"))
(def pattern (str "世界"))

(let [search (com.eaio.stringsearch.BoyerMooreHorspoolRaita.)]
  (println  (str (inc (.searchString search text pattern)) "文字目")))
;-> 6文字目
; http://news.mynavi.jp/column/tool/045/
; list 2

(def text (str "こんにちは世界。新世界へようこそ。"))
(def pattern (str "世界"))

(let [search (com.eaio.stringsearch.BoyerMooreHorspoolRaita.)
      processed (.processString search pattern)]
  (loop [x -1]
    (let [y (.searchString search text (inc x) pattern processed)]
      (if (not (= y -1))
        (do 
          (println (str (inc y) "文字目"))
          (recur y))))))
; ->
; 6文字目
; 10文字目
; nil
; http://news.mynavi.jp/column/tool/045/
; list 3

(def text (str "こんにちは世界。新世紀へようこそ。"))
(def pattern (str "世?"))

(let [search (com.eaio.stringsearch.BNDMWildcards.)
      processed (.processString search pattern)]
  (loop [x -1]
    (let [y (.searchString search text (inc x) pattern processed)]
      (if (not (= y -1))
        (do 
          (println (str (inc y) "文字目"))
          (recur y))))))
; ->
; 6文字目
; 10文字目
; nil
; http://news.mynavi.jp/column/tool/046/
; list 1

(def text "Java, java, kava, Kava, sava, ^ava")
(def pattern "[jJ]ava")

(let [search (com.eaio.stringsearch.ShiftOrClasses.)
      processed (.processString search pattern)]
  (loop [x -1]
    (let [y (.searchString search text (inc x) pattern processed)]
      (if (not (= y -1))
        (do 
          (println (str (inc y) "文字目"))
          (recur y))))))
; ->
; 1文字目
; 7文字目
; nil
; http://news.mynavi.jp/column/tool/046/
; list 3

(def pattern "Java")
(def text "Hello KaVa")
; (def text "Hello Java")
; (def text "Hello java")
; (def text "Hello JAVA")

(let [search (com.eaio.stringsearch.ShiftOrMismatches.)
      processed (.processString search pattern 2)]
  (let [x (vec (.searchString search text pattern processed 2))]
    (println (str (inc (first x)) "文字目"))
    (println (str "    " (second x) "文字のエラー"))))

おまけ1: import でオブジェクト生成を短く書く

(let [search (com.eaio.stringsearch.BoyerMooreHorspool.)]
  (println  (str (inc (.searchString search text pattern)) "文字目")))

com.eaio.stringsearch.BoyerMooreHorspool.って長いですよね...

Java同様、importで短くしましょう。

(import '(com.eaio.stringsearch))

(let [search (BoyerMooreHorspool.)]
  (println  (str (inc (.searchString search text pattern)) "文字目")))

または

(ns yournamespace
  (:import (com.eaio.stringsearch)))

(let [search (BoyerMooreHorspool.)]
  (println  (str (inc (.searchString search text pattern)) "文字目")))

おまけ2: 公式のドキュメントはどこに?

こちらが公式
http://johannburkard.de/software/stringsearch/

しかし、Doocumentation のページを辿っても Usage 的なものはないので…

StringSearch 1.2 tar.gz をダウンロード・展開した先にあるindex.htmlを読むのがおすすめ。(Javaだけど)

stringsearch-1.2/documentation/index.html

おまけ3: 日本語のあいまい検索がうまくいかない!

残念ながら、できません。

ShiftOrMismatches だけ日本語のようなマルチバイトに対応してません...

詳細は "おまけ2" でダウンロードしたファイルの中に書いてあります。

stringsearch-1.2/documentation/usage.html

感想

  • JavaのライブラリをClojureから呼び出す勉強になった!

  • ClojureJavaと比べてオブジェクト生成時のコードが短くて素敵!(ほぼ誤差)

    Java
    StringSearch search = new BoyerMooreHorspoolRaita();

    Clojure
    let [search (BoyerMooreHorspool.)]

  • Javaのライブラリを呼ぶと全然短くならない!(たぶん事実)

  • fireplace.vim で補完が効かないので、APIドキュメントを参照しないとtypoする!