Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

2,028 views

Published on

Sapporo.cpp 第8回勉強会(2014.12.27)発表内容

Published in: Technology
  • Be the first to comment

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

  1. 1. Sapporo.cpp 第8回勉強会(2014.12.27) その文字列検索、 std::string::findだけで 大丈夫ですか? H.Hiro Twitter: @h_hiro_ http://hhiro.net/about/
  2. 2. 自己紹介
  3. 3. H.Hiro ● 情報系の研究員 やってます ● 趣味でもプログラム書いてます ● でも最近は趣味ではあまり プログラム書けてないのです
  4. 4. 告知 第35回 北海道開発オフ ● みんなで集まって、だけど 思い思いに開発したり勉強したり ● でもはかどるんです ● 1月17日(土) 9:00~16:00 http://devdo.doorkeeper.jp/
  5. 5. よろしく お願いします
  6. 6. 今回話す内容
  7. 7. 文字列を 検索する
  8. 8. C++ Advent Calendar 2014に 書いた記事の拡大版です http://qiita.com/h_hiro_/ items/dcad2e2eddcb42671d9d
  9. 9. 具体的には BANNANABANANAN BANANApattern: text: "テキスト"から"パターン"が 出現する場所を見つけたい
  10. 10. 具体的には BANNANABANANAN BANANApattern: text: 今の場合だとここが出現位置。 (0起点での)7文字目から 始まる場所
  11. 11. 文字列データに 対する 最も基本的な 処理の一つ
  12. 12. 今回のテーマ
  13. 13. ● C++には、標準で std::stringにfind関数があって 文字列検索が行える ● ただ、それは非常に素朴な方法 ● 文字数が増えても、(ある程度) 高速に検索したい
  14. 14. 実際、 文字数が増えると 「高速に検索できる」 ことの価値が上がる
  15. 15. Web検索エンジンは その最たる例
  16. 16. 今回は、そんな バリバリの実装の話は しませんが
  17. 17. 何に注目して 高速化を図って いるのかという アイデアを紹介します
  18. 18. 予告しておくと (1) パターン前処理型 (2) 索引型
  19. 19. では、最初に 基本となる検索
  20. 20. 基本的な 文字列検索 std::string::find
  21. 21. std::string::findの使い方 std::string text = "BANNANABANANAN"; std::string pattern = "BANANA"; text.find(pattern); // "7"を返す
  22. 22. std::string::findの検索手順 BANNANABANANAN BANANApattern: text: まず、パターンを左端に合わせて
  23. 23. std::string::findの検索手順 BANNANABANANAN BANANApattern: text: まず、パターンを左端に合わせて パターンの末尾まで一致して いるか調べる
  24. 24. std::string::findの検索手順 BANNANABANANAN BANANApattern: text: 一致していない文字が一つでも あれば、左端を一つずらし
  25. 25. std::string::findの検索手順 BANNANABANANAN BANANApattern: text: 一致していない文字が一つでも あれば、左端を一つずらし 同様に調べていく
  26. 26. std::string::findの検索手順 BANNANABANANAN BANANApattern: text: 全部一致している箇所が 見つかったら、それを結果として 出力する
  27. 27. まとめるとこんな具合になる text BANNANABANANAN pattern BANA B B ※赤文字: B 間違っていた文字 B B B BANANA
  28. 28. まとめるとこんな具合になる text BANNANABANANAN pattern BANA B B B B B B BANANA 判定する起点(左端)が 1文字ずつ動いている
  29. 29. まとめるとこんな具合になる text BANNANABANANAN pattern BANA B B B B B B BANANA →もっと多い文字数 動かせるか? 判定する起点(左端)が 1文字ずつ動いている
  30. 30. 高速化の手段(1) パターンを前処理する
  31. 31. 代表的なものが 二つあるので うち一つを紹介します
  32. 32. 前処理つきの検索(Knuth-Morris-Pratt) text BANNANABANANAN pattern BANANA まず、パターンを全部見て、 パターンの先頭から■文字が パターンの他の位置にも出現するか調べる ● BANANA → ■にかかわらず出現しない ● CACAO → ■が1か2なら、3文字目に出現する → ■が3以上なら、出現しない
  33. 33. 前処理つきの検索(Knuth-Morris-Pratt) text BANNANABANANAN pattern BANA さて、さっきと同様 “A”が違っていたことが わかったときに
  34. 34. 前処理つきの検索(Knuth-Morris-Pratt) text BANNANABANANAN pattern BANA B さっきの例では 左端を一つずらして 検索を再開していたのだが
  35. 35. 前処理つきの検索(Knuth-Morris-Pratt) text BANNANABANANAN pattern BANA B パターン中に“B”が先頭以外には ないことを事前に調べていれば、 次に調べ始める場所は、ここまで動かせる。 →左端を1文字よりも大きく動かせた!
  36. 36. 前処理つきの検索(Knuth-Morris-Pratt) text BANNANABANANAN pattern BANA B B B B BANANA
  37. 37. パターンを前処理する検索 Knuth-Morris-Pratt ● パターンの先頭と同じ文字列が、パターンの 別の位置に出現するかを利用 例:BANBAABAN ● 最悪時間計算量は低いが、実用上はBMがより高速 Boyer-Moore ● パターンとテキストの文字が一致していなかった とき、パターンをテキスト側の文字に合わせる ● 詳しくはQiitaの記事を
  38. 38. 使ってみる
  39. 39. これらの検索アルゴリズムは Boostに入っている ● boost::algorithm::knuth_morris_pratt (パターンを前処理した結果のクラス)とか boost::algorithm::knuth_morris_pratt_search (単に検索を1回行うための関数)とか ● ここにコード貼っても長くなりすぎるので Qiitaの記事中のサンプルをご覧ください
  40. 40. 注意点(1)
  41. 41. パターンを 時間をかけて 前処理するのだから
  42. 42. パターンがある程度 長いときに効果を発揮する ● 逆に、短いときは逆効果だったり ● パターンの長さが100くらいだと 単にfindしたほうが速かった http://qiita.com/h_hiro_/items/ dcad2e2eddcb42671d9d #%E5%AE%9F%E9%A8%93
  43. 43. 注意点(2)
  44. 44. ここまで パターンを前処理して がんばって きたわけだけど
  45. 45. どちらにせよ 計算時間を決める 最大の要素が
  46. 46. どちらにせよ 計算時間を決める 最大の要素が テキストの大きさ
  47. 47. どちらにせよ 計算時間を決める 最大の要素が テキストの大きさ →大規模DBには厳しい
  48. 48. それなら、 前処理が必要なのは
  49. 49. それなら、 前処理が必要なのは パターンよりもむしろ テキストだ!
  50. 50. 高速化の手段(2) 索引を付与する (テキストを前処理)
  51. 51. 索引の方式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” (転置インデックス)と呼ばれる
  52. 52. 索引の方式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が出た」を検索する場合、
  53. 53. 索引の方式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は候補から外れることがわかる!
  54. 54. 索引の方式1: 単語ごとに保存する 利点: 単語単位に区切っているので 意図した結果が出やすい 欠点: 単語の区切りに沿わないものを 抽出できない
  55. 55. 欠点: 単語の区切りに沿わないものを 抽出できない →対応したければ  「すべての部分文字列」を  索引に格納するようにする
  56. 56. 索引の方式2: すべての部分文字列を保存する C++11が出たと思ったらもうC++14が出る。 (1文字目が起点の部分文字列) “C”, “C+”, “C++”, “C++1”, “C++11”, ... (2文字目が起点の部分文字列) “+”, “++”, “++1”, “++11”, “++11が”, ... :
  57. 57. メモリ 使いすぎない?
  58. 58. 実際は かなり節約 できます。
  59. 59. 索引の方式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
  60. 60. 索引の方式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
  61. 61. 注意点
  62. 62. 前半(パターンの前処理)の ときに言ったこと パターンを時間をかけて 前処理するのだから ● パターンがある程度長いときに 効果を発揮する ● 逆に、短いときは逆効果だったり
  63. 63. テキストの前処理だと テキストを時間をかけて 前処理するのだから ● テキストがある程度長いときに 効果を発揮する ● 逆に、短いときは逆効果だったり
  64. 64. テキストは、パターンに比べると とてつもなく長いことも多い (データベース使って 文書を格納してるとか) ↓ 前処理の時間が ばかにならない!
  65. 65. ● テキストが頻繁に更新される 場合にはあまり向かない (テキストエディタ内の検索など) ● 索引を作るとすれば、相応の 計算量が必要 ● それ以上に検索の高速化の 意義がある応用に使われる (文書DB検索など)
  66. 66. おわりに
  67. 67. 普段はstd::string::findのように シンプルに検索してもいいけど ● パターンを前処理 ● テキストを前処理 も必要に応じて使おう!

×