言語処理100本ノック 2015をやったら結構Python基礎力がついた 第1章

# はじめに
今更ですが、Pythonの勉強がてら[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)をやってみました。最初は何も見ずにやってみて、その後、他の方の100本ノックの書き方を参考に、より良い（スマートな）書き方でやり直してみます。

より良い書き方の参考文献は最後にまとめて記載しています。

# 第1章: 準備運動

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

```python:コード
input_str = 'stressed'
result = input_str[::-1]
print(result)
```

```:出力結果
desserts
```

### より良いコード
スライスのステップに負の値を設定すると末尾から見てくれる。これより大きく変わるのはなさそう。

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

```python:コード
input_str = 'パタトクカシーー'
result = ''
for index, s in enumerate(input_str):
   if index % 2 == 0:
       result += s
print(result)
```

```:出力結果
パトカー
```
### より良いコード
これもスライスでOK。

```python:コード
input_str = 'パタトクカシーー'
result = input_str[::2]
print(result)
```


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

```python:コード
p = 'パトカー'
t = 'タクシー'
result = ''
for i in range(len(p)):
   result += p[i]
   result += t[i]
print(result)
```
```:出力結果
パタトクカシーー
```
### より良いコード
`['パタ', 'トク', 'カシ', 'ーー']`というリストを作って`join()`する。

```python:コード
p = 'パトカー'
t = 'タクシー'
result = ''.join([char1 + char2 for char1, char2 in zip(p, t)])
print(result)
```
## 03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し，各単語の（アルファベットの）文字数を先頭から出現順に並べたリストを作成せよ．

```python:コード
input_str = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
result = []
input_str = input_str.replace(',', '').replace('.', '').split(' ')
for s in input_str:
   result.append(len(s))
print(result)
```
```:出力結果
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
```
### より良いコード
`split()`の引数はデフォルトで`' '`のため指定不要。

```python:コード
input_str = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
result = []
input_str = input_str.replace(',', '').replace('.', '').split()
for s in input_str:
   result.append(len(s))
print(result)
```

## 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文字を取り出し，取り出した文字列から単語の位置（先頭から何番目の単語か）への連想配列（辞書型もしくはマップ型）を作成せよ．

```python:コード
input_str = 'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.'
single_ary = [1, 5, 6, 7, 8, 9, 15, 16, 19]
result = {}
input_str = input_str.replace('.', '').split(' ')
for index, s in enumerate(input_str):
    if index + 1 in single_ary:
        result[s[0]] = index
    else:
        result[s[0] + s[1]] = index
print(result)
```
```:出力結果
{'H': 0, 'He': 1, 'Li': 2, 'Be': 3, 'B': 4, 'C': 5, 'N': 6, 'O': 7, 'F': 8, 'Ne': 9, 'Na': 10, 'Mi': 11, 'Al': 12, 'Si': 13, 'P': 14, 'S': 15, 'Cl': 16, 'Ar': 17, 'K': 18, 'Ca': 19}
```
### より良いコード
if文を三項演算子に。リストにaryって命名もよくなさそうなので修正。

```python:コード
input_str = 'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.'
single_list = [1, 5, 6, 7, 8, 9, 15, 16, 19]
result = {}
input_str = input_str.split()
for index, s in enumerate(input_str):
    l = 1 if index + 1 in single_list else 2
    result[s[:l]] = index
print(result)
```

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

```python:コード
input_str = 'I am an NLPer'

def create_word_n_gram(input_str, num):
    str_list = input_str.split(' ')
    results = []
    for i in range(len(str_list) - num + 1):
        ngram = ''
        for j in range(num):
            ngram += str_list[j + i]
        results.append(ngram)
    return results

def create_char_n_gram(input_str, num):
    str_no_space = input_str.replace(' ', '')
    results = []
    for i in range(len(str_no_space) - num + 1):
        ngram = ''
        for j in range(num):
            ngram += str_no_space[j + i]
        results.append(ngram)
    return results

print(create_word_n_gram(input_str, 2))
print(create_char_n_gram(input_str, 2))
```
```:出力結果
['Iam', 'aman', 'anNLPer']
['Ia', 'am', 'ma', 'an', 'nN', 'NL', 'LP', 'Pe', 'er']
```
### より良いコード
文字n-gramはスペースも含む。ずっと勘違いしていた。。。
単語n-gramも出力形式がいけていないので修正。

```python:コード
input_str = 'I am an NLPer'

def create_word_n_gram(input_str, num):
    str_list = input_str.split(' ')
    results = []
    for i in range(len(str_list) - num + 1):
        results.append(str_list[i:i + num])
    return results

def create_char_n_gram(input_str, num):
    results = []
    for i in range(len(input_str) - num + 1):
        results.append(input_str[i:i + num])
    return results

print(create_word_n_gram(input_str, 2))
print(create_char_n_gram(input_str, 2))
```
```:出力結果
[['I', 'am'], ['am', 'an'], ['an', 'NLPer']]
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
```

## 06. 集合
"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を，それぞれ, XとYとして求め，XとYの和集合，積集合，差集合を求めよ．さらに，'se'というbi-gramがXおよびYに含まれるかどうかを調べよ．

