言語処理100本ノック 第1章: 準備運動

はじめに

今回、会社で言語処理100本ノックもくもく会をやることになったので、せっかくなら1章ずつくらいブログ化しようかなと思いました。
何番煎じがわからないですが、こうやってアウトプットすることでやらざるをえない状況に追い込むことが目的です。

100本ノックの全てのコードはGitHub上にあげる予定です。 github.com

最初なので、第1章: 準備運動をやります。

第1章: 準備運動

Python 3.7.0でやっていきます。
問題の解釈違い、間違い等ありましたら、教えていただけると幸いです。

github.com

00. 文字列の逆順

問題

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

解答&出力

string = "stressed"
print(string[::-1])
desserts

ひとこと

reversedを使うこともできますが、せっかくなのでリストのstepを指定することにしました。

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

問題

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

解答&出力

string = "パタトクカシーー"
print("".join([string[i - 1] for i in (1, 3, 5, 7)]))
パトカー

ひとこと

2文字おきという解釈をすることで、今回もリストに対してstepを指定することはできました。
が、今回は、何文字目を取り出すかという指定に変更があっても簡単に修正できるように、取り出す場所をリストで指定するようにしました。

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

問題

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

解答&出力

string1 = "パトカー"
string2 = "タクシー"
print("".join(s1+s2 for s1, s2 in zip(string1, string2)))
パタトクカシーー

ひとこと

zipを使ってそれぞれの先頭から順番に出力するようにしました。
文字列長が異なる場合の要件がわからないので考慮はしてないです。

03. 円周率

問題

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

解答&出力

import string

text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
for word in text.split(" "):
    num_alpha = len([w for w in word if w in string.ascii_letters])
    print(num_alpha)
3
1
4
1
5
9
2
6
5
3
5
8
9
7
9

ひとこと

文はsplitでスペース分割してますが、引数が与えられない場合はホワイトスペースで分割されるので、今回は特に指定しなくても問題なさそうです。
アルファベットの文字数を数えるということなので、.,を除く処理ではなく、stringモジュールを使ってアルファベットかどうかの判定をしています。
リストではなく単に縦に長く1つずつ出力してますが、ご愛嬌ということで。

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

解答&出力

text = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
target = (1, 5, 6, 7, 8, 9, 15, 16, 19)

symbols = {}
for i, word in enumerate(text.split(" ")):
    length = 1 if i + 1 in target else 2
    symbol = word[:length]
    symbols[symbol] = i + 1

print(symbols)
{'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}

ひとこと

enumerateで何番目の単語かを保持しておき、1文字取り出すのか2文字取り出すのかをリストで指定するようにしました。
12番目のマグネシウムMgではなくMiになってますが、まあ良いでしょう。

05. n-gram

問題

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

解答&出力

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


if __name__ == "__main__":
    text = "I am an NLPer"
    print(generate_n_gram(text, 2))
    print(generate_n_gram(text.split(" "), 2))
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
[['I', 'am'], ['am', 'an'], ['an', 'NLPer']]

ひとこと

リスト内包表記とリストのstartstopを使って実装しました。
最後の方のn-gramがしっかりn個組になるように、rangeはリストの長さから(n - 1)を引いてます。

06. 集合

問題

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

解答&出力

from div01.sec05 import generate_n_gram

string_x = "paraparaparadise"
string_y = "paragraph"

set_x = set(generate_n_gram(string_x, 2))
set_y = set(generate_n_gram(string_y, 2))

print(set_x.union(set_y))
print(set_x.intersection(set_y))
print(set_x.difference(set_y))

target = "se"
print(target in set_x)
print(target in set_y)
{'di', 'ph', 'ap', 'pa', 'gr', 'ra', 'ad', 'is', 'se', 'ar', 'ag'}
{'ap', 'ar', 'pa', 'ra'}
{'is', 'ad', 'di', 'se'}
True
False

ひとこと

05の関数を利用してから、単にSetを使っているだけです。

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

問題

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

解答&出力

def generate_template_str(x, y, z):
    return f"{x}時の{y}は{z}"


if __name__ == "__main__":
    x = 12
    y = "気温"
    z = 22.4
    print(generate_template_str(x, y, z))
12時の気温は22.4

ひとこと

python 3.6から使えるf-stringを使って文字列の変数展開で実装しました。

08. 暗号文

問題

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

解答&出力

def cipher(text):
    ciphered = ""
    for char in text:
        if char.islower():
            ciphered += chr(219 - ord(char))
            continue
        ciphered += char

    return ciphered


if __name__ == "__main__":
    text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
    print(cipher(text))
    print(cipher(cipher(text)))
Nld I mvvw z wirmp, zoxlslorx lu xlfihv, zugvi gsv svzeb ovxgfivh rmeloermt jfzmgfn nvxszmrxh.
Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.

ひとこと

問題文の通りに文字コードを引いて置換しました。
英語のメッセージは03のを用いて、暗号化の確認と、暗号化後のメッセージからの復号化の確認ができました。

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

text = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
output_text = []

for word in text.split(" "):
    if len(word) > 4:
        middle = list(word[1:-1])
        random.shuffle(middle)
        word = word[0] + "".join(middle) + word[-1]
    output_text.append(word)

print(" ".join(output_text))
I cnldou't belviee that I culod aulcalty urtaensndd what I was redanig : the pnmeeahonl peowr of the huamn mind .

ひとこと

リストのstartstopを使って先頭と末尾以外の中央部分を抽出し、randomモジュールのshuffleを使って破壊的に並び替え、先頭と末尾の文字と連結しています。
先頭と末尾さえ固定されていれば、単語として認識できるのって面白いですね。

おわりに

準備運動編ということでPythonを知っていれば簡単な問題だった気がします。
ただ、以降の問題を眺めていると明らかに難易度が上がっているので、挫折しないように取り組めたらと思います。

続き

ohshige.hatenablog.com