2017年9月17日日曜日

【Python】「言語処理100本ノック 2015」を始めてみました

夏の一コマ
α6500 & SIGMA 35mm F1.4 DG HSM Art

 言語処理100本ノック 2015を始めてみました。
 まずは「第1章: 準備運動」からですが、準備運動でもプログラミング素人にはなかなか大変です。
 環境は、Windows10 + Anaconda3.4.4 です。

00. 文字列の逆順

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

s = "stressed"
ans = s[::-1]
print(ans)

# > desserts

文字列のスライスのステップを使ってみました。
ステップに-数値を指定すると逆順にステップします。
ちなみに、スライスで指定する数字は、要素(文字列なら文字)の位置ではなく、要素と要素の間(ナイフを入れて切るところ)と考えると理解しやすかったです。

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

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

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

# > パトカー

むしろこちらの方が1問目に相応しいような気がする問題。
文字列のスライスのステップを知っていれば簡単ですね。

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

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

s1 = "パトカー"
s2 = "タクシー"
ans = "".join([c1 + c2 for c1, c2 in zip(s1, s2)])
print(ans)

# > パタトクカシーー

リスト内包表記を使用しています。for文より処理速度が速いとのことなので、使えるときは内包表記を使用するように心がけています。
“区切り文字列”.join(文字列のリスト)で区切り文字列を挟んで文字列のリストを結合した文字列を得られます(上の例は空文字列で結合)。
zip()は複数のシーケンスオブジェクトを同時にループしたいときに便利。要素数が違う場合は一番少ないものに合わせられます。

03. 円周率

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

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

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

文字列をsplit()で区切ってリストにしてから”,”と”.”をstrip()で除去。リスト内包表記でリスト化しています。
“文字列A”.split(“区切り文字”)で文字列Aを区切り文字列で区切った文字列のリストを得ることができます(区切り文字列は除去される)。区切り文字を指定しない場合は空白文字(半角スペース、全角スペース、改行、改ページ、タブ、垂直タブ、復帰)が区切り文字として扱われます。
strip()は文字列の両端から指定した文字列を削除するメソッドです。
len()はシーケンスの要素数を返す関数ですね。

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

s = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
ans = {word[:1] if i in [1, 5, 6, 7, 8, 9, 15, 16, 19] else word[:2]: i for i, word in enumerate(s.split(), start=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}

split()メソッドでリスト化してから条件式(三項演算子)と内包表記で辞書型にまとめています。
enumerate関数はfor文でループ処理をするときカウンターと要素の両方を取得できる関数です。startでカウンターの初期値を指定できます(何も指定しないと0からスタート)。

05. n-gram

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

# 単語n-gram
def w_n_gram(n, text):
    words = [word.strip(",.") for word in text.split()]
    return [words[i:i + n] for i in range(len(words) - n + 1)]

# 文字n-gram
def c_n_gram(n, text):
    return [text[i:i + n] for i in range(len(text) - n + 1)]

s = "I am an NLPer"
ans1 = c_n_gram(2, s)
ans2 = w_n_gram(2, s)
print(ans1)
print(ans2)

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

n-gramの意味が分からなくて検索してしまいました。
問題で指定された文字列だけでいうなら、単語n-gramのstrip(“,.”)は要らないですね。逆により汎用化するなら”;”や”:”など必要な記号を列記する必要があります。

06. 集合

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

s1 = "paraparaparadise"
s2 = "paragraph"

# 文字bi-gram
def get_bi_gram(text):
    return {text[i:i + 2] for i in range(len(text) - 1)}

x = get_bi_gram(s1)
y = get_bi_gram(s2)

print("X: {0}".format(x))
print("Y: {0}".format(y))
print("和集合: {0}".format(x | y))
print("積集合: {0}".format(x & y))
print("差集合: {0}".format(x - y))
print("Xに'se'は含まれるか? {0}".format('se' in x))
print("Yに'se'は含まれるか? {0}".format('se' in y))

# > X: {'ad', 'ra', 'is', 'ar', 'se', 'ap', 'pa', 'di'}
# > Y: {'ra', 'ag', 'ar', 'ap', 'ph', 'pa', 'gr'}
# > 和集合: {'is', 'se', 'gr', 'ad', 'ra', 'ag', 'ar', 'ap', 'ph', 'pa', 'di'}
# > 積集合: {'pa', 'ap', 'ar', 'ra'}
# > 差集合: {'ad', 'is', 'di', 'se'}
# > Xに'se'は含まれるか? True
# > Yに'se'は含まれるか? False

題名のとおり集合型を使う問題ですね。
今回はbi-gram限定ですので、集合型のbi-gramを返す関数を作って対応しています。内包表記を使えば一行で済むものなので、関数にする必要性も薄いような気もしますが。
あとは集合型の演算方法さえ知っていればOK。

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

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

def xyz(x, y, z):
    return "{0}時の{1}は{2}".format(x, y, z)

x = 12
y = "気温"
z = 22.4
print(xyz(x, y, z))

# > 12時の気温は22.4

なんだか拍子抜けする問題。
format()メソッドの使用方法さえ知っていれば簡単。

08. 暗号文

与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.

  • 英小文字ならば(219 - 文字コード)の文字に置換
  • その他の文字はそのまま出力

この関数を用い,英語のメッセージを暗号化・復号化せよ.

def cipher(text):
    return "".join([chr(219-ord(c)) if c.islower() else c for c in text])

s = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind."
s_ciphered = cipher(s)
print("原文: {0}".format(s))
print("暗号化後: {0}".format(s_ciphered))
print("復号化後: {0}".format(cipher(s_ciphered)))

# > 原文: I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind.
# > 暗号化後: I xlfowm'g yvorvev gszg I xlfow zxgfzoob fmwvihgzmw dszg I dzh ivzwrmt : gsv ksvmlnvmzo kldvi lu gsv sfnzm nrmw.
# > 復号化後: I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind.

リスト内包表記と条件式(三項演算子)で文字列を1文字ずつ指定の処理をしてリスト化したものをjoin()メソッドで結合しています。
islower()は文字列がアルファベットの小文字かどうかを判定するメソッド。
chrは文字コードから文字を返す関数、ord()は文字から文字コードを返す関数です。

09. Typoglycemia

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

from random import shuffle

def randomize_text(text):
    words = [randomize_word(word) for word in text.split()]
    return " ".join(words)

def randomize_word(word):
    if len(word) <= 4:
        return word
    else:
        mid_char = [c for c in word[1:-1]]
        shuffle(mid_char)
        return word[:1] + "".join(mid_char) + word[-1:]

s = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
print(randomize_text(s))

# > I cd'nolut bvlieee that I cluod atlaulcy unendatsrd what I was radneig : the pomnaheenl pweor of the hmuan mind .

問題文から”.”や”:”もスペースで区切られていることを前提に作成。
まずsplit()で文を単語のリスト化。
次に4文字以上の単語の先頭と末尾を除いたものを文字のリストとしてから、randomモジュールのshuffleを使ってランダムに並び替え。
先頭と末尾を戻して再結合して単語を作り、最後にスペースを区切りにして単語のリストを結合して文に再構築しています。

0 件のコメント: