AutomA’s blog

Pythonキチガイを目指すブログ

【Python3】言語処理100本ノック 第一章

6月から外部研修が終わり、社内でOJTとやらが始まりました。
これからは自然言語処理を教えて頂けるらしく、かの有名な「自然言語処理100本ノック2015」を解いていくのが目下のお仕事となっています。

もうそこそこ進めてしまっているのですが、とりあえずアウトプットも兼ねて1章から発表していきます。

方針としては、
Pythonらしいコードで
・ハイパフォーマンスで
・おしゃれなコード
を目標としてやっていきます。

Pythonクックブック(第二版)』によると、Pythonという言語はPythonらしく書けばおのずとハイパフォーマンスになるようになっているらしいので、ひとつ達成できれば自然と全部満たすコードになるんじゃないかな。おしゃれかは知らんけど。

ここもっとパフォーマンスあげられるよ!という部分ありましたらご指摘してもらえるとありがたいです。順次更新していきます。

では頭から順番に解いていきます。
解説はあっさりめでいきます。

第一章

00. 文字列の逆順

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

string = "stressed"
ans = string[::-1]

Pythonの魅力のひとつ、スライス処理ですね。

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

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

str1 = "パトカー"
str2 = "タクシー"
ans = "".join([i+j for i, j in zip(str1, str2)])

zip関数がでてきました。
処理部分は、リスト内包表記を使って一行で回答しています。
いけるところまでワンライナーでいくぞー。

03. 円周率

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

words = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
words = words.replace(".", "")
words = words.replace(",", "").split()
ans = [len(i) for i in words]

即堕ち2コマ。(ワンライナー失敗)
replaceって一度に複数の値を置換できないんですよね。

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

words = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
words = words.replace(".", "").split()
one = [1, 5, 6, 7, 8, 9, 15, 16, 19]
ans = {(index, element[:1 if index in one else 2]) for index, element in enumerate(words, start=1)}

enumerate関数はこういうときに便利です。startで開始番号を指定できるのがポイントです。
ただ、このコードやっやこしい!
内包表記はスッキリしますし速度も速いですが、処理が増えたときの可読性はどうなんだろう。
慣れれば読みやすくなるかな?

05. n-gram

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

def ngram(seq, n):
    return [(seq[i:i+n])for i, j in enumerate(seq)if len(seq[i:i+n]) == n]
words = "I am an NLPer"
list_ = words.split(" ")
ans_word = ngram(words, 2)
ans_list = ngram(list_, 2)

そもそもn-gramが何かすら知らなかったのでいろいろと苦労した問題。
個人的に if len(seq[i:i+n])==n の部分が場当たり的な感じがして気に入らないですね。なんとかならんだろうか

06. 集合

"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.

def ngram(seq, n):
    return {(seq[i:i+n])for i, j in enumerate(seq)if len(seq[i:i+n]) == n}
X = ngram("paraparaparadise", 2)
Y = ngram("paragraph", 2)
# 和集合
union = X&Y
# 積集合
intersection = X|Y
# 差集合
difference = X.difference(Y)
# "se"がXおよびYに含まれるかどうか
bigram_in = "se" in X and "se" in Y

集合演算は始めてやったのでカンニングしました。(白状)
ポイントは、集合演算子はset型しか受け付けないので、ngram関数の戻り値をセット内包表記に変えている点です。
「AおよびBに含まれるかどうか調べよ」という文言は複数の解釈ができそうですが、私は「Aに含まれる且つBに含まれるかどうか調べよ」と考えました。

07. テンプレートによる文生成

引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.

def temp(x, y, z):
    return str(x)+"時の"+str(y)+"は"+str(z)
x = 12
y = "気温"
z = 22.4
print(temp(x, y, z))

特に言うことはないですね。
文字列を接続するときはstr型に変換するのを忘れないでねってことくらいか。

08. 暗号文

与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.
・英小文字ならば(219 - 文字コード)の文字に置換
・その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.

def cipher(string):
    return ''.join(chr(219-ord(i)) if 'a'<=i and i<='z' else i for i in string)
string = "My power is 53,0000."
ciphered = cipher(string)
original = cipher(ciphered)

a~zの文字列のみord関数でUnicode番号に変換して、chr(219-文字コード)で暗号化。
面白いのは、暗号化された文字をふたたびchr(219-文字コード)すれば元の文字にもどることですね。

09. Typoglycemia

スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ.

import random
words = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
words = words.replace(".", "")
words = words.replace(":", "").split()
def shuffle_body(word):
    body = list(word[1:-1])
    random.shuffle(body)
    return (word[0]+"".join([i for i in body])+word[-1])
ans = [shuffle_body(i)if len(i)> 4 else i for i in words]

うーん、素直すぎただろうか。
スマートな書き方があればぜひそうしたかったが、いくら考えても思いつかなかったので断念。



以上、第一章の9問を解いてみました。
一章に限らず、「知っていれば簡単だけど知らないと難しい」という問題が多いので、Pythonの便利な処理を知る上で100本ノックはとてもいい教材だと思います。

腕試しもかねて、皆さんも解いてみてはいかがでしょうか。
そして私よりもキレイで軽いコードが書けたらぜひ教えてください!!