言語処理100本ノック:第1章 準備運動

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の[「第1章: 準備運動」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch1)の記録です。
1年以上前にやった内容の復習です。改めて当時のコードを見ると、修正点が多くそれは自分自身の成長なのでしょうね。当時やったプログラムと比べるとコード量が1/2程度に圧縮された感があります。
そして、ある程度Python経験を積んだ今だから感じますが**Pythonおよび言語処理を学ぶのにいいチュートリアル**です。
後半に比べると1本のノックが軽く、まさに「準備運動」という名にふさわしい内容です。

# 環境

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|pyenv|1.2.15|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.6.9|pyenv上でpython3.6.9を使っています<br>3.7や3.8系を使っていないことに深い理由はありません<br>パッケージはvenvを使って管理しています|

# [第1章: 準備運動](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch1)
> テキストや文字列を扱う題材に取り組みながら，プログラミング言語のやや高度なトピックを復習します．
>> 文字列, ユニコード, リスト型, 辞書型, 集合型, イテレータ, スライス, 乱数

## [00. 文字列の逆順](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec00)
> 文字列"stressed"の文字を逆に（末尾から先頭に向かって）並べた文字列を得よ．

### 回答: [000.文字列の逆順.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/000.%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E9%80%86%E9%A0%86.ipynb)
`[start:stop:step]`でスライスを指定し、負の数にすることで逆順になります。

```python:000.文字列の逆順.ipynb
print('stressed'[::-1])
```

```bash:ターミナル出力結果
desserts
```

## [01. 「パタトクカシーー」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec01)
> 「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ．

### 回答: [001.「パタトクカシーー」.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/001.%E3%83%91%E3%82%BF%E3%83%88%E3%82%AF%E3%82%AB%E3%82%B7%E3%83%BC%E3%83%BC.ipynb)
`[start:stop:step]`でスライスを指定し、頭から8文字目までを2文字ステップで出力。

```python:001.「パタトクカシーー」.ipynb
print('パタトクカシーー'[0:7:2])
```

```bash:ターミナル出力結果
パトカー
```

## [02. 「パトカー」＋「タクシー」＝「パタトクカシーー」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec02)
> 「パトカー」＋「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ．

### 回答: [002.「パトカー」＋「タクシー」＝「パタトクカシーー」.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/002.%E3%80%8C%E3%83%91%E3%83%88%E3%82%AB%E3%83%BC%E3%80%8D%EF%BC%8B%E3%80%8C%E3%82%BF%E3%82%AF%E3%82%B7%E3%83%BC%E3%80%8D%EF%BC%9D%E3%80%8C%E3%83%91%E3%82%BF%E3%83%88%E3%82%AF%E3%82%AB%E3%82%B7%E3%83%BC%E3%83%BC%E3%80%8Dipynb)
`zip`関数を使って「パトカー」と「タクシー」の2単語をループし、内包表記で`['パタ','トク','カシ','ーー']`とリスト化します。リストを`join`関数でつなげて出力。
`zip`関数は頭では理解しているけど、私が経験した言語にない種類のコマンドなので、使うという発想になかなか至らないです。

```python:002.「パトカー」＋「タクシー」＝「パタトクカシーー」.ipynb
result = [char1+char2 for char1, char2 in zip('パトカー', 'タクシー')]
print(''.join(result))
```

```bash:ターミナル出力結果
パタトクカシーー
```

## [03. 円周率](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec03)
> "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し，各単語の（アルファベットの）文字数を先頭から出現順に並べたリストを作成せよ．

### 回答: [003.円周率.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/003.%E5%86%86%E5%91%A8%E7%8E%87.ipynb)
`split`関数でスペース区切分割をします。英語の言語処理では非常に使うやつですね。`strip`関数では単語末尾のカンマとピリオドを除去しています。

```python:003.円周率.ipynb
sentence = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
for word in sentence.split():
    print(len(word.strip(',.')), word.strip(',.'))
```

