言語処理100本ノックチャレンジ（Python）準備運動編

[言語処理100本ノック](http://www.cl.ecei.tohoku.ac.jp/nlp100/)を最近見つけたので、チャレンジしていきたいと思います。

## 今回チャレンジした項目
準備運動の中で下記にチャレンジしてまとめています。
なお、Pythonのバージョンは 3.6.0 で、私のPython歴は1年未満の初級者です。

```
00. 文字列の逆順
文字列"stressed"の文字を逆に（末尾から先頭に向かって）並べた文字列を得よ．

01. 「パタトクカシーー」
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ．

02. 「パトカー」＋「タクシー」＝「パタトクカシーー」
「パトカー」＋「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ．

03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し，各単語の（アルファベットの）文字数を先頭から出現順に並べたリストを作成せよ．

04. 元素記号
"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."という文を単語に分解し，1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字，それ以外の単語は先頭に2文字を取り出し，取り出した文字列から単語の位置（先頭から何番目の単語か）への連想配列（辞書型もしくはマップ型）を作成せよ．

05. n-gram
与えられたシーケンス（文字列やリストなど）からn-gramを作る関数を作成せよ．この関数を用い，"I am an NLPer"という文から単語bi-gram，文字bi-gramを得よ．
```

## 00.文字列の逆順
スライスという配列操作で得ることができます。
なんとなく文字列型に `reverse`のようなメソッドがあることを想像していたんですがないみたいです。

スライスについては、公式ドキュメントや数多くの書籍、サイトで解説されているので割愛します。

``` main.py
def _00_reverse(word):
    return word[::-1]

if __name__ == '__main__':
    print('00: ', _00_reverse('stressed'))
```

## 01.「パタトクカシーー」
こちらもスライスによって得ることができます。

``` main.py
def _01_extract(word):
    return word[::2]

if __name__ == '__main__':
    print('01: ', _01_extract('パタトクカシーー'))
```

## 02. 「パトカー」＋「タクシー」＝「パタトクカシーー」
`zip`という組み込み関数を使うと、複数の配列を一緒にイテレーションすることができます。

蛇足ですが、文字数が多い場合は文字列連結がパフォーマンスのボトルネックになりそうなので、配列にappendしてからjoinするなどしたほうが良さそうです。また、今回は引数2つ固定ですが、可変長にしたほうがより一般的な関数になりそうです。

``` main.py
def _02_concat(word1, word2):
    concat_word = ''
    for letter1, letter2 in zip(word1, word2):
        concat_word += letter1 + letter2
    return concat_word

if __name__ == '__main__':
    print('02: ', _02_concat('パトカー', 'タクシー'))
```

## 03. 円周率
モジュール `re`では正規表現を扱うことができます。
カンマやコロンなどの単語と無関係な記号を除外して、単語のリストを取得するために`re`モジュールの`findall`で、正規表現にマッチする単語のリストを取得しています。最後はリスト内包表記で、単語の長さをカウントしています。

文章が長いときは、`findall`だと処理が厳しい気がするので、`finditer`でイテレータを取得して処理すると良いのかもしれません。（確認してません）

``` main.py
import re

def _03_pie(sentence):
    pattern = re.compile('\w+')
    words = pattern.findall(sentence)
    return [len(word) for word in words]

if __name__ == '__main__':
    print('03: ', _03_pie("Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."))
```

## 04. 元素記号
03と同様に正規表現で単語に分割しています。
`enumerate`を使うとイテレーションしたときのインデックスが取得できます。また、オプションの引数でインデックスの開始を指定できます。

``` main.py
def _04_element_symbol(sentence):
    pattern = re.compile('\w+')
    words = pattern.findall(sentence)
    def get_init_char(i, word):
        return word[:1] if i in [1,5,6,7,8,9,15,16,19] else word[:2]
    return [get_init_char(i, word) for i, word in enumerate(words, 1)]

if __name__ == '__main__':
    print('04: ', _04_element_symbol("Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."))
```

## 05. n-gram
N-gramというのは単語や文字そのものだけではなく、前後N単語（文字）も含めた組み合わせを取り扱う手法です。N=2のときはbi-gramというようです。ここでは、単語と文字を正規表現でそれぞれ表現して処理するようにしました。bi-gramはタプルのリストで返しています。

``` main.py
def _05_n_gram(sentence, type='word'):
    if type not in ['word', 'char']:
        raise ValueError('type requires word or char')

    pattern = re.compile('\w+' if type == 'word' else '\w')
    words = pattern.findall(sentence)
    return [(word, words[i+1]) for i, word in enumerate(words)
        if i+1 < len(words)]

if __name__ == '__main__':
    print('05(word): ', _05_n_gram("I am an NLPer", 'word'))
    print('05(char): ', _05_n_gram("I am an NLPer", 'char'))
```

## まとめ
もっと良い書き方などある気がしますが、なんとか完走目指したいと思います。