言語処理100本ノック with Python【第1章】
Djangoの勉強で少し触れた程度で、内包表記やlambdaなどのThe Python! なコードを全然書いていないので、勉強がてら言語処理100本ノックを少しずつやっていく自分用まとめ記事です。
Quitaに結構記事があがっていたのでそちらをぱくり参考にしつつ、学んだ部分についてメモっていきます。
少しずつ記事を追記します(最初の投稿日は2018/4/1)。
100問終わるのはいつになるやら…。
このサイトの問題を解いていきます。問題文の引用はこのサイトからしています。
www.cl.ecei.tohoku.ac.jp
他の引用は各問題文の下にある参考サイトからしています。
Jupyterで書いたコードはgithubに上げています。
github.com
No | 日付 | 学んだこと |
---|---|---|
00 | 4/1 | スライス |
01 | 4/2 | - |
02 | 4/3 | zip()、join()、functools.reduce() |
03 | 4/3 | split()、内包表記、isalpha()、translate()、maketrans()、 compile()、sub() |
04 | 4/4 | OrderDict() |
05 | 4/5 | range()、stript() |
06 | 4/6 | set型 |
07 | 4/6 | string.Template |
08 | 4/6 | islower()、chr()、ord() |
08 | 4/7 | random.shuffle() |
準備運動
テキストや文字列を扱う題材に取り組みながら,プログラミング言語のやや高度なトピックを復習します.
00. 文字列の逆順
文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
素人の言語処理100本ノック:00
言語処理100本ノック with Python(第1章)
word= 'stressed' result = word[::-1] # = word[-1::-1] print(result) # desserts
スライスの使い方
文字列[開始インデックス : 終了インデックス : ステップ数]
01. 「パタトクカシーー」
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.
word= 'パタトクカシーー' result = word[::2] print(result) # パトカー
尚、タクシーを取り出す場合は「word[1::2]」とスタート位置をずらせばよい。
02. 「パトカー」+「タクシー」=「パタトクカシーー」
「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.
解法1
police_car = 'パトカー' taxi = 'タクシー' result = '' for (a, b) in zip(police_car, taxi): # zipはイテレータを返すので中身を見るときはリストにする result += a + b print(result) # パタトクカシーー
zipの詳しい使い方は参考にしたQiitaの記事にいろいろあった。
要素数は短い方に合わせる。
解法2
内包表記:pythonの内包表記を少し詳しく
# 文字列をつなげていると、ループを回すごとに新しいメモリを確保するので遅くなるらしい。 # 文字列のリストを作ってから、最後にjoinするver. police_car = 'パトカー' taxi = 'タクシー' print(''.join([char1 + char2 for char1, char2 in zip(police_car, taxi)]))
join
'間に挿入する文字列'.join([連結したい文字列のリスト])
joinでリストを連結する場合はすべて文字列である必要がある。そうでない場合はmapを使う。
map
map(関数, リスト)
str_list = ['python', 'list', 125] maped_list = map(str, str_list) # 全てstr型のリストを作成
解法3
from functools import reduce police_car = 'パトカー' taxi = 'タクシー' result = ''.join(reduce(lambda x, y: x + y, zip(police_car, taxi))) print(result)
functools.reduce
指定したイテラブルに対して指定した関数を累積的に実行し、結果を1つにまとめてくれるもの
直前の結果とイテラブルから取り出した値の2つを関数に渡して新たな結果を作り、これをイテラブルの終わりまで順次繰り返してくれます。
>>> def add(x, y): ... return x+y ... >>> import functools >>> functools.reduce(add, [1,2,3]) 6 # 0+1+2+3 >>> functools.reduce(add, [1,2,3], 1) # 初期値1を設定 7 # 1+1+2+3
勉強になります。。
03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.
素人の言語処理100本ノック:03
言語処理100本ノック with Python(第1章)
Python で言語処理100本ノック2015
解法1
words = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." words = words.replace('.', "") words = words.replace(',', "") words = words.split() # 引数に何も指定しない場合、スペースやタブ等で自動的に区切る result = [] # for word in words: # result.append(len(word)) result = [len(word) for word in words] # 上のfor文を内包表記で書き換え print(result) # [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
内包表記
forの内包表記でリストの場合は[]で囲み、辞書の場合は{}で囲む
dic = {str(i):i for i in range(10)} # dic = {} # for i in range(10): # dic[str(i)] = i
解法2
target = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.' result = [] words = target.split(' ') for word in words: count = 0 for char in word: if char.isalpha(): count += 1 result.append(count) print(result)
文字判定メソッド一覧
メソッド | 説明 |
---|---|
isalnum() | 文字がすべて英数文字かどうかの判定 |
isalpha() | 文字がすべて英字かどうかの判定 |
isdigit() | 文字がすべて数字かどうかの判定 |
islower() | 大小文字の区別がある文字がすべて小文字かどうかの判定 |
isspace() | 文字がすべて空白かどうかの判定 |
istitle() | 文字列がタイトルケースかどうかの判定 |
isalnum()で日本語が正しく判定出来ないようだったけどエンコードすればおk。
[修正] Python 文字列の英数字判定でハマった
test = 'てすと' test.isalnum() # True test = 'てすと'.encode('utf-8') test.isalnum() # False
解法3
words = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." print([len(word.translate(word.maketrans("","",",."))) for word in words.split()])
maketrans、translate
解法1では、replace()でコンマやピリオドを置き換え・削除したが、複数文字を対象にする場合には、translate()を使う。処理時間も早いらしい(下記リンク参考)。
文字の変換にはstr.translate()が便利
まず、maketransを使ってマッピング変換テーブルを作成し、その後translateに引数として渡す。
しかしPythonといえばワンライナー!
translate(maketrans('変換前の文字列', '変換後の文字列', ‘削除対象の文字列’))
今回は変換はせず単純に削除対象だけを指定している。
最初、replace()の感覚で「、。」を消そうと思って以下のようにしたらエラーが出た。
変換前と変換後、二つのパラメータは同じ長さでなくてはならないようです。
print([len(word.translate(word.maketrans(",.",""))) for word in words.split()]) # ValueError: the first two maketrans arguments must have equal length
map
この問題とは関係ないけど、mapの文字置換の方法も例としてあったのでメモ。
text = "あいう、えお。" table = { '、': ',', '。': '.', } result = ''.join(table.get(c, c) for c in text)
解法4
import re s = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." pat = re.compile('[.,]') print([len(pat.sub('', word)) for word in s.split()]) print([len(re.sub('[.,]','', word)) for word in s.split()]) # compileしない版
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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.
素人の言語処理100本ノック:04
enumrate:
Python zipとenumerateの使い方
解法1
s = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can." num_list = [1, 5, 6, 7, 8, 9, 15, 16, 19] s = s.replace(".", "") words = s.split(" ") result = {} for i, word in enumerate(words,1): # iの開始位置を1に指定 if i in num_list: result[word[:1]] = i else: result[word[:2]] = i print(result) # {'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(for分内を内包表記で)
for i, word in enumerate(words, 1): j = 1 if i in num_list else 2 result[word[:j]] = i
OrderDict
Pythonの辞書で入れた順番を管理する
words = OrderedDict((('one', 1), ('two', 2)))
のようにタプルを引数に渡すと良い。
python OrderedDict便利だが注意が必要 | Shannon Lab
05. n-gram
与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.
素人の言語処理100本ノック:05
言語処理100本ノック 第1章 in Python
n-gramとは
n-gramとは、任意の文書や文字列などにおける任意のn文字が連続した文字列のことである。
1文字続きのものはunigram、2文字続きのものはbigram、3文字続きのものはtrigram、と特に呼ばれ、4文字以上のものは、単に4-gram、5-gramと表現されることが多い。
n-gramとは - IT用語辞典 Weblio辞書
N-gram - Negative/Positive Thinking
解法1
# def n_gram(target, n): # result = [] # for i in range(0, len(target) - n + 1): # result.append(target[i:i + n]) # return result def n_gram(s,n): return [s[i:i+n] for i in range(len(s)-n+1)] target = 'I am an NLPer' words_target = target.split(' ') print(n_gram(target, 2)) # 文字bi-gram print(n_gram(words_target, 2)) # 単語bi-gram # ['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er'] # [['I', 'am'], ['am', 'an'], ['an', 'NLPer']]
解法2
target="I am an NLPer" #文字bi-gram charGram=[target[i:i+2] for i in range(len(target)-1)] #単語bi-gram words=[word.strip(".,") for word in target.split()] wordGram=["-".join(words[i:i+2]) for i in range(len(words)-1)] print(charGram) print(wordGram)
内包表記、、慣れないなぁ。。
pythonの内包表記を少し詳しく
内包表記の方が通常のfor文とかより早いらしい。あとこんな計測の仕方があるのか~と勉強になった。
Pythonのリスト内包表記の速度
Pythonって読みやすいことが利点!っていうけど内包表記読みにくくね??と思ってたら面白い記事を発見。
Pythonのリスト内包表記をdisる - shkh's blog
06. 集合
"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.
set型
集合にはset型を使う。リストと同じく複数の値を格納できるが違いは、
・重複した要素がない
・要素に順序がない
重複する要素を追加することも出来るが(.add())、エラーは発生せずに無視される。
要素の削除はremove()、全削除はclear()。
解法
paradise = 'paraparaparadise' paragraph = 'paragraph' def n_gram(s,n): return [s[i:i+n] for i in range(len(s)-n+1)] X = set(n_gram(paradise ,2)) Y = set(n_gram(paragraph ,2)) print(X | Y) # = X.union(Y) print(X & Y) # = X.intersection(Y) print(X - Y) # = X.difference(Y) print('se' in X) print('se' in Y) # {'pa', 'se', 'ar', 'gr', 'ph', 'ra', 'ag', 'ad', 'ap', 'di', 'is'} # {'ar', 'ap', 'pa', 'ra'} # {'ad', 'di', 'se', 'is'} # True # False
07. テンプレートによる文生成
引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.
解法1
def create_sentense(x, y, z): return ('{0}時の{1}は{2}'.format(x,y,z)) print(create_sentense(12, "気温", 22.4)) # 12時の気温は22.4
急にえらく簡単だな、と思って参考のQiitaの記事を見たらstring.Templateクラスというものがあるらしい。
解法2
from string import Template def create_sentense(x, y, z): s = Template('$hour時の$targetは$value') return s.substitute(hour=x, target=y, value=z) # テンプレート置換を行い新たな文字列を形成 print(create_sentense(12, "気温", 22.4))
6.1. string — 一般的な文字列操作 — Python 3.6.5 ドキュメント
文字列置換は「string substitution」というらしい。覚えておこう。
08. 暗号文
与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.
・英小文字ならば(219 - 文字コード)の文字に置換
・その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.
おっ、03でついでにメモっておいたislower()の出番!
解法
def cipher(sentense): result = '' for c in sentense: if c.islower(): result += chr(219-ord(c)) else: result += c return result print(cipher('hitono okanede yakiniku tabetai!!')) print(cipher(cipher('hitono okanede yakiniku tabetai!!'))) # srglml lpzmvwv bzprmrpf gzyvgzr!! # hitono okanede yakiniku tabetai!!
09. Typoglycemia
スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ.
素人の言語処理100本ノック:09
言語処理100本ノック with Python(第1章)
Typoglycemiaってそもそも何だろう。
Typoglycemiaとは、単語を構成する文字を並べ替えても、最初と最後の文字が合っていれば読めてしまう現象のことである。
Typoglycemiaとは (タイポグリセミアとは) [単語記事] - ニコニコ大百科
あ~。だから先頭と末尾は入れ替えちゃだめなのね。
解法1
import random words = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ." def typoglycemia(words): result = [] for word in words.split(): if len(word) <= 4: result.append(word) else: chr_list = list(word[1:-1]) random.shuffle(chr_list) result.append(word[0] + ''.join(chr_list) + word[-1]) return ' '.join(result) print(typoglycemia(words)) print(type(typoglycemia(words))) # I clduno't bleviee that I cluod atlcaluy uaresndntd what I was ranedig : the peamhnneol pwoer of the hmaun mind . # <class 'str'>
解法2
import random words = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ." def typoglycemia(word): if len(word) <= 4: return word typo = list(word)[1:-1] random.shuffle(typo) return word[0] + ''.join(typo) + word[-1] print(' '.join(list(map(typoglycemia, words.split()))))