文字数が円周率になるのですね。

```bash:ターミナル出力結果
3 Now
1 I
4 need
1 a
5 drink
9 alcoholic
2 of
6 course
5 after
3 the
5 heavy
8 lectures
9 involving
7 quantum
9 mechanics
```

## [04. 元素記号](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec04)
> "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文字を取り出し，取り出した文字列から単語の位置（先頭から何番目の単語か）への連想配列（辞書型もしくはマップ型）を作成せよ．

### 回答: [004.元素記号.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/004.%E5%85%83%E7%B4%A0%E8%A8%98%E5%8F%B7.ipynb)
内包表記の辞書型を使っています(if文との組み合わせがやりかたわからずに苦労しました)。
出力が元素記号順になるように辞書のソートをしています。
最後に1要素ごとに改行したかったので、出力に`pprint`を使っています。

```python:004.元素記号.ipynb
from pprint import pprint

sentence = 'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.'
word_list = sentence.split()

result = ({word[0] if i in {1, 5, 6, 7, 8, 9, 15, 16, 19} else word[:2]: i for i, word in enumerate(word_list, 1)})
pprint(sorted(result.items(), key=lambda x:x[1]))
```

```bash:ターミナル出力結果
[('H', 1),
 ('He', 2),
 ('Li', 3),
 ('Be', 4),
 ('B', 5),
 ('C', 6),
 ('N', 7),
 ('O', 8),
 ('F', 9),
 ('Ne', 10),
 ('Na', 11),
 ('Mi', 12),
 ('Al', 13),
 ('Si', 14),
 ('P', 15),
 ('S', 16),
 ('Cl', 17),
 ('Ar', 18),
 ('K', 19),
 ('Ca', 20)]
```

## [05. n-gram](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec05)
> 与えられたシーケンス（文字列やリストなど）からn-gramを作る関数を作成せよ．この関数を用い，"I am an NLPer"という文から単語bi-gram，文字bi-gramを得よ．

### 回答: [005.n-gram.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/005.n-gram.ipynb)
新しく出てくる技術要素としては`for`での`range`使用くらいなものでしょうか。

```python:005.n-gram.ipynb
def generate_ngram(sentence):
    
    # 空白で分割してリスト化
    words = sentence.split()
    
    # 空白除去
    chars = sentence.replace(' ','')
    
    # 単語bi-gram生成
    bigram_word = [words[i-1] + ' ' + words[i] for i in range(len(words)) if i > 0]
    
    # 文字bi-gram生成
    bigram_char = [chars[i-1] + chars[i] for i in range(len(chars)) if i > 0]
    
    return bigram_word, bigram_char

print(generate_ngram('I am an NLPer'))
```

```bash:ターミナル出力結果
(['I am', 'am an', 'an NLPer'], ['Ia', 'am', 'ma', 'an', 'nN', 'NL', 'LP', 'Pe', 'er'])
```

## [06. 集合](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec06)
> "paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を，それぞれ, XとYとして求め，XとYの和集合，積集合，差集合を求めよ．さらに，'se'というbi-gramがXおよびYに含まれるかどうかを調べよ．

### 回答: [006.集合.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/006.%E9%9B%86%E5%90%88.ipynb)
Pythonでは`set`というものがあり、和集合・積集合・差集合を簡単に求めることができるようです。

```python:006.集合.ipynb
def generate_ngram(sentense):
    
    # 空白除去
    chars = sentense.replace(' ','')
    
    # 文字bi-gram生成
    bigram_char = [chars[i-1] + chars[i] for i in range(len(chars)) if i > 0]
    
    return bigram_char

bigram_x = set(generate_ngram('paraparaparadise'))
bigram_y = set(generate_ngram('paragraph'))

#和集合
print(bigram_x.union(bigram_y))

#積集合
print(bigram_x.intersection(bigram_y))

#差集合
print(bigram_x.difference(bigram_y))

search_word = {'se'}
print(search_word.intersection(bigram_x))
print(search_word.intersection(bigram_y))
```

