stringiで輝く☆テキストショリスト

# stringiで輝く☆テキストショリスト
@kohske  
2015年2月25日  

---

# はじめに

> テキスト処理を制する者は世界を制す

Rのテキスト処理、ショボイと思ってませんか？
実際、ショボイです。
「統計処理はRでやるけど前処理でのテキスト処理はアウトソーシング」これRあるあるのひとつです。

ですが、`stringi`パッケージによってRは立派なテキスト処理ツールになります。
特に大量のテキストをサバく必要がある場合、`stringi`はチョッパヤです。
テキストショリストの皆様に於かれましてはこれを使わない手はありません。
テキスト処理のアウトソージングはもう終わり、`stringi`で今日から楽々テキストショリスト生活。

ちなみに読み方は「ストリンジィ（IPA [STRINɡI])」です。

---

## `stringi`パッケージとは

> Fast, correct, consistent and convenient string/text processing in each locale and any native character encoding.

> 速く、正しく、使いやすく、便利な文字列、テキスト処理。ロケール対応とエンコーディングサポートを添えて。

- 速い。
    - コアな部分はC++で実装されてます。
- 正しい。
    - 「正しくありたい」ということだと思います。
- 使いやすい。
    - インタフェースは`stringr`パッケージに倣ってます。神の手による統一されたインタフェース。
- 便利。
    - R baseにはない便利な関数があります。文字列リバースとか。
