今回は
自然言語処理100本ノックの準備運動を解いてみました。



解説動画はこちら


自然言語処理とは
コンピューターに言語を処理させるための
一連の方法です

自然言語処理100本ノックはこちら
自然言語処理100本ノック

プログラミングを行う上で
こういった文字列の処理方法を覚えておくと
プログラミングスキルが格段にアップします。

やっておいて損はしないと思います。

どの言語でも対応することは可能です。

さてPython言語を用いて
1つ1つ解いていきましょう。

第1章: 準備運動 からです。

00. 文字列の逆順

文字列"stressed"の文字を逆に
(末尾から先頭に向かって)並べた文字列を得よ.

解き方としては
文字列のリバース方法を考えるです。

Python言語の
リスト型などはリバース用の関数がありますが
文字列には無いようです。

Pythonでは次のようにすることで実現できます。
"stressed"[::-1]
'desserts'

これだけです。
インデックスを用いると文字列を切り出すことができます。

インデックスの書き方は
開始位置 : 終了位置 : 飛ばす個数
になっています。

-1することで終わりから抽出を行います。

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

文字の奇数番目を取り出す方法です。

Pythonの文字列のインデックスの操作で簡単に取り出せます。
"パタトクカシーー"[0::2]
'パトカー'

これだけですね。
インデックスの最後を2とすることで
1こ飛ばしで文字を抽出できます。

02. 「パトカー」+「タクシー」=「パタトクカシーー」

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

文字を交互に組み上げる問題です。

ここから少し考えるのが必要になりそうですね。
こういうのは業務でも良く出てきそうな問題なので
覚えておくと仕事がはかどりますね。

文字列からの文字の切り出しは
インデックスを用いて
行うことができました。

単純の文字と文字を足すと
文字の連結になってしまいます。

交互にというのがポイントです。

zip関数を使うと2つの文字列やリストなどを組み合わせて
使うことができます。

''.join([p+t for p,t in zip('パトカー','タクシー')])
'パタトクカシーー'

一旦zipで1文字ずつ交互に切り出し、
それを内包表記で書いてあげるとリスト型になります。

文字列型のjoin関数でリスト型を文字列に連結できます。

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


最終的に欲しいものはリストです。

各単語に分解するというのは
文字列型のsplit関数でスペース区切りで区切ると
実現できます。

アルファベットの・・・
と書かれているので
それ以外はの文字が不要になりますね。

文字列の削除はreplace関数で実現できます。

文章を単語にしたら
欲しいのは文字数ですね。

文字列の文字数はlen関数で測ることができます。

出来上がったコードはこのような形になりました。

w = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

[len(s) for s in w.replace(',','').replace('.','').split(' ')]
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

replaceを二回続けていますが
reライブラリを用いると簡素になります。

import re

w = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

w = re.sub(r'[.,]', "", w)

[len(s) for s in w.split(' ')]
[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文字を取り出し,
取り出した文字列から単語の位置(先頭から何番目の単語か)
への 連想配列(辞書型もしくはマップ型)を作成せよ.


最終的に欲しいものは辞書型ですね。

まず単語分解は前問と同じようにsplitで行います。

次に考えることは何文字切り出すです。
これも文字列のインデックスで
実現できます。

文字の切り出しに条件がついています。
1,5,6,7,8,9,15,16,19番目だけ1文字
他は2文字

ややこしい条件ですよね。
条件分岐は IF文を用います。

文章から単語に切り分けた段階でリスト型になっています。
リスト型では順番に処理することができるので
そこにenumerate関数を加えると
何番目という数値を取ることができます。

あとは条件分岐とうまく設定すればいいです。

コードはこうなりました。

w = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."

{i+1:s[0] if i+1 in [1,5,6,7,8,9,15,16,19] else s[0:2] for i,s in enumerate(w.split(' '))}

内包表記では
FOR , IF文を組み合わせて使うことができます。

05. n-gram
与えられたシーケンス(文字列やリストなど)から
n-gramを作る関数を作成せよ.

この関数を用い,
"I am an NLPer"という文から
単語bi-gram,文字bi-gramを得よ.

n-gram
任意の文字列や文書を連続したn個の文字や単語で
分割するテキスト分割方法.

特に,nが1の場合をユニグラム(uni-gram)
2の場合をバイグラム(bi-gram)
3の場合をトライグラム(tri-gram)と呼ぶ.

ということでバイグラムを取得するための
関数を作れという問題ですね。

文字列はそのままfor文の繰り返しに用いることができます。

そこからインデックスを用いて
n文字を取得していけば良いという考えです。

コードは非常にシンプルになります。

def n_gram(st, n):
    return [st[i:i+n] for i in range(len(st)-n+1)]

for i in range(1,4):
    print(n_gram("I am an NLPer",i))
['I', ' ', 'a', 'm', ' ', 'a', 'n', ' ', 'N', 'L', 'P', 'e', 'r']
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
['I a', ' am', 'am ', 'm a', ' an', 'an ', 'n N', ' NL', 'NLP', 'LPe', 'Per']

単語単位でやりたい場合は
一旦スペースでsplitすれば
単語単位のn-gramになります。

for i in range(1,4):
    print(n_gram("I am an NLPer".split(' '),i))
[['I'], ['am'], ['an'], ['NLPer']]
[['I', 'am'], ['am', 'an'], ['an', 'NLPer']]
[['I', 'am', 'an'], ['am', 'an', 'NLPer']]

まだまだ準備運動は続きますね

今回はここまでです
それでは。