67
Sapporo.cpp 第8回勉強会(2014.12.27) その文字列検索、 std::string::findだけで 大丈夫ですか? H.Hiro Twitter: @h_hiro_ http://hhiro.net/about/

その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

  • Upload
    hiro-h

  • View
    503

  • Download
    3

Embed Size (px)

Citation preview

Page 1: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

Sapporo.cpp 第8回勉強会(2014.12.27)

その文字列検索、std::string::findだけで

大丈夫ですか?

H.HiroTwitter: @h_hiro_

http://hhiro.net/about/

Page 2: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

自己紹介

Page 3: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

H.Hiro●情報系の研究員やってます

●趣味でもプログラム書いてます●でも最近は趣味ではあまりプログラム書けてないのです

Page 4: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

告知

第35回 北海道開発オフ●みんなで集まって、だけど思い思いに開発したり勉強したり

●でもはかどるんです●1月17日(土) 9:00~16:00http://devdo.doorkeeper.jp/

Page 5: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

よろしくお願いします

Page 6: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

今回話す内容

Page 7: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

文字列を検索する

Page 8: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

C++ Advent Calendar 2014に書いた記事の拡大版です

http://qiita.com/h_hiro_/items/dcad2e2eddcb42671d9d

Page 9: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

具体的には

BANNANABANANAN

BANANApattern:text:

"テキスト"から"パターン"が出現する場所を見つけたい

Page 10: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

具体的には

BANNANABANANAN

BANANApattern:text:

今の場合だとここが出現位置。(0起点での)7文字目から始まる場所

Page 11: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

文字列データに対する

最も基本的な処理の一つ

Page 12: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

今回のテーマ

Page 13: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

●C++には、標準でstd::stringにfind関数があって文字列検索が行える

●ただ、それは非常に素朴な方法●文字数が増えても、(ある程度)高速に検索したい

Page 14: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

実際、文字数が増えると

「高速に検索できる」ことの価値が上がる

Page 15: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

Web検索エンジンはその最たる例

Page 16: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

今回は、そんなバリバリの実装の話は

しませんが

Page 17: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

何に注目して高速化を図っているのかという

アイデアを紹介します

Page 18: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

予告しておくと(1) パターン前処理型(2) 索引型

Page 19: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

では、最初に基本となる検索

Page 20: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

基本的な文字列検索

std::string::find

Page 21: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

std::string::findの使い方

std::string text = "BANNANABANANAN";std::string pattern = "BANANA";

text.find(pattern);

// "7"を返す

Page 22: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

std::string::findの検索手順

BANNANABANANAN

BANANApattern:text:

まず、パターンを左端に合わせて

Page 23: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

std::string::findの検索手順

BANNANABANANAN

BANANApattern:text:

まず、パターンを左端に合わせてパターンの末尾まで一致しているか調べる

Page 24: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

std::string::findの検索手順

BANNANABANANAN

BANANApattern:text:

一致していない文字が一つでもあれば、左端を一つずらし

Page 25: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

std::string::findの検索手順

BANNANABANANAN

BANANApattern:text:

一致していない文字が一つでもあれば、左端を一つずらし同様に調べていく

Page 26: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

std::string::findの検索手順

BANNANABANANAN

BANANApattern:text:

全部一致している箇所が見つかったら、それを結果として出力する

Page 27: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

まとめるとこんな具合になるtext BANNANABANANAN

pattern BANA B B ※赤文字: B 間違っていた文字 B B B BANANA

Page 28: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

まとめるとこんな具合になるtext BANNANABANANAN

pattern BANA B B B B B B BANANA

判定する起点(左端)が1文字ずつ動いている

Page 29: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

まとめるとこんな具合になるtext BANNANABANANAN

pattern BANA B B B B B B BANANA

→もっと多い文字数動かせるか?

判定する起点(左端)が1文字ずつ動いている

Page 30: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

高速化の手段(1)パターンを前処理する

Page 31: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

代表的なものが二つあるので

うち一つを紹介します

Page 32: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

前処理つきの検索(Knuth-Morris-Pratt)

text BANNANABANANAN

pattern BANANA

まず、パターンを全部見て、パターンの先頭から■文字が

パターンの他の位置にも出現するか調べる●BANANA → ■にかかわらず出現しない●CACAO→ ■が1か2なら、3文字目に出現する→ ■が3以上なら、出現しない

Page 33: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

前処理つきの検索(Knuth-Morris-Pratt)

text BANNANABANANAN

pattern BANA

さて、さっきと同様“A”が違っていたことが

わかったときに

Page 34: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

前処理つきの検索(Knuth-Morris-Pratt)

text BANNANABANANAN

pattern BANA B

さっきの例では左端を一つずらして

検索を再開していたのだが

Page 35: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

前処理つきの検索(Knuth-Morris-Pratt)

text BANNANABANANAN

pattern BANA B

パターン中に“B”が先頭以外にはないことを事前に調べていれば、

次に調べ始める場所は、ここまで動かせる。→左端を1文字よりも大きく動かせた!

Page 36: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

前処理つきの検索(Knuth-Morris-Pratt)

text BANNANABANANAN

pattern BANA B B B B BANANA

Page 37: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

パターンを前処理する検索Knuth-Morris-Pratt●パターンの先頭と同じ文字列が、パターンの別の位置に出現するかを利用例:BANBAABAN

●最悪時間計算量は低いが、実用上はBMがより高速

