Python初心者が少しずつ言語処理100本ノックを頑張るエントリー

# 言語処理100本ノック 2015

http://www.cl.ecei.tohoku.ac.jp/nlp100/

初心者がPython(3.x)で頑張ります。同様の記事はたくさんあると思うのですが、個人的な備忘録として。アドバイスやご指摘などありましたら是非コメントをください！

ソースコードはgithubにもあげています。
https://github.com/hbkr/nlp100

## Chapter1

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

```py3:000.py
s = "stressed"
print(s[::-1])
```
```
desserts
```

`s[i:j:k]`は slice of s from i to j with step k とのことなので、`s[::-1]`では末尾から-1文字ずつ先頭までさかのぼります。

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

```py3:001.py
s = "パタトクカシーー"
print(s[::2])
```
```
パトカー
```

上記の説明通り、`s[::2]`で先頭から末尾まで1文字飛ばしで文字列を取り出せます。

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

```py3:002.py
s = "".join(i+j for i, j in zip("パトカー", "タクシー"))
print(s)
```
```
パタトクカシーー
```

`zip`で複数のシーケンスオブジェクトを同時にループ回せます。`sep.join(seq)`は`sep`を区切り文字として`seq`を連結しひとつの文字列にします。リスト内包表記したものを空文字で`join`しています。

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

```py3:003.py
s = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
count = [len(i.strip(",.")) for i in s.split()]
print(count)
```
```
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
```

`str.split(sep)`で`sep`を区切り文字として文字列を分割してリストにします。区切り文字が指定されていない場合、スペース、タブ、改行文字列などで区切られます。`str.strip(",.")`で前後の,.を削除したものを`len()`で文字数カウントしています。

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

```py3:004.py
s = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
dic = {word[:2-(i in (1,5,6,7,8,9,15,16,19))]:i for i, word in enumerate(s.replace(".", "").split(), 1)}
print(dic)
```
```
{'He': 2, 'K': 19, 'S': 16, 'Ar': 18, 'Si': 14, 'O': 8, 'F': 9, 'P': 15, 'Na': 11, 'Cl': 17, 'B': 5, 'Ca': 20, 'Ne': 10, 'Be': 4, 'N': 7, 'C': 6, 'Mi': 12, 'Li': 3, 'H': 1, 'Al': 13}
```

`enumerate(seq[, start=0])`で要素のインデックスと要素の両方を取得できます。インデックスをそのまま`in`演算子に渡して取得文字数を調整しています。

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

```py3:005.py
def n_gram(s, n): return {tuple(s[i:i+n]) for i in range(len(s)-n+1)}

s = "I am an NLPer"
print(n_gram(s, 2))
print(n_gram([t.strip(".,") for t in s.split()], 2))
```
```
{('m', ' '), ('n', ' '), ('e', 'r'), ('N', 'L'), (' ', 'N'), ('a', 'm'), ('a', 'n'), ('L', 'P'), ('I', ' '), (' ', 'a'), ('P', 'e')}
{('an', 'NLPer'), ('I', 'am'), ('am', 'an')}
```

N-gram方式は、文章を文字列の並び順にN文字を見出し語としてインデックスする方法です。`n_gram(s, n)`関数では渡されたシーケンスオブジェクト`s`を`n`要素ずつ切り出して集合型で返します。集合型で返すことで要素が重複しないようにしています。

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

```py3:006.py
n_gram = lambda s, n: {tuple(s[i:i+n]) for i in range(len(s)-n+1)}

X = n_gram("paraparaparadise", 2)
Y = n_gram("paragraph", 2)

print("X: %s" % X)
print("Y: %s" % Y)
print("union: %s" % str(X|Y))
print("difference: %s" % str(X-Y))
print("intersection: %s" % str(X&Y))

if n_gram("se", 2) <= X: print("'se' is included in X.")
if n_gram("se", 2) <= Y: print("'se' is included in Y.")
```
```
X: {('a', 'd'), ('a', 'p'), ('i', 's'), ('s', 'e'), ('a', 'r'), ('p', 'a'), ('d', 'i'), ('r', 'a')}
Y: {('g', 'r'), ('p', 'h'), ('a', 'p'), ('a', 'r'), ('p', 'a'), ('r', 'a'), ('a', 'g')}
union: {('a', 'd'), ('g', 'r'), ('p', 'h'), ('a', 'p'), ('i', 's'), ('s', 'e'), ('a', 'r'), ('p', 'a'), ('d', 'i'), ('r', 'a'), ('a', 'g')}
difference: {('i', 's'), ('d', 'i'), ('a', 'd'), ('s', 'e')}
intersection: {('a', 'r'), ('p', 'a'), ('a', 'p'), ('r', 'a')}
'se' is included in X.
```

005.pyで作成した`n_gram`を使いますが、今回は`lambda`式を使ってみました（今回は関数を作れとは書いてなかったので）。`X|Y`で和集合、`X-Y`で差集合、`X&Y`で積集合です。

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

```py3:007.py
def f(x, y, z): return "%s時の%sは%s" % (x, y, z)

print(f(12, "気温", 22.4))
```
```
12時の気温は22.4
```

`"{0}時の{1}は{2}です".format(x, y, z)`でも良いですね。

### 08. 暗号文
***
*与えられた文字列の各文字を，以下の仕様で変換する関数cipherを実装せよ．
- 英小文字ならば(219 - 文字コード)の文字に置換
- その他の文字はそのまま出力
この関数を用い，英語のメッセージを暗号化・復号化せよ．*

```py3:008.py
def cipher(s): return "".join(chr(219-ord(c)) if c.islower() else c for c in s)

s = "Hi He Lied Because Boron Could Not Oxidize Fluorine."
print(cipher(s))
print(cipher(cipher(s)))
```
```
Hr Hv Lrvw Bvxzfhv Blilm Clfow Nlg Ocrwrav Foflirmv.
Hi He Lied Because Boron Could Not Oxidize Fluorine.
```

`islower`の所は`"a"<=c<="z"`でも良いみたいです。そっちの方が早いのかな？

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

```py3:009.py
from random import random

typo = lambda s: " ".join(t[0]+"".join(sorted(t[1:-1], key=lambda k:random()))+t[-1] if len(t) > 4 else t for t in s.split())

s = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(typo(s))
```
```
I cdnlu'ot blieeve that I culod aclualty uetdnnsard what I was rdeniag : the pnneehmoal pwoer of the huamn mind .
```

なんか意地になって１行で頑張った。`shuffle()`関数が返り値無しなので`sorted()`を使っています。