```bash:ターミナル出力結果
{'ag', 'ap', 'se', 'ra', 'is', 'pa', 'ad', 'ph', 'di', 'ar', 'gr'}
{'pa', 'ar', 'ap', 'ra'}
{'ad', 'se', 'di', 'is'}

{'se'}
set()
```

## [07. テンプレートによる文生成](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec07)
> 引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ．さらに，x=12, y="気温", z=22.4として，実行結果を確認せよ．

### 回答: [007.テンプレートによる文生成.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/007.%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AB%E3%82%88%E3%82%8B%E6%96%87%E7%94%9F%E6%88%90.ipynb)
`+`で文字結合しました。`'{}時の{}は{}'.format(x, y, z)`でもいいですね。

```python:007.テンプレートによる文生成.ipynb
def create_sentence(x,y,z):
    return str(x) + '時の' + str(y) + 'は' + str(z)

print(create_sentence(12, '気温', 22.4))
```

```bash:ターミナル出力結果
12時の気温は22.4
```

## [08. 暗号文](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec08)
> 与えられた文字列の各文字を，以下の仕様で変換する関数cipherを実装せよ．

> - 英小文字ならば(219 - 文字コード)の文字に置換
> - その他の文字はそのまま出力

> この関数を用い，英語のメッセージを暗号化・復号化せよ．

### 回答: [008.暗号文.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/008.%E6%9A%97%E5%8F%B7%E6%96%87.ipynb)
「219 - 文字コード」とはこんな意味らしいです。
a の文字コードは 97で、今回の暗号化で219 - 97 = 122にすると文字コード 122 となり z。
z の文字コードは 122で、今回の暗号化で219 - 122 = 97 にすると文字コード 97 は a。
つまり、ローマ字小文字 a～z を z～a の逆順に入れ替えるという暗号化です。
組込関数`chr`を使って文字コードの制御をします。
内包表記を使おうか迷いましたが、`join`を最後にしなければいけないのは二度手間っぽいのでやめました。

```python:008.暗号文.ipynb
def cipher(sentence):
    result = ''
    for char in sentence:
        if char.islower():
            result += chr(219-ord(char))
        else:
            result += char
    return result

print(cipher('I Am An Idiot'))
```

```bash:ターミナル出力結果
I An Am Iwrlg
```

## [09. Typoglycemia](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec09)
> スペースで区切られた単語列に対して，各単語の先頭と末尾の文字は残し，それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ．ただし，長さが４以下の単語は並び替えないこととする．適当な英語の文（例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."）を与え，その実行結果を確認せよ．

### 回答: [009.Typoglycemia.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/01.%E6%BA%96%E5%82%99%E9%81%8B%E5%8B%95/009.Typoglycemia.ipynb)
*タイポグリセミア*は[Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%BF%E3%82%A4%E3%83%9D%E3%82%B0%E3%83%AA%E3%82%BB%E3%83%9F%E3%82%A2)によると以下の説明。
> 文章中のいくつかの単語で最初と最後の文字以外の順番が入れ替わっても正しく読めてしまう現象である。

なるほど、何となく読めますね。
`random`パッケージの`shuffle`関数を使って文字の並べ替えをしています。

```python:009.Typoglycemia.ipynb
from random import shuffle

def typoglycemia(word):
    mid_chars = list(word[1:-1])
    shuffle(mid_chars)
    return word[0] + ''.join(mid_chars) + word[-1]

sentence = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
' '.join([word if len(word) <= 4 else typoglycemia(word) for word in sentence.split(' ')])
```

```bash:ターミナル出力結果
"I cul'dnot beilvee that I culod altualcy udnnrseatd what I was riadeng : the paemhnenol peowr of the hmuan mind ."
```