Julia でテキスト処理

以前，Julia で [FizzBuzz のショートコーディング](https://qiita.com/Rshimura/items/1aa8c6df0cc868b0b1d7)に挑戦しました。今回は，まっとうに Julia をテキスト処理のために使いたいと思います。そのための練習になります。

# 環境設定
Jupyter Notebook と REPL でやっていきます。Julia および Jupyter Notebook がインストールされている環境では，

```

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.0.0 (2018-08-08)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

### ここで ”]”をタイプするとプロンプトが切り替わる。
(v1.0) pkg> add IJulia
```
これで，Jupyter Notebook に Julia 1.0.0 のカーネルが追加されました（[参考](https://qiita.com/phigasui/items/8af41d1bcbe7ef276cfe)）。


# 文字列に関するあれこれ

[Julia のstringに関するドキュメント](https://docs.julialang.org/en/v1/base/strings/) の中から，使いそうなものをピックアップ。

### インデックス

```julia
julia> "QWERTY"[1]
'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)

julia> "QWERTY"[1:3]
"QWE"

julia> "QWERTY"[1:3:6]
"QR"

julia> "QWERTY"[1:2:6]
"QET"
```

次に，日本語の場合。

```julia
julia> "パリピープル"[1:3]
ERROR: StringIndexError("パリピープル", 3)
Stacktrace:
 [1] string_index_err(::String, ::Int64) at ./strings/string.jl:12
 [2] getindex(::String, ::UnitRange{Int64}) at ./strings/string.jl:246
 [3] top-level scope at none:0
```
想定していた「パリピ」が出ずに，エラーとなってしまいました……Juliaではutf-8でエンコードされるためです。つまり，ASCIIと同じ部分については1byteで表現され，その他の部分については，最大4byteで表現されます。日本語はたいてい3byteです。よって，

```julia
julia> "パリピープル"[1:3:7]
"パリピ"
```
と3ずつスキップしてやれば望みの「パリピ」を得ることができました*(Python とはスキップ数の指定の仕方が異なるので注意)。日本語で二文字ずつとりたければ，

```julia
julia> "パリピープル"[1:6:16]
"パピプ"

julia> "般若波羅蜜多"[1:6:16]
"般波蜜"
```
という感じになります。ちなみに，「次の有効なインデックス」を知るための関数として`nextind(str, i)`が用意されており，こんな感じでちゃんと有効なインデックスを調べることができます。

```julia
julia> function f(s)
           n = firstindex(s)
           while n ≤ lastindex(s)
               println(s[n], " :", n)
               n = nextind(s, n)
           end
       end
f (generic function with 1 method)

julia> f(s)
か :1
み :4
お :7
お :10
お :13
お :16
お :19
お :22
か :25

```

### その他関数

- `reverse`: 文字列の反転

```julia

julia> reverse("toilet")
"teliot"
```
- `split`: 文字列の分割
  - `split(<str> [,<dlm>])`：strをdlmで分解。デリミタのデフォルトは空白文字・改行文字など（`isspace()`で判別）。
  - `limit=<lim>` で分割結果の最大数を指定。`limit=0`がデフォルトで無制限を意味する。
  - `keepempty=<bool>` で空のフィールドを保存するか否か決める。デフォルトは，
      - `dlm`が指定されているとき`true`：保存される
      - `dlm`がない（＝空白文字で分割）のとき`false`：取り除かれる
具体例は以下。

```julia
# デリミタ指定なし（＝空白分割）
julia> split("ひふみ ゆん あおば はじめ")
4-element Array{SubString{String},1}:
 "ひふみ"
 "ゆん" 
 "あおば"
 "はじめ"

# 厳密には空白ではないので，改行も分割できる
# このとき，keepempty=false なので空白フィールドは無視される。（保存したければkeepempty=trueとする）
julia> split("ひふみ
       ゆん


       あおば

       はじめ")
4-element Array{SubString{String},1}:
 "ひふみ"
 "ゆん" 
 "あおば"
 "はじめ"


# デリミタを指定
julia> split("ひふみ/ゆん/あおば/はじめ", "/")
4-element Array{SubString{String},1}:
 "ひふみ"
 "ゆん" 
 "あおば"
 "はじめ"

# 正規表現も利用できる
julia> split("ひふみ//////ゆん///あおば//はじめ", r"/+")
4-element Array{SubString{String},1}:
 "ひふみ"
 "ゆん" 
 "あおば"
 "はじめ"

# また，keepempty オプションが現在は true なので
julia> split("ひふみ//////ゆん///あおば//はじめ", "/")
12-element Array{SubString{String},1}:
 "ひふみ"
 ""   
 ""   
 ""   
 ""   
 ""   
 "ゆん" 
 ""   
 ""   
 "あおば"
 ""   
 "はじめ"

# となるが，keepempty=false とすれば
julia> split("ひふみ//////ゆん///あおば//はじめ", "/", keepempty=false)
4-element Array{SubString{String},1}:
 "ひふみ"
 "ゆん" 
 "あおば"
 "はじめ"


```


# 練習1
おなじみの，[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/#) のとりあえず第一章をやっていきたいと思います。

## 第1章

#### 0.逆順

```julia
julia> reverse("stressed")
"desserts"
```

#### 1.奇数インデックス取り出し

```julia
julia> f("パタトクカシーー") # 先程の関数。ただ使いたかっただけ。
パ :1
タ :4
ト :7
ク :10
カ :13
シ :16
ー :19
ー :22

julia> "パタトクカシーー"[1:6:22]
"パトカー"
```

#### 2.合体

```julia
julia> for i = 1:3:10
           x = "パトカー"[i]
           y = "タクシー"[i]
           print(x, y)
       end
パタトクカシーー
```

#### 3.円周率

```julia
julia> s = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

julia> for w = split(s, r"[,\. ] ?", keepempty=false)
           print(length(w), " ")
       end
3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 
```

#### 4.元素記号

Mg はこれで良いんだろうか……

```julia
julia> s = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."

# 単語に分解
julia> w_list = split(s, r"[,\. ] ?", keepempty=false)
20-element Array{SubString{String},1}:
 "Hi"      
 "He"      
 "Lied"    
 "Because" 
 "Boron"   
 "Could"   
 "Not"     
 "Oxidize" 
 "Fluorine"
 "New"     
 "Nations" 
 "Might"   
 "Also"    
 "Sign"    
 "Peace"   
 "Security"
 "Clause"  
 "Arthur"  
 "King"    
 "Can"     

# 頭文字ひとつだけを取る番号のリストを用意
julia> single = [1, 5, 6, 7, 8, 9, 15, 16, 19]
9-element Array{Int64,1}:
  1
  5
  6
  7
  8
  9
 15
 16
 19

# 辞書型
julia> element_dict = Dict()
Dict{Any,Any} with 0 entries

# 登録
julia> for i = 1:length(w_list)
           word_tmp = w_list[i]
           element_dict[word_tmp[1:(i in single ? 1 : 2)]] = i
       end

# 確認
julia> element_dict
Dict{Any,Any} with 20 entries:
  "Si" => 14
  "C"  => 6
  "P"  => 15
  "Ne" => 10
  "Li" => 3
  "O"  => 8
  "B"  => 5
  "N"  => 7
  "He" => 2
  "Be" => 4
  "H"  => 1
  "Cl" => 17
  "Na" => 11
  "Al" => 13
  "S"  => 16
  "Ar" => 18
  "Mi" => 12
  "Ca" => 20
  "K"  => 19
  "F"  => 9

```

#### 5.n-gram

```julia
# 関数定義
julia> function gen_ngram(c, n)
           [c[i:(i + n - 1)] for i in 1 : length(c) - n + 1]
       end
gen_ngram (generic function with 1 method)

julia> gen_ngram("I am an NLPer", 2)
12-element Array{String,1}:
 "I "
 " a"
 "am"
 "m "
 " a"
 "an"
 "n "
 " N"
 "NL"
 "LP"
 "Pe"
 "er"

julia> gen_ngram(split("I am an NLPer"), 2)
3-element Array{Array{SubString{String},1},1}:
 ["I", "am"]    
 ["am", "an"]   
 ["an", "NLPer"]
```

#### 6.集合

```julia
julia> X = Set(gen_ngram("paraparaparadise", 2))
Set(["pa", "ar", "ad", "ap", "is", "se", "ra", "di"])

julia> Y = Set(gen_ngram("paragraph", 2))
Set(["pa", "ar", "ap", "gr", "ph", "ra", "ag"])

julia> X ∩ Y
Set(["pa", "ar", "ap", "ra"])

julia> X ∪ Y
Set(["is", "ph", "ra", "pa", "ar", "se", "di", "ad", "ap", "gr", "ag"])

julia> setdiff(X, Y)
Set(["ad", "is", "se", "di"])

julia> setdiff(Y, X)
Set(["gr", "ph", "ag"])
```

#### 7.テンプレートによる生成

```julia
julia> function announce(x, y, z)
           println(x, "時の", y, "は", z)
       end
announce (generic function with 1 method)

julia> announce(12, "気温", 22.4)
12時の気温は22.4
```

#### 8.暗号文

```julia
julia> function cipher(plain_txt)
           for c in plain_txt
               if c in 'a':'z'
                   print(Char(219 - codepoint(c)))
               else
                   print(c)
               end
           end
       end
cipher (generic function with 1 method)

# 確認
julia> cipher("May the Force be with you.")
Mzb gsv Flixv yv drgs blf.
```

#### 9.Typoglycemia

```julia
# 与えられた文字列をランダムに並べ替える関数
julia> function random_permutation(str::String)
           randnum_dict = Dict(zip(rand(Int, length(str)), str))
           permutated_arr = sort(collect(randnum_dict))
           result_str = ""
           for c in permutated_arr
               result_str *= c[2]
           end
           result_str
       end
random_permutation (generic function with 1 method)

# 文を単語に分解し，それぞれの単語の中身を並べ替える関数
julia> function typoglycemia(phrase)
           word_list = split(phrase)
           for w in word_list
               if length(w) > 4
                   w_tmp = String(w[2:end-1])
                   w = w[1] * random_permutation(w_tmp) * w[end]
               end
               print(w, " ")
           end
       end
typoglycemia (generic function with 1 method)

# 確認
julia> typoglycemia("I wabcdes bopqrsrn ijklmn 1987654.")
I wbeacds bqsrropn iljmkn 1568749. 

# 本番
julia> typoglycemia("I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")
I c'ndulot beveile that I culod alalutcy uaerdnsntd what I was raednig : the phneneaoml pwoer of the human mind . 
```

アポストロフィの扱いが雑なので，若干あれですが……Typoglycemia現象は実感できます。

#### 練習1まとめ
 以上，第一章をざっとやりましたが，どれも最善実装的な感じにはなっていないと思います。特に，sortのあたりは，なんかもっと簡単な方法がありそうです。

 ともかく，基本的な文字列に関する操作には少し慣れたので，次はもうちょっと実践的な感じのもので練習したいと思います。

# 練習2：次回予告

~~次回は，バイオインフォ界隈ではおなじみのUniprotデータベースのちょっとした調査を題材に練習します。過去に，Perl でコーディングしたものがありますので，そちらとのパフォーマンス比較もできたらと思います。~~
第３章に行きました。（[続き](https://qiita.com/Rshimura/items/e8727e4e4737163e63ce)）