- ロケール & エンコーディング
    - 裏で[ICU](http://site.icu-project.org/)が働いてます。

## オフィシャルサイトなど

- CRAN http://cran.r-project.org/web/packages/stringi/index.html
- github https://github.com/Rexamine/stringi
- 公式サイト http://www.rexamine.com/resources/stringi/
- オンラインマニュアル http://docs.rexamine.com/R-man/stringi/stringi-package.html

---

# 準備

## システム要件

- R ≥ 2.15
- ICU4C ≥ 50 （無くても可）

## インストール

CRANから入れます。


```r
install.packages('stringi')
```

開発版はgithubより。コンパイル必要です。


```r
library('devtools') # 必要なら install.packages('devtools') 
install_github('Rexamine/stringi')
```

## 確認

正しくインストール出来てるか確認しましょう。


```r
library('stringi')
```

```
## Warning: package 'stringi' was built under R version 3.1.2
```

```r
stri_install_check()
```

```
## stringi_0.4.1; ja_JP.UTF-8; ICU4C 52.1; Unicode 6.3
## All tests completed successfully.
```

## 関数の形式

`stringi`パッケージの関数は

- 関数名は`stri_*`
- 最初の引数は処理対象とする文字列ベクトル（例外あり）
- 以降の引数はオプション

と統一されています。

---

# 文字列の検索・照合・分割など

## 検索エンジン

`stringi`パッケージでは文字列の検索に5種類のエンジンが用意されています。

- `stri_*_regex`: (ICUの)正規表現
- `stri_*_fixed`: ロケールに依存しないバイト列
- `stri_*_coll`: ICUの`StringSearch`エンジン。ロケールを考慮した検索を行います。自然言語処理向き。遅い。
- `stri_*_charclass`: Unicode文字クラス
- `stri_*_boundaries`: テキスト境界

通常は`stri_*_regex`を使っておけばいいんじゃないでしょうか。

## 関数一覧

- `stri_detect_*`: パターン検索。結果は論理値 (`TRUE`/`FALSE`)。
- `stri_count_*`: パターンの出現数。結果は数値。
- `stri_locate_*`: パターンの出現位置。結果は数値。
- `stri_extract_*`: パターンの抽出。結果は文字列。
- `stri_match_*`: パターンの抽出。結果は文字列。正規表現のサブグループの一致も得られます。
- `stri_replace_*`: 文字列の置換。結果は文字列。
- `stri_split_*`: 文字列の分割。結果は文字列。
- `stri_startswith_*`, `stri_endswith_*`: 文字列の先頭・終端のパターン一致判定。結果は論理値 (`TRUE`/`FALSE`)。
- `stri_subset_*`: パターン一致文字列の抽出。結果は文字列。

## 利用例




```r
# 処理する文字列ベクトル
target = c("迷わず行けよ", "行けばわかるさ", "アリガトー!!")
```

### パターン検索


```r
# 「行」を含む
stri_detect_regex(target, "行")
```

```
## [1]  TRUE  TRUE FALSE
```

```r
# 先頭が「行」
stri_detect_regex(target, "^行")
```

```
## [1] FALSE  TRUE FALSE
```

```r
# *_regexと*_fixedの違い
stri_detect_regex(target, "\x81")
```

```
## [1] FALSE FALSE FALSE
```

```r
stri_detect_fixed(target, "\x81") # バイト列検索ではヒットします
```

```
## [1]  TRUE  TRUE FALSE
```

```r
# 文字列のバイト列表現がこうなってるので。
lapply(target, charToRaw)
```

```
## [[1]]
##  [1] e8 bf b7 e3 82 8f e3 81 9a e8 a1 8c e3 81 91 e3 82 88
## 
## [[2]]
##  [1] e8 a1 8c e3 81 91 e3 81 b0 e3 82 8f e3 81 8b e3 82 8b e3 81 95
## 
## [[3]]
##  [1] e3 82 a2 e3 83 aa e3 82 ac e3 83 88 e3 83 bc 21 21
```

### パターン出現数

```r
stri_count_regex(target, "行")
```

```
## [1] 1 1 0
```

```r
# "行"または"け"または"ば"
stri_count_regex(target, "[行けば]")
```

```
## [1] 2 3 0
```

### パターンの出現位置

```r
# "行けば"
stri_locate_all_regex(target, "行けば")
```

```
## [[1]]
##      start end
## [1,]    NA  NA
## 
## [[2]]
##      start end
## [1,]     1   3
## 
## [[3]]
##      start end
## [1,]    NA  NA
```

```r
# "行"または"け"または"ば"
stri_locate_all_regex(target, "[行けば]")
```

```
## [[1]]
##      start end
## [1,]     4   4
## [2,]     5   5
## 
## [[2]]
##      start end
## [1,]     1   1
## [2,]     2   2
## [3,]     3   3
## 
## [[3]]
##      start end
## [1,]    NA  NA
```

```r
# 最初の"行"または"け"または"ば"
stri_locate_first_regex(target, "[行けば]")
```

```
##      start end
## [1,]     4   4
## [2,]     1   1
## [3,]    NA  NA
```

### パターンの抽出

```r
# "行けば"
stri_extract_all_regex(target, "行けば")
```

```
## [[1]]
## [1] NA
## 
## [[2]]
## [1] "行けば"
## 
## [[3]]
## [1] NA
```

```r
# "行"または"け"または"ば"
stri_extract_all_regex(target, "[行けば]")
```

```
## [[1]]
## [1] "行" "け"
## 
## [[2]]
## [1] "行" "け" "ば"
## 
## [[3]]
## [1] NA
```

```r
# 最初の"行"または"け"または"ば"
stri_extract_first_regex(target, "[行けば]")
```

```
## [1] "行" "行" NA
```

### パターンの抽出（サブグループ含む）

```r
# "行けば"
stri_match_all_regex(target, "行けば")
```

```
## [[1]]
##      [,1]
## [1,] NA  
## 
## [[2]]
##      [,1]    
## [1,] "行けば"
## 
## [[3]]
##      [,1]
## [1,] NA
```

```r
# "行"と"ば"をサブグループに。
stri_match_all_regex(target, "(行)け(ば)")
```

```
## [[1]]
##      [,1] [,2] [,3]
## [1,] NA   NA   NA  
## 
## [[2]]
##      [,1]     [,2] [,3]
## [1,] "行けば" "行" "ば"
## 
## [[3]]
##      [,1] [,2] [,3]
## [1,] NA   NA   NA
```

### 文字列の置換

```r
# 飛べ
stri_replace_all_regex(target, "行け", "飛べ")
```

```
## [1] "迷わず飛べよ"   "飛べばわかるさ" "アリガトー!!"
```

```r
# マッチの参照
stri_replace_all_regex(target, "(行け)", "$1$1")
```

```
## [1] "迷わず行け行けよ"   "行け行けばわかるさ" "アリガトー!!"
```

### 文字列の分割

```r
stri_split_regex(target, "け")
```

```
## [[1]]
## [1] "迷わず行" "よ"      
## 
## [[2]]
## [1] "行"         "ばわかるさ"
## 
## [[3]]
## [1] "アリガトー!!"
```

### パターン一致文字列の抽出

```r
stri_subset_regex(target, "行け")
```

```
## [1] "迷わず行けよ"   "行けばわかるさ"
```

```r
stri_subset_regex(target, "行けば")
```

```
## [1] "行けばわかるさ"
```

---

# 文字列の結合

## 関数一覧

- `stri_join`: 文字列の結合。`paste`と同じです。`stri_c`、`stri_paste`でもOK。
- `stri_dup`: 文字列の繰り返し。
- `%s+%`: 文字列の結合演算子。
- `stri_flatten`: 文字列ベクトルを一つの文字列に。

## 利用例


```r
# 結合
stri_join(target, "ボンバイェ")
```

```
## [1] "迷わず行けよボンバイェ"   "行けばわかるさボンバイェ" "アリガトー!!ボンバイェ"
```

```r
# 結合
stri_join(target, c("、いーち", "、にー", "、さーん、ダ～〜"))
```

```
## [1] "迷わず行けよ、いーち"         "行けばわかるさ、にー"         "アリガトー!!、さーん、ダ～〜"
```

```r
# 繰り返し
stri_dup(target, 2)
```

```
## [1] "迷わず行けよ迷わず行けよ"     "行けばわかるさ行けばわかるさ" "アリガトー!!アリガトー!!"
```

```r
# 演算子
target %s+% "ボンバイェ"
```

```
## [1] "迷わず行けよボンバイェ"   "行けばわかるさボンバイェ" "アリガトー!!ボンバイェ"
```

```r
# 演算子
target %s+% c("、いーち", "、にー", "、さーん、ダ～〜")
```

```
## [1] "迷わず行けよ、いーち"         "行けばわかるさ、にー"         "アリガトー!!、さーん、ダ～〜"
```

```r
# 一つの文字列に
stri_flatten(target)
```

```
## [1] "迷わず行けよ行けばわかるさアリガトー!!"
```

```r
# 間に文字を挟んで
stri_flatten(target, collapse = "！")
```

```
## [1] "迷わず行けよ！行けばわかるさ！アリガトー!!"
```

---

# 文字列の置換、反転

## 関数一覧

- `stri_sub`: 部分文字列の抽出、置換
- `stri_reverse`: 文字列の反転

## 例用例


```r
# 抽出
stri_sub(target, 2, 4)
```

```
## [1] "わず行" "けばわ" "リガト"
```

```r
# 置換（文字列に破壊的変更が生じます）
target2 = target
stri_sub(target2, 2, 4) <- "ボンバイエ"
target2
```

```
## [1] "迷ボンバイエけよ"   "行ボンバイエかるさ" "アボンバイエー!!"
```

```r
# 反転
stri_reverse(target)
```

```
## [1] "よけ行ずわ迷"   "さるかわばけ行" "!!ートガリア"
```

---

# 文字列のトリム

文字列前後の無駄なゴミを取り除きます。デフォルトはスペース系を削除。ゴミはキャラクタクラスで指定します。

## 関数一覧

- `stri_trim`: 前後のゴミを削除
- `stri_pad`: 前後に文字を挿入して指定した長さの文字列に
- `stri_wrap`: 適当な文字数で分割`

## 利用例


```r
target_gomi = c("  迷わず行けよ  ", "行けばわかるさ", "アリガトー!!")
# 前後のスペース系削除
stri_trim_both(target_gomi)
```

```
## [1] "迷わず行けよ"   "行けばわかるさ" "アリガトー!!"
```

```r
# 前だけ
stri_trim_left(target_gomi)
```

```
## [1] "迷わず行けよ  " "行けばわかるさ" "アリガトー!!"
```

```r
# 詰め物
stri_pad_both(target, 20, "@")
```

```
## [1] "@@@@@@@迷わず行けよ@@@@@@@"  "@@@@@@行けばわかるさ@@@@@@@" "@@@@@@アリガトー!!@@@@@@@"
```

```r
# 分割
stri_wrap(target, 5)
```

```
## [1] "迷わず行け" "よ"         "行けばわか" "るさ"       "アリガト"   "ー!!"
```

---

# 文字列の長さ

## 関数一覧

- `stri_length`: 文字列長を取得
- `stri_count_boundaries`: テキスト境界の数を取得
- `stri_count_words`: 単語数を取得

境界についてはICUのBreakIteratorを知っておく必要があります。

## 利用例


```r
# 文字列長
stri_length(target)
```

```
## [1] 6 7 7
```

---

# 文字列の大文字・小文字変換

## 関数一覧
- `stri_trans_tolower`: 小文字に
- `stri_trans_toupper`: 大文字に
- `stri_trans_totitle`: 単語の先頭だけ大文字に、後は小文字に

## 利用例


```r
target_en = c("Hello", "sTRingi", "worlD")
stri_trans_tolower(target_en)
```

```
## [1] "hello"   "stringi" "world"
```

```r
stri_trans_toupper(target_en)
```

```
## [1] "HELLO"   "STRINGI" "WORLD"
```

```r
stri_trans_totitle(target_en)
```

```
## [1] "Hello"   "Stringi" "World"
```

---

# 文字列のUnicode正規化

マニアックすぎるので解説は保留（というか説明しないといけない前提知識が多過ぎ）。

---

# 文字列の比較・ソート・重複チェックなど

## 関数一覧

- `stri_cmp`: ２つの文字列比較。色々な比較が出来ます。一つ目が大きければ`1`、小さければ`-1`、同じなら`0`です。
- ` %s<%`: 文字列比較演算子。たくさんあります。
- `stri_order`: 文字列を辞書順に並べた時の順番
- `stri_sort`: 文字列を辞書順にソート
- `stri_unique`: 重複する文字列を削除
- `stri_duplicate`: 文字列が重複するか判定

比較にはロケール依存バージョンと非依存バージョンがあります。まあ、通常は気にしないでOKでしょう。

## 利用例


```r
target_cmpA = "アントニオ猪木"
target_cmpB = "アントキの猪木"
# 色々な比較
stri_cmp(target_cmpA, target_cmpB)
```

```
## [1] 1
```

```r
stri_cmp(target_cmpB, target_cmpA)
```

```
## [1] -1
```

```r
stri_cmp(target_cmpA, target_cmpA)
```

```
## [1] 0
```

```r
stri_cmp_eq(target_cmpA, target_cmpB)
```

```
## [1] FALSE
```

```r
# 辞書順の符号号比較
stri_cmp_lt(target_cmpA, target_cmpB)
```

```
## [1] FALSE
```

```r
# 等しくなくない？
stri_cmp_neq(target_cmpA, target_cmpB)
```

```
## [1] TRUE
```

```r
# 演算子
target_cmpA %s==% target_cmpB
```

```
## [1] FALSE
```

```r
target_cmpA %s!=% target_cmpB
```

```
## [1] TRUE
```

```r
target_cmpA %s<% target_cmpB
```

```
## [1] FALSE
```

```r
target_cmpA %s>% target_cmpB
```

```
## [1] TRUE
```

```r
# 順番
stri_order(target)
```

```
## [1] 3 2 1
```

```r
# ソート
stri_sort(target)
```

```
## [1] "アリガトー!!"   "行けばわかるさ" "迷わず行けよ"
```

```r
# 重複削除
(target_dup = c(target, "行けばわかるさ"))
```

```
## [1] "迷わず行けよ"   "行けばわかるさ" "アリガトー!!"   "行けばわかるさ"
```

```r
stri_unique(target_dup)
```

```
## [1] "迷わず行けよ"   "行けばわかるさ" "アリガトー!!"
```

```r
# 重複チェック
stri_duplicated(target_dup)
```

```
## [1] FALSE FALSE FALSE  TRUE
```

```r
# 重複があるかどうか
stri_duplicated_any(target)
```

```
## [1] 0
```

```r
stri_duplicated_any(target_dup)
```

```
## [1] 4
```

---

# Unicode文字列のエスケープ

Unicode文字列のエスケープ、アンエスケープ


```r
stri_escape_unicode("उ")
```

```
## [1] "\\u0909"
```

```r
stri_unescape_unicode("\\u0909")
```

```
## [1] "उ"
```

---

# ランダム文字列の生成

## 関数一覧

- `stri_rand_strings`: 適当な文字列生成
- `stri_rand_shuffle`: 文字列のランダムシャッフル
- `stri_rand_lipsum`: ランダムなlorem ipsumの生成

## 利用例


```r
#　ランダム文字列生成
stri_rand_strings(2, 20, "[アントニオ猪木]")
```

```
## [1] "猪木オニニオ木木トオントオオオト猪ンン木" "アア猪猪ニ猪オトオ猪アニンニアニン猪オン"
```

```r
# シャッフル
stri_rand_shuffle(target)
```

```
## [1] "行わけず迷よ"   "ばけわるか行さ" "!ア!ートガリ"
```

```r
# lorem ipsum
stri_rand_lipsum(2)
```

```
## [1] "Lorem ipsum dolor sit amet, leo eu. Pretium suspendisse leo suspendisse sed interdum pharetra tincidunt turpis erat. Dis nec vel luctus. Urna, blandit molestie, magnis rhoncus mi morbi aliquam dictum vitae. Ac aliquam erat, dictum dignissim. Mi, nulla auctor est et consequat non pellentesque mollis cubilia. Venenatis vehicula inceptos eget. Phasellus, tincidunt ut. Viverra etiam non, amet in vivamus. Quam pulvinar tellus pharetra. Varius blandit nisl, curae quis. Diam est consequat tellus, odio, nibh ridiculus sed in sed vestibulum. Vestibulum odio lobortis semper neque."
## [2] "Sed hendrerit primis sagittis libero non lorem nec cras pharetra et? Habitasse aliquam, urna urna hac diam, in placerat maximus quam. Vitae est, dictumst, praesent fringilla. Maximus sed. Porta in massa fringilla orci in. Ut donec amet at placerat parturient pretium nulla accumsan. Vivamus id tincidunt dolor sagittis maximus eu at, netus amet tempus."
```

---

# 文字列の統計情報

## 関数一覧

- `stri_stats_general`: ざっくりしたテキスト統計情報
- `stri_stats_latex`: LaTeXテキストの統計情報

## 利用例


```r
stri_stats_general(target)
```

```
##       Lines LinesNEmpty       Chars CharsNWhite 
##           3           3          20          20
```

---

# ベンチマーク

少しだけベンチとっときます


```r
library(microbenchmark)
microbenchmark(grepl("行", target), stri_detect_regex(target, "行"))
```

```
## Unit: microseconds
##                             expr   min    lq    mean median     uq    max neval
##              grepl("行", target) 4.765 5.414 6.73867 5.6695 6.0995 66.944   100
##  stri_detect_regex(target, "行") 6.838 7.549 8.43641 7.9300 8.4990 40.597   100
```

速くない！！


```r
# 長い文字列
tar = stri_rand_strings(10000, 42, "[あ-ん]")
microbenchmark(grepl("いのき", tar), stri_detect_regex(tar, "いのき"))
```

```
## Unit: milliseconds
##                              expr       min        lq      mean    median        uq      max neval
##              grepl("いのき", tar) 13.683766 13.958156 16.745969 17.614225 18.266413 23.37691   100
##  stri_detect_regex(tar, "いのき")  4.883445  5.043954  5.429288  5.141571  5.285112 10.37166   100
```

速い!!

処理するテキストが大きいほど威力発揮すると思います。

---

# おわりに

`stringi`パッケージの有能っぷりはわかってもらえたでしょうか？
「テキスト処理はパイソンにアウトソース」なんてもう言わせない。

いつの日かR-coreに取り入れられることを夢見て、輝く☆テキストショリスト

Enjoy!!