```python:コード
input_str_x = 'paraparaparadise'
input_str_y = 'paragraph'
word = 'se'

def create_char_n_gram(input_str, num):
    str_no_space = input_str.replace(' ', '')
    results = []
    for i in range(len(str_no_space) - num + 1):
        ngram = ''
        for j in range(num):
            ngram += str_no_space[j + i]
        results.append(ngram)
    return results

def calculate_union(list_x, list_y):
    list_union = list(set(list_x + list_y))
    return list_union

def calculate_intersection(list_x, list_y):
    list_sum = list_x + list_y
    list_intersection = [elem for elem in set(list_sum) if list_sum.count(elem) > 1]
    return list_intersection

def calculate_difference(list_x, list_y):
    list_intersection = calculate_intersection(list_x, list_y)
    list_sum = list_x + list_intersection
    list_difference = [elem for elem in set(list_sum) if list_sum.count(elem) == 1]
    return list_difference

def check_including_word(word_list, word):
    if word in word_list:
        return True
    else:
        return False

x = create_char_n_gram(input_str_x, 2)
y = create_char_n_gram(input_str_y, 2)

print(calculate_union(x, y))
print(calculate_intersection(x, y))
print(calculate_difference(x, y))
print(check_including_word(x, word))
print(check_including_word(y, word))
```
```:出力結果
['ar', 'ag', 'gr', 'is', 'ph', 'se', 'pa', 'di', 'ap', 'ad', 'ra']
['ar', 'pa', 'ap', 'ra']
['is', 'se', 'di', 'ad']
True
False
```
### より良いコード
setにしてしまえば集合演算が可能。関数なんていらなかった・・・
チェック関数もかなり冗長。こっちは書いている最中に気付くべき。

```python:コード
input_str_x = 'paraparaparadise'
input_str_y = 'paragraph'
word = 'se'

def create_char_n_gram(input_str, num):
    results = []
    for i in range(len(input_str) - num + 1):
        results.append(input_str[i:i + num])
    return results

x = set(create_char_n_gram(input_str_x, 2))
y = set(create_char_n_gram(input_str_y, 2))

print(x | y)
print(x - y)
print(x & y)
print(word in x)
print(word in y)
```

## 07. テンプレートによる文生成
引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ．さらに，x=12, y="気温", z=22.4として，実行結果を確認せよ．

```python:コード
x = 12
y = '気温'
z = 22.4

def create_str(x, y, z):
    return str(x) + '時の' + y + 'は' + str(z)
    
print(create_str(x, y, z))
```
```:出力結果
12時の気温は22.4
```
### より良いコード
文字列の足し方はいろいろあると思うが、これより大きく変わるのはなさそう。

## 08. 暗号文
与えられた文字列の各文字を，以下の仕様で変換する関数cipherを実装せよ．

英小文字ならば(219 - 文字コード)の文字に置換
その他の文字はそのまま出力
この関数を用い，英語のメッセージを暗号化・復号化せよ．

```python:コード
input_str = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'

def cipher(input_str):
    result = list(map(lambda e: chr(219 - ord(e)) if e.islower() else e, input_str))
    return ''.join(result)

print(cipher(input_str))
print(cipher(cipher(input_str)))
```
```:出力結果
Nld I mvvw z wirmp, zoxlslorx lu xlfihv, zugvi gsv svzeb ovxgfivh rmeloermt jfzmgfn nvxszmrxh.
Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.
```
### より良いコード

これより大きく変わるのはなさそう。
正しくは「復号化」ではなく「復号」。と思ったけど、今は「復号化」も普通に使われているらしい・・・

※一瞬、同じ操作で暗号化と復号を両方やるのは不可能だと思ってしまったが、`219 - x = y`なら、`219 - y = x`になるのは当然だった。219は他の数字でもよい。

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

```python:コード
import random
input_str = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."

def create_typoglycemia(input_str):
    input_str_list = input_str.split(' ')
    result = []
    for word in input_str_list:
        length = len(word)
        if length > 4:
            first_char = word[0]
            last_char = word[length - 1]
            random_str = ''.join(random.sample(list(word[1:length - 1]), length - 2))
            result.append(word[0] + random_str + word[length - 1])
        else:
            result.append(word)
    return ' '.join(result)

print(create_typoglycemia(input_str))
```
```:出力結果
I cunldo't biveele that I culod aclatluy urdseanntd what I was rdineag : the pehaneomnl pewor of the huamn mind .
```
### より良いコード
定義したくせに一度しか使っていない変数や、定義したくせに全く使っていない変数を削除。

```python:コード
import random
input_str = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."

def create_typoglycemia(input_str):
    result = []
    for word in input_str.split(' '):
        length = len(word)
        if length > 4:
            random_str = ''.join(random.sample(word[1:length - 1], length - 2))
            result.append(word[0] + random_str + word[length - 1])
        else:
            result.append(word)
    return ' '.join(result)

print(create_typoglycemia(input_str))
```


# おわりに
参考とさせていただいた、Qiitaに上がっている言語処理100本ノック2015（Python）の記事をまとめておきます。

* https://qiita.com/gamma1129/items/37bf660cf4e4b21d4267
* https://qiita.com/tanaka0325/items/08831b96b684d7ecb2f7
* https://qiita.com/nubilum/items/af0a2ff057b9a6d708ad