言語処理100本ノック 第1章 in Python


[言語処理100本ノック](http://qiita.com/drafts/3884c90a04ae01eaacef/edit)の第一章です。

環境はWindows10,python3.6.0です。[こちら](http://qiita.com/piyo56/items/eb72b496669f541055c3)を参考にさせていただきました。

## 00.文字列の逆順
"stressed"の文字を逆に並べた文字列を得よ。


```python
# coding: utf-8
target = "stressed"
new_target = target[::-1]
print(new_target)
```

    desserts
    

default は step が正の時0,8、負の時8、0

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


```python
# coding: utf-8
word = "パタトクカシーー"
new_word = word[::2]
print(new_word)
```

    パトカー
    

**uを忘れないようにする。**

## 02．「パトカー」＋「タクシー」　=　「パタトクカシー」

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


```python
# cording utf-8
word1 = u"パトカー"
word2 = u"タクシー"
mix_word = ""
for w1,w2 in zip (word1,word2):
    mix_word += w1 + w2
print(mix_word)
```

    パタトクカシーー
    

- 長い方に合わせるzip


```python
import itertools
target1 = '12345'
target2 = 'abc'
zipped = itertools.zip_longest(target1,target2)
print(list(zipped))
```

    [('1', 'a'), ('2', 'b'), ('3', 'c'), ('4', None), ('5', None)]
    

-  値をNone以外にする


```python
import itertools
target1 = '12345'
target2 = 'abc'
zipped = itertools.zip_longest(target1,target2,fillvalue = False )
print(list(zipped))
```

    [('1', 'a'), ('2', 'b'), ('3', 'c'), ('4', False), ('5', False)]
    

-  もう一回zip()すると元に戻る。


```python
import itertools
target1 = '12345'
target2 = 'abc'
zipped = itertools.zip_longest(target1,target2,fillvalue = False )
zipped_list = list(zipped)
zizipped = zip(zipped_list[0],zipped_list[1],zipped_list[2],zipped_list[3],zipped_list[4])
print(list(zizipped))
```

    [('1', '2', '3', '4', '5'), ('a', 'b', 'c', False, False)]
    
-  *を使ったver

```python
import itertools
target1 = '12345'
target2 = 'abc'
zipped = itertools.zip_longest(target1,target2,fillvalue = False )
zipped_list = list(zipped)
zizipped = zip(*zipped_list)
print(list(zizipped))
```

    [('1', '2', '3', '4', '5'), ('a', 'b', 'c', False, False)]
    

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


```python
words = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
result = []
new_words = words.translate(str.maketrans("","",",."))
for word in new_words.split(' '):
    word_length = len(word)
    result.append(word_length)
print(result)
```

    [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
    

strip は両端から指定の文字列を削除する。

str.translate(str.maketrans("","",".,"))

第1→第2　第三因数は削除したい文字列。



-  美しい解答


```python
words = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
result = [len(word.strip(",.")) for word in words.split(" ")]
print(result)
```

    [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
    


```python
import re
words = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
result = [len(word) for word in (re.sub(r"[,.]","",words).split(" "))]
print(result)
```

    [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
    

## 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
sentence = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
words = [word.strip(',.') for word in sentence.split()]
dic = {word[0]:words.index(word) + 1 for word in words if words.index(word) in (0,4,5,6,7,8,14,15,18)}
dic.update({word[:2]:words.index(word) + 1 for word in words if words.index(word) not in (0,4,5,6,7,8,14,15,18)})
print(dic)
```

    {'H': 1, 'B': 5, 'C': 6, 'N': 7, 'O': 8, 'F': 9, 'P': 15, 'S': 16, 'K': 19, 'He': 2, 'Li': 3, 'Be': 4, 'Ne': 10, 'Na': 11, 'Mi': 12, 'Al': 13, 'Si': 14, 'Cl': 17, 'Ar': 18, 'Ca': 20}
    
-  別解 1

```python
sentence = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
words = [word.strip(',.') for word in sentence.split()]
link = {}
for i,v in enumerate(words,1):
    length = 1 if i in [1,5,6,7,8,9,15,16,19] else 2
    link.update({v[:length]:i})
print(link)
```

    {'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}
    
-  別解 2

```python
sentence ="Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
#先頭の1文字(または2文字)と、その単語のインデックスを対応付ける辞書を作成
link = {w[:2-(i in (1,5,6,7,8,9,15,16,19))]:i for i,w in enumerate(sentence.split(),1)}
print(link)
```

    {'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}
    

Boolean値の利用　True = 1 False= 0

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


```python
import re

sentence_string = "I am an NLPer"
sentence_list = sentence_string.split()
def n_gram(sequence,n):
    u"""文字列で渡されたとき文字bi-gram,リストとして渡されたとき単語bi-gramとして扱われる。
    """
    result = []
    if isinstance(sequence,str):
        sequence = list(re.sub("[,. ]","",sequence))
    for i in range(len(sequence)- n+1):
        result.append('-'.join(sequence[i:i+n]))        
    return result
print(n_gram(sentence_string,2))
print(n_gram(sentence_list,2))
```

    ['I-a', 'a-m', 'm-a', 'a-n', 'n-N', 'N-L', 'L-P', 'P-e', 'e-r']
    ['I-am', 'am-an', 'an-NLPer']
    

 - 内包表記版(shiracamus様より)

```python
import re

sentence_string = "I am an NLPer"
sentence_list = sentence_string.split()

def n_gram(sequence, n):
    u"""文字列で渡されたとき文字bi-gram,リストとして渡されたとき単語bi-gramとして扱われる。
    """
    if isinstance(sequence, str):
        sequence = list(re.sub("[,. ]", "", sequence))
    return ['-'.join(sequence[i:i+n])
            for i in range(len(sequence) - n + 1)]

print(n_gram(sentence_string, 2))
print(n_gram(sentence_list, 2))
```
内包表記、使えますね。。


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



```python
import re
X = "paraparaparadise"
Y = "paragraph"
def n_gram(sequence,n):
    result = []
    if isinstance(sequence,str):
        sequence = list(re.sub("[,. ]","",sequence))
    for i in range(len(sequence)- n+1):
        result.append('-'.join(sequence[i:i+n]))
    return result
​
X = (set(n_gram(X,2)))
Y = (set(n_gram(Y,2)))
print("X:",X)
print("Y:",Y)
print("和集合:",X | Y)
print("積集合:",X & Y)
print("差集合1:",X - Y)
print("差集合2:",Y - X)
if 's-e' in X:
    print('seはXに含まれる')
if 's-e' in Y:
    print('seはYに含まれる')
```


    X: {'a-d', 'a-r', 'r-a', 'i-s', 's-e', 'd-i', 'p-a', 'a-p'}
    Y: {'a-r', 'r-a', 'p-h', 'g-r', 'a-g', 'p-a', 'a-p'}
    和集合: {'a-r', 'i-s', 'p-a', 'a-p', 'a-d', 'r-a', 'p-h', 'g-r', 's-e', 'd-i', 'a-g'}
    積集合: {'p-a', 'a-p', 'a-r', 'r-a'}
    差集合1: {'a-d', 'd-i', 's-e', 'i-s'}
    差集合2: {'a-g', 'g-r', 'p-h'}
    seはXに含まれる


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


```python
def make_sentence(x,y,z):
    print(u"{0}時の{1}は{2}".format(x,y,z))
make_sentence(x = 12,y = "気温",z = 22.4)
```

    12時の気温は22.4
    

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


```python
import re
pat = re.compile(u"[a-z]") 
def cipher(string):
    return ''.join(chr(219-ord(c)) if pat.match(c) else c for c in string)

if __name__ == "__main__":
    sentence = u"Hello world!"
    ciphertext = cipher(sentence)
    print(sentence)
    print(ciphertext)
    print(cipher(ciphertext))
```

    re.compile('[a-z]')
    Hello world!
    Hvool dliow!
    Hello world!
    
- 正規表現不使用版(shiracamus様より)

```python

def cipher(string):
    return ''.join(chr(219 - ord(c)) if c.islower() else c for c in string)

if __name__ == "__main__":
    sentence = u"Hello world!"
    ciphertext = cipher(sentence)
    print(sentence)
    print(ciphertext)
    print(cipher(ciphertext))
```
str.islower()が使えるんですね。str.islower()は小文字+大文字小文字の区別の無い文字の文字列の場合でも`True`になるみたいです。

**chr(i)**
Unicode コードポイントが整数 i である文字を表す文字列を返します。例えば chr(97) は文字列 'a' を、 chr(8364) は文字列 '€' を返します。 ord() の逆です。

引数の有効な範囲は 0 から 1,114,111 (16 進数で 0x10FFFF) です。 i が範囲外の場合 ValueError が送出されます。

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


```python
from random import shuffle
def change_order(sentence):
    produced_word_list = []
    word_list = sentence.split(' ')
    for word in word_list:
        if len(word) <= 4:
            produced_word_list.append(word)
        else:
            middle = list(word[1:-1])
            shuffle(middle)
            produced_word = word[0] + ''.join(middle) + word[-1]
            produced_word_list.append(produced_word)
    return ' '.join(produced_word_list)
sentence = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(change_order(sentence))
```

    I cnud'olt bvieele that I cluod aucltaly uestradnnd what I was reading : the pnemonaehl power of the huamn mind .
    

-  別解

```python
import random
def change_order(sentence):
    produced_word_list = []
    word_list = sentence.split(' ')
    for word in word_list:
        if len(word) <= 4:
            produced_word_list.append(word)
        else:
            middle_list = list(word[1:-1])
            new_middle = ''
            while len(middle_list) > 0:
                rnd = random.randint(0,len(middle_list)-1)
                new_middle += middle_list.pop(rnd)
            new_word = word[0] + new_middle + word[-1]
            produced_word_list.append(new_word)
    return ' '.join(produced_word_list)
sentence = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(change_order(sentence))
```

    I cl'oundt beevile that I culod aauctlly unnetdarsd what I was rdeaing : the pneoenhaml peowr of the haumn mind .
    
- ジェネレータ と random.shuffle 版(shiracamus様より)

```python
import random

def change_order(sentence):
    def produced_words():
        word_list = sentence.split()
        for word in word_list:
            if len(word) <= 4:
                yield word
            else:
                middle = list(word[1:-1])
                random.shuffle(middle)
                yield word[0] + ''.join(middle) + word[-1]
    return ' '.join(produced_words())

sentence = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(change_order(sentence))
```

###ジェネレーターの謎
ジェネレーター、、、苦手意識しかなかったですが、せっかくなので、
[クラスとイテレータ - Dive Into Python 3 日本語版](http://diveintopython3-ja.rdy.jp/iterators.html)
[Pythonのiterator](http://nihaoshijie.hatenadiary.jp/entry/2016/11/21/221220)
を参考に理解しようとした。。


ジェネレーターは
for文の中に`next()`と`iter()`メソッドが含まれていて、ジェネレーター関数中の`__iter__`,`__next__`メソッドを呼び出している(？)という認識でよいのでしょうか。。
iteratorオブジェクトは2つのメソッドを持っていて、
`__iter__`メソッドは自分自身を返し、`__next__`メソッドは次の要素を返す。

[クラスとイテレータ - Dive Into Python 3 日本語版](http://diveintopython3-ja.rdy.jp/iterators.html)よりメモ
>
```
>>> import plural6
>>> r1 = plural6.LazyRules()
>>> r2 = plural6.LazyRules()
>>> r1.rules_filename                               ①
'plural6-rules.txt'
>>> r2.rules_filename
'plural6-rules.txt'
>>> r2.rules_filename = 'r2-override.txt'           ②
>>> r2.rules_filename
'r2-override.txt'
>>> r1.rules_filename
'plural6-rules.txt'
>>> r2.__class__.rules_filename                     ③
'plural6-rules.txt'
>>> r2.__class__.rules_filename = 'papayawhip.txt'  ④
>>> r1.rules_filename
'papayawhip.txt'
>>> r2.rules_filename                               ⑤
'r2-overridetxt'
```
①	このクラスの各々のインスタンスは、クラスで定義された値を持つ属性rules_filenameを受け継いでいる。
②	1つのインスタンスの属性値を変更しても、他のインスタンスの属性値には影響を与えず……
③	……クラス属性も変更しない。クラス自体にアクセスするための特殊属性__class__を使うことで、（個々のインスタンスの属性ではなく）クラス属性を参照できる。
④	クラス属性を変更すると、まだ値を受け継いでいるインスタンス（ここではr1）はその影響を受ける。
⑤	その属性を上書きしているインスタンス（ここではr2）は影響を受けない。
>

個々のインスタンスとクラスのインスタンスは別物。




```python:sample1.py
class MyIterator(object):
    def __init__(self, *numbers):
        self._numbers = numbers
        self._i = 0
    def __iter__(self):
        # next()はselfが実装してるのでそのままselfを返す
        return self
    def next(self):
        if self._i == len(self._numbers):
            raise StopIteration()
        value = self._numbers[self._i]
        self._i += 1
        return value

my_iterator = MyIterator(10, 20, 30)
for num in my_iterator:
    print 'hello %d' % num
```
```python:sample2.py
class Fib:
    '''iterator that yields numbers in the Fibonacci sequence'''

    def __init__(self, max):
        self.max = max

    def __iter__(self):
        self.a = 0
        self.b = 1
        return self

    def __next__(self):
        fib = self.a
        if fib > self.max:
            raise StopIteration
        self.a, self.b = self.b, self.a + self.b
        return fib
```

Classの中で`next`が特殊メソッド`__next__`の場合と普通のメソッド`next`の場合とあるのは、なぜなのか。
Class内の`__next__`は外部のメソッド`next`から呼び出しているのに対し、Class内の`next`はClassの中で実行まで行っている(？)



わけわからない。