始める前に
今回から「自然言語処理100本ノック」の解説を章ごとに行っていきたいと思います。
できるだけ、Python初心者向けでも理解できるように解説していこうと思うので、
これからよろしくお願いします!
第1章: 準備運動
00. パタトクカシーー
x="パトカー"
y="タクシー"
ans=""
for i in range(len(x)):
ans+=x[i]
ans+=y[i]
print(ans) #パタトクカシーー
2つの文字列の前からインデックスを指定して、ansに格納していきます。
一応、頻出のrangeについて復習すると、range(start, stop, step)を基本として、
start以上stop未満を指定step数で連続した整数を作ります。
今回の場合は、len(x)が4であるため、range(4)となり、0,1,2,3の整数を作ります。
01. タクシー
text = "パタトクカシーー"
ans=text[1::2]
print(ans) #タクシー
if i % 2 ==0 など、いくつか方法がありますが、Pythonにおいてスライスが一番効率的です。
使い方は[start:stop:step]です。
rangeの引数と同じように使います。
1::2では、stopが省略されています。このように省略されていると、文字列の末尾(最後まで)が自動的に指定されます。
つまり、インデックスが1の「タ」から「ク」、「シ」、「ー」とスライスされます。
02. 文字列の逆順
text="stressed"
ans=text[::-1]
print(ans)#desserts
01と同様にスライスの問題です。
startとstopが省略されています。つまり、これは最初から最後までの文字を指定しています。
そして、stepが-1となっていますが、これは逆の順番でスライスしていきます。
よって、d→e→s→s→e→r→t→sでdessertsとなります。
03. 円周率
解答① for文とリストを別々
sentence='Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
sentence=sentence.replace(".","").replace(",","")
words=sentence.split()
ans=[]
for word in words:
ans.append(len(word))
print(ans))#[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
解答② リストの中にfor文を入れる
sentence='Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
sentence=sentence.replace(".","").replace(",","")
ans=[len(word) for word in sentence.split()]
print(ans)#[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
文章から余分な,.を取り除き、単語ごとにリスト化します。その後、単語の長さを数えリストに格納します。
リスト内包表記
for文からリストの使い方は2つあると思います。
通常の使い方の①では、ansを[]でリストとして、宣言してからfor文の中でその値をappendで格納しています。
対して、②ではans=[len(word) for word in sentence.split()]のように、右側でfor文を使い、要素を取り出して、左側で値を返してみます。
このような方法は、リスト内包表記と呼ばれます。
使い方: [返したい値 for 要素 in リスト if 要素の条件]
replace()の使い方
文字列.replace(置き換え前, 置き換え後, 置き換える回数)
replace(“.”,””)とreplace(“,”,””)のように、コンマとカンマを””で置き換えることでその文字をなくすことができます。
split()の使い方
文字列.split(区切り文字, 最大分割数)
split()のようにデフォルトでは、空白文字で分割条件なくリスト化します。
(前)
‘Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.’
(後)
[‘Now’,’I’,’need’,’a’,’drink,’,’alcoholic’,’of’,’course,’,’after’,’the’,’heavy’,’lectures’,’involving’,’quantum’,’mechanics.’]
04. 元素記号
解答①自作関数を使用して、タプルリストから辞書型を作る
#04
sentence="Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can"
sentence=sentence.replace(".","").replace(",","").split()
def get_value(i,v):
if i in [1, 5, 6, 7, 8, 9, 15, 16, 19]:
return (v[0],i)
else:
return (v[:2],i)
ans=[get_value(i,v) for i,v in enumerate(sentence,1)]
print(dict(ans))
#{'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}
解答②自作関数を使用せず、そのまま辞書を作る
sentence="Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can"
sentence=sentence.replace(".","").replace(",","").split()
select={ 1, 5, 6, 7, 8, 9, 15, 16, 19}
ans = {
v[0] if i in select else v[:2] : i
for i,v in enumerate(sentence, 1)
}
print(ans)
#{'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文字と先頭からの番号を使って辞書を作成します。
解説①では、リスト内包表記を使用しているのに対して、解答②では、辞書内包表記を使っています。
どちらも内包表記を使用していて、大きな違いはありませんが、辞書型への変換に注意が必要です。
dict()の使い方
04の解答では、キーと値のペアから作っています。
例えば、
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = dict(pairs)
print(d)
このコードでは、リストの中に(キー、値)が入っています。
このリストをdictの引数に入れることで辞書を作ることができます。
enumerateの使い方
for index, value in enumerate(list, start=0):
startはインデックスの開始位置となります。インデックスとその値をindex,valueに代入します。
enumerate(sentence,1)では、1を開始位置とするので、index=1 value=Hi ,index=2 value=He,index=3 value=Lied…のように続いていきます。
05. n-gram
def n_gram(n,sentence):
return ["".join(sentence[i:i+n]) for i in range(0,len(sentence)-n+1)]
sentence="I am an NLPer"
#単語bi-gram
print(n_gram(2,sentence.split()))
#文字tri-gram
print(n_gram(3,list(sentence.replace(" ",""))))
それぞれ、単語と文字の前処理をして、リスト化したものをn_gramという関数に代入します。
特に、文字tri-gramのlist(sentence.replace(” “,””))はひとつの大きな単語にしてから、list()によりひとつずつの文字をリスト化します。
n_gramとは
n_gramとは、「n個の連続した要素を取り出す方法」です。
そして、文字n-gramと単語n-gramの二つがあります。
実際に今回のケースに当てはめるとわかりやすいです。
文字tri-gramの場合
[“I”,”a”,”m”,”a”,”n”,”N”,”L”,”P”,”e”,”r”]という文字リストを作ります。
文字tri-gramとは、「3個の連続した文字を取り出す」ということなので、
“I”と”a”と”m”, ”a”と”m”と”a”, ”m”と”a”と”n”, ”a”と”n”と”N”, ”n”と”N”と”L”, ”N”と”L”と”P”, “L”と”P”と”e”, ”P”と”e”と”r”
となります。
単語bi-gramの場合
[“I”, “am”, “an”, “NLPer”]という単語リストを作ります。
単語bi-gramとは、「2個の連続した単語を取り出す」ということなので、
“I”と”am”, “am”と”an”, “an”と”NLPer”
となります。
n_gram関数の作り方
n_gram関数の中にある、[“”.join(sentence[i:i+n]) for i in range(0,len(sentence)-n+1)]を分解すると、
“”.join(sentence[i:i+n]) とfor i in range(0,len(sentence)-n+1)の二つに分解できます。
“区切り文字”.join(リストやタプル)は、リストの要素を指定の区切り文字を使って合体させます。
つまり、””.join(sentence[i:i+n]) はn個のスライスした要素リストを区切りなしでくっつけます。
次に、for i in range(0,len(sentence)-n+1)]でも、len(sentence)-n+1)に注目してほしいです。
もし、すべてのリストを3個ずつずらして取得した場合、最後の2つは2つの要素と1つの要素のみしか取得できません。
これを対策するために、len(sentence)-n+1をすることですべてn個を取得できます。
len(sentence)-n+1より、len(sentence)-(nー1)の方がイメージしやすいと思います。
最後のn-1個はn未満になってしまうため、その分だけ引いているだけです。
06. 集合
def n_gram(n,sentence):
return ["".join(sentence[i:i+n]) for i in range(0,len(sentence)-n+1)]
A="paraparaparadise"
B="paragraph"
A=set(n_gram(2,list(A)))
B=set(n_gram(2,list(B)))
# 和集合
print(A | B)
# 積集合
print(A & B)
# 差集合
print(A - B)
if 'se' in (A & B):
print("seはAとBに含まれています")
else:
print("seはどちらにも含まれていません")
05で書いたn_gramを使います。
文字列をlist()により、文字をリスト化しn_gram関数に渡して、文字bi-gramを作成します。
その後、set()により、リストを集合にして、和、積、差の集合を調べていきます。
和集合 (全部) A ∪ B → A | B
積集合 (共通) A ∩ B → A & B
差集合 (片方のみ)A - B → A – B
07. テンプレートによる文生成
def generate_template(x,y,z):
return f'{x}時の{y}は{z}'
print(generate_template(12,"気温",224))
変数の埋め込みが今回のポイントです。
埋め込みの方法として3つありますが、以下は参考程度にどうぞ。
① f文字列(f-string)
f'{x}時の{y}は{z}’のようにそのまま埋め込みます。
② format() メソッドを使う方法
‘{}時の{}は{}’.format(x,y,z)のように後ろから変数を埋め込みます。
③ %(パーセント)演算子
‘%d時の%sは%d’%(x,y,z)のように、%sや%dを指定して、その型に合った変数を埋め込みます。
08. 暗号文
def cipher(sentence):
return "".join([chr(219-ord(c))if 97 <= ord(c) and ord(c) <=127 else c for c in sentence])
cipher("testデータです")#gvhgデータです
「英小文字ならば (219 – 文字コード) のASCIIコードに対応する文字に置換」とは、a~z の文字を逆順に置き換えます。((219-文字コード)の-はマイナスです))
aの文字コードは97、zの文字コードは122です。
この公式に当てはめると、
219-a=219-97=122となり、zの文字コード
219-z=210-122=97となり、aの文字コード
になります。
よって、文字コード変換はord(c)で取得できるため、
219-ord(c)の値を計算し、chr(c)で文字コードから文字への変換を行います。
文字列の文字イテレーションから文字列の再構築方法
今回は文字列をfor文を使用し、文字のリストを作成後、join()でくっつけました。
他の方法として、new_sentence=””を作った後に、new_sentence+=cのような方法があります。
しかし、これは長い文字列では効率が悪く遅くなります。
この理由は、文字列は不変であるため、一度作った文字列に追加や削除はできないから、新しい文字列としてメモリに新しく作られてしまうのです。
09. Typoglycemia
#09
import random
def random_word(word):
if len(word) <=4:
return word
else:
random_list=random.sample(list(word[1:-1]),len(word)-2)
return "".join([word[0]]+random_list+[word[-1]])
test="I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(" ".join([random_word(word) for word in test.split()]))
random.sampleを使用することで、中間のを入れ替えることができます。
random.sample(リスト,数)でリストから指定した数の要素を取得します。
そして、returnの箇所でword[0]とword[-1]は文字なので、リスト化しくっつけます。
余談ですが、私は先にrandom.shuffleを使用して以下のように実装しました
def random_word(word):
if len(word) <=4:
return word
else:
word=list(word)
new_word=word[:]#コピー
random_list=[i for i in range(1,len(word)-1)]
random.shuffle(random_list)
for i ,v in enumerate(random_list,1):
new_word[i]=word[v]
return "".join(new_word)
この場合でも、問題なく行えましたが、若干わかりずらいコードになってしまいました。
まとめ
私は近頃JavaScriptやGo,C++をやって、Pythonを避けていました。しかし、今回の準備運動でかなりPythonの独特な感じを思い出すことができたので、うるおぼえな方にもおすすめします。
次回は、第2章: UNIXコマンドの解説に進んでいきます。


コメント