Boyer-Moore●パターンとテキストの文字が一致していなかったとき、パターンをテキスト側の文字に合わせる

●詳しくはQiitaの記事を

Page 38: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

使ってみる

Page 39: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

これらの検索アルゴリズムはBoostに入っている●boost::algorithm::knuth_morris_pratt(パターンを前処理した結果のクラス)とかboost::algorithm::knuth_morris_pratt_search(単に検索を1回行うための関数)とか

●ここにコード貼っても長くなりすぎるのでQiitaの記事中のサンプルをご覧ください

Page 40: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

注意点(1)

Page 41: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

パターンを時間をかけて

前処理するのだから

Page 42: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

パターンがある程度長いときに効果を発揮する●逆に、短いときは逆効果だったり●パターンの長さが100くらいだと単にfindしたほうが速かったhttp://qiita.com/h_hiro_/items/dcad2e2eddcb42671d9d#%E5%AE%9F%E9%A8%93

Page 43: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

注意点(2)

Page 44: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

ここまでパターンを前処理して

がんばってきたわけだけど

Page 45: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

どちらにせよ計算時間を決める

最大の要素が

Page 46: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

どちらにせよ計算時間を決める

最大の要素がテキストの大きさ

Page 47: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

どちらにせよ計算時間を決める

最大の要素がテキストの大きさ

→大規模DBには厳しい

Page 48: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

それなら、前処理が必要なのは

Page 49: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

それなら、前処理が必要なのはパターンよりもむしろ

テキストだ!

Page 50: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

高速化の手段(2)索引を付与する

(テキストを前処理)

Page 51: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

索引の方式1:単語ごとに保存して候補を絞り込む1.C++11がようやく出た。2.C++11が出たと思ったらもうC++14が出る。3.C++17はすぐ出るんだろうか。

単語 出現した文章のID

C++ 1, 2, 3

出た 1, 2

出る 2, 3

単語 出現した文章のID

11 1, 2

14 2

17 3

“inverted index” (転置インデックス)と呼ばれる

Page 52: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

索引の方式1:単語ごとに保存して候補を絞り込む1.C++11がようやく出た。2.C++11が出たと思ったらもうC++14が出る。3.C++17はすぐ出るんだろうか。

単語 出現した文章のID

C++ 1, 2, 3

出た 1, 2

出る 2, 3

単語 出現した文章のID

11 1, 2

14 2

17 3

「C++11が出た」を検索する場合、

Page 53: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

索引の方式1:単語ごとに保存して候補を絞り込む1.C++11がようやく出た。2.C++11が出たと思ったらもうC++14が出る。3.C++17はすぐ出るんだろうか。単語 出現した文章のID

C++ 1, 2, 3

出た 1, 2

出る 2, 3

単語 出現した文章のID

11 1, 2

14 2

17 3

IDだけに注目すると、3は候補から外れることがわかる!

Page 54: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

索引の方式1:単語ごとに保存する利点:単語単位に区切っているので意図した結果が出やすい欠点:単語の区切りに沿わないものを抽出できない

Page 55: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

欠点:単語の区切りに沿わないものを抽出できない

→対応したければ 「すべての部分文字列」を 索引に格納するようにする

Page 56: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

索引の方式2:すべての部分文字列を保存するC++11が出たと思ったらもうC++14が出る。

(1文字目が起点の部分文字列)“C”, “C+”, “C++”, “C++1”, “C++11”, ...(2文字目が起点の部分文字列)“+”, “++”, “++1”, “++11”, “++11が”, ...:

Page 57: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

メモリ使いすぎない?

Page 58: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

実際はかなり節約できます。

Page 59: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

索引の方式2:すべての部分文字列を保存する1 2 3 4 5 6

P E O P L E

P

E

O

P

L

E

E

L

E

O

P

L

O

P

L

Suffix tree:●完全に木構造ですべての部分文字列を格納

●検索は超高速(木を順に辿るだけ)

●ただしメモリはものすごく食う(ポインタを文字数×5以上は使う)

E

E

Page 60: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

索引の方式2:すべての部分文字列を保存する1 2 3 4 5 6

P E O P L E Suffix array:●辞書順で並べて左端の配列だけ保存

●容量は小さめ(文字列長×ポインタサイズ)

●ただし、suffix treeに比べると検索のオーバーヘッドが大きい

6 E

2 E O P L E

5 L E

3 O P L E

1 P E O P L E

4 P L E

Page 61: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

注意点

Page 62: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

前半(パターンの前処理)のときに言ったこと

パターンを時間をかけて前処理するのだから●パターンがある程度長いときに効果を発揮する

●逆に、短いときは逆効果だったり

Page 63: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

テキストの前処理だと

テキストを時間をかけて前処理するのだから●テキストがある程度長いときに効果を発揮する

●逆に、短いときは逆効果だったり

Page 64: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

テキストは、パターンに比べるととてつもなく長いことも多い

(データベース使って文書を格納してるとか)

前処理の時間がばかにならない!

Page 65: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

●テキストが頻繁に更新される場合にはあまり向かない(テキストエディタ内の検索など)

●索引を作るとすれば、相応の計算量が必要

●それ以上に検索の高速化の意義がある応用に使われる(文書DB検索など)

Page 66: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

おわりに

Page 67: その文字列検索、std::string::findだけで大丈夫ですか?【Sapporo.cpp 第8回勉強会(2014.12.27)】

普段はstd::string::findのようにシンプルに検索してもいいけど●パターンを前処理●テキストを前処理も必要に応じて使おう!