言語処理100本ノック 2015の「第1章: 準備運動」の記録です。
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を使っています 3.7や3.8系を使っていないことに深い理由はありません パッケージはvenvを使って管理しています |
第1章: 準備運動
テキストや文字列を扱う題材に取り組みながら,プログラミング言語のやや高度なトピックを復習します.
文字列, ユニコード, リスト型, 辞書型, 集合型, イテレータ, スライス, 乱数
00. 文字列の逆順
文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
回答: 000.文字列の逆順.ipynb
[start:stop:step]
でスライスを指定し、負の数にすることで逆順になります。
print('stressed'[::-1])
desserts
01. 「パタトクカシーー」
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.
回答: 001.「パタトクカシーー」.ipynb
[start:stop:step]
でスライスを指定し、頭から8文字目までを2文字ステップで出力。
print('パタトクカシーー'[0:7:2])
パトカー
02. 「パトカー」+「タクシー」=「パタトクカシーー」
「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.
回答: 002.「パトカー」+「タクシー」=「パタトクカシーー」.ipynb
zip
関数を使って「パトカー」と「タクシー」の2単語をループし、内包表記で['パタ','トク','カシ','ーー']
とリスト化します。リストをjoin
関数でつなげて出力。
zip
関数は頭では理解しているけど、私が経験した言語にない種類のコマンドなので、使うという発想になかなか至らないです。
result = [char1+char2 for char1, char2 in zip('パトカー', 'タクシー')]
print(''.join(result))
パタトクカシーー
03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.
回答: 003.円周率.ipynb
split
関数でスペース区切分割をします。英語の言語処理では非常に使うやつですね。strip
関数では単語末尾のカンマとピリオドを除去しています。
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(',.'))
文字数が円周率になるのですね。
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. 元素記号
"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
内包表記の辞書型を使っています(if文との組み合わせがやりかたわからずに苦労しました)。
出力が元素記号順になるように辞書のソートをしています。
最後に1要素ごとに改行したかったので、出力にpprint
を使っています。
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]))
[('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
与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.
回答: 005.n-gram.ipynb
新しく出てくる技術要素としてはfor
でのrange
使用くらいなものでしょうか。
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'))
(['I am', 'am an', 'an NLPer'], ['Ia', 'am', 'ma', 'an', 'nN', 'NL', 'LP', 'Pe', 'er'])
06. 集合
"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.
回答: 006.集合.ipynb
Pythonではset
というものがあり、和集合・積集合・差集合を簡単に求めることができるようです。
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))
{'ag', 'ap', 'se', 'ra', 'is', 'pa', 'ad', 'ph', 'di', 'ar', 'gr'}
{'pa', 'ar', 'ap', 'ra'}
{'ad', 'se', 'di', 'is'}
{'se'}
set()
07. テンプレートによる文生成
引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.
回答: 007.テンプレートによる文生成.ipynb
+
で文字結合しました。'{}時の{}は{}'.format(x, y, z)
でもいいですね。
def create_sentence(x,y,z):
return str(x) + '時の' + str(y) + 'は' + str(z)
print(create_sentence(12, '気温', 22.4))
12時の気温は22.4
08. 暗号文
与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.
- 英小文字ならば(219 - 文字コード)の文字に置換
- その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.
回答: 008.暗号文.ipynb
「219 - 文字コード」とはこんな意味らしいです。
a の文字コードは 97で、今回の暗号化で219 - 97 = 122にすると文字コード 122 となり z。
z の文字コードは 122で、今回の暗号化で219 - 122 = 97 にすると文字コード 97 は a。
つまり、ローマ字小文字 a~z を z~a の逆順に入れ替えるという暗号化です。
組込関数chr
を使って文字コードの制御をします。
内包表記を使おうか迷いましたが、join
を最後にしなければいけないのは二度手間っぽいのでやめました。
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'))
I An Am Iwrlg
09. Typoglycemia
スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ.
回答: 009.Typoglycemia.ipynb
タイポグリセミアはWikipediaによると以下の説明。
文章中のいくつかの単語で最初と最後の文字以外の順番が入れ替わっても正しく読めてしまう現象である。
なるほど、何となく読めますね。
random
パッケージのshuffle
関数を使って文字の並べ替えをしています。
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(' ')])
"I cul'dnot beilvee that I culod altualcy udnnrseatd what I was riadeng : the paemhnenol peowr of the hmuan mind ."