脳筋プログラミング

筋トレ大好きな初心者プログラマーの備忘録です。

言語処理100本ノック(00〜05)

勉強のために以下で今日表されている「言語処理100本ノック 2015」を順番に解いていきます。

http://www.cl.ecei.tohoku.ac.jp

将来的には「もっとスマートに書けるじゃん」となっていればいいですが、とりあえず今は泥臭くやっていこうと思います。 難しそうなところは自分なりの考察を入れて書いていきます。最初はないかな...

00.文字列の逆順

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

解答:

print("stressed"[::-1])

##########結果##########

desserts

考察:なし

01.「パタトクカシーー」

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

解答:

print("パタトクカシーー"[::2])

##########結果##########

パトカー

考察:なし

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

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

解答:

str1 = "パトカー"
str2 = "タクシー"
str3 = ""

for i in range(len(str1)):
       str3 += str1[i] + str2[i]

print(str3)

##########結果##########

パタトクカシーー

考察:全然スマートじゃないですね。調べたところ、zip関数というものがありました。(参考:2. 組み込み関数 — Python 3.6.3 ドキュメント) 入力イテラブルの中で最短のものが尽きたときに止まるようなので、lenを取る必要ものないですね。

str1 = "パトカー"
str2 = "タクシー"
str3 = ""

for a, b in zip(str1, str2):
       str3 += (a+b)

print(str3)

##########結果##########

パタトクカシーー

03. 円周率

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

解答:

import re

regex = re.compile('[^a-zA-Z]')  # (1)

str1 = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
str2 = [len(regex.sub('', i)) for i in str1.split()]  # (2)
print(str2)

##########結果##########

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

考察:ここで最初のimportですね。文中には「.」と「,」しかありませんが、その他の記号も考慮して、regexで対処します。(1)(stringのascii_lowercaseあたりを活用したかったのですが、上手い方法が見つかりませんでした...)

(2)はリスト内包表記です。意味合い的には「str1を空白でsplitしながら、「a-zA-Z」以外の文字が入って入れば削除し、その結果の文字数をリストに突っ込む」と言った感じでしょうか。

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

解答:

import re

regex = re.compile('[^a-zA-Z]')

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

str2 = [regex.sub('', i) for i in str1.split()]
number = [1, 5, 6, 7, 8, 9, 15, 16, 19]

result = {}

for i in range(len(str2)):
       if i + 1 in number:
              result[i + 1] = str2[i][:1]
       else:
              result[i + 1] = str2[i][:2]

print(result)

##########結果##########

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

考察:全然スマートじゃないですね!特に「i + 1」とかやってるあたり...(1)は先ほどの「03.円周率」と同様の方法です。調べるとenumerate関数あたりが使い勝手が良さそうです。(参考:2. 組み込み関数 — Python 3.6.3 ドキュメント

import re

regex = re.compile('[^a-zA-Z]')

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

str2 = [regex.sub('', i) for i in str1.split()]
number = [1, 5, 6, 7, 8, 9, 15, 16, 19]

result = {}

for i, a in enumerate(str2, start=1):
       if i in number:
              result[i] = a[:1]
       else:
              result[i] = a[:2]

print(result)

##########結果##########

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

気持ち悪い感じはなくなりました。が、Pythonのことだからもっと短く、もっとスマートに書けそう! ということで、こちらがよりスマートな書き方です。

import re

regex = re.compile('[^a-zA-Z]')

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

str2 = [regex.sub('', i) for i in str1.split()]
number = [1, 5, 6, 7, 8, 9, 15, 16, 19]

result = {i: a[:2-(i in number)] for i, a in enumerate(str2, start=1)}

print(result)

##########結果##########

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

辞書内包表記ですね。特に「2-(i in number)」はfor文を省略しているのでかなり簡略化しています。

最初からこの解答に辿り着くにはまだまだ勉強が必要ですね。

05. n-gram

問題:与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.

すいません、最初に申し上げておきますが、恥ずかしながら問題文の意味が全くわからなかったです。 そもそもn-gramとは何なのか、については以下のサイトで詳細に書かれていましたので、参考にさせていただきました。(参考:N-gram - Negative/Positive Thinking) そして今回の問題では、N-gramの中でも2-gram(bigram)で出力しろ、ということなんですね。

解答:

a = "I am an NLPer"

def word_ngram():
       # 単語bi-gram
       word = [b for i in [a] for b in zip(i.split(" ")[:-1], i.split(" ")[1:])]  # (1)

       return word

def text_ngram():
       # 文字bi-gram
       text = [a[b:b+2] for b in range(len(a) - 1)]

       return text

print(word_ngram())
print(text_ngram())

##########結果##########

[('I', 'am'), ('am', 'an'), ('an', 'NLPer')]
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']

(1)は内包表記の2重ループです。最初見たときはギョっとしましたが、分解すると以下と同義です。

word = []
for i in [a]:
       for b in zip(i.split()[:-1], i.split()[1:]):
              word.append(b)

i.split()[:-1]、i.split()[1:]で空白前後の単語を抽出し、それをzip関数でイテレータで返しています。

5問目でいきなり難易度が高くなっているように感じましたが、引き続きやっていきます。