iOSアプリで全文検索
iOSアプリで日本語の全文検索したいこと、ありますよね。私はあります。
そもそもiOSアプリ内で日本語の全文検索をするという需要がないみたいで、情報が少なかったのでまとめました。
SQLiteの全文検索拡張
実はSQLiteにはFTS3 and FTS4 extensionsという全文検索拡張が存在します。
以前は、iOS SDKに含まれているSQLiteライブラリで無効だったため、自前でビルドする必要があったのですが、 iOS 6 SDKからはデフォルトで有効です。
基本的なFTSの使い方
テーブル
CREATE VIRTUAL TABLE article USING fts4 (title, body);
完全一致、前方一致検索を高速にできます。
INSERT INTO article(title, body) VALUES ('Apple', 'I have iPhone4 and iPhone5'); SELECT * FROM article WHERE body MATCH 'iPhone5'; SELECT * FROM article WHERE body MATCH 'iPhone* ';
ただし、日本語の場合、デフォルトで使用可能なtokenizerは日本語をうまくtokenに分けることができません。*1
そのため、tokenizerが理解できる形式として、あらかじめ半角スペースでtokenに分けたデータを挿入することで対応します。
テーブル構造
CREATE TABLE article (id, title, body); CREATE VIRTUAL TABLE articleTokens USING fts4 (articleId, bodyTokens);
元データを保持するテーブルと、token分割したデータを保持するテーブルを分けます。
INSERT
INSERTしたいのはこんなデータ
INSERT INTO article(id, title, body) VALUES (1, 'アップル', '私はiPhone4とiPhone5を持っています'); INSERT INTO articleTokens(articleId, bodyTokens) VALUES (1, '私は iPhone4 と iPhone5 を 持っています');
これを目標にSQLを組み立てます。
トークン分割
tokenに分けるのはCFStringTokenizerを使用します。
トークン分割メソッド
- (NSArray *)tokenArrayWithString:(NSString *)string { NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"ja"]; CFRange range = CFRangeMake(0, CFStringGetLength((CFStringRef)string)); CFStringTokenizerRef tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, (CFStringRef)string, range, kCFStringTokenizerUnitWordBoundary, (CFLocaleRef)locale); NSMutableArray *tokenArray = [NSMutableArray array]; while(CFStringTokenizerAdvanceToNextToken(tokenizer) != kCFStringTokenizerTokenNone) { CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer); if(range.location != kCFNotFound) { NSString *token = [string substringWithRange:NSMakeRange(tokenRange.location, tokenRange.length)]; [tokenArray addObject:token]; } } CFRelease(tokenizer); return tokenArray; }
得られたNSArrayを半角スペースで連結して使用します。
[tokenArray componentsJoinedByString:@" "];
SELECT
SELECT * FROM article JOIN ( SELECT articleId FROM articleTokens WHERE bodyTokens MATCH 'iPhone*' ) AS result ON article.id = result.articleId
こんな感じでデータが取得できます。
まとめ
SQLiteのFTSを使用して、日本語で全文検索を使用する方法を紹介しました。
日本語の場合、CFStringTokenizerを使用してINSERTするデータ自体を分割してしまうことによって、 わりと簡単に全文検索を使用する事ができます。
さらにranking用関数を作成して、マッチした順にデータを返すことも可能です。
CoreDataにも不向きな用途があるので、パフォーマンスが求められる部分にはSQLiteを使うのも良いと思います。
参考リンク
*1:icu tokenizerはiOS 7 SDKに含まれるライブラリでも使用できませんでした。