kNakajima's Blog

技術系のアウトプットブログです。

「言語処理100本ノック」 第1章をPythonで解く

言語処理100本ノック

第1章の問題の自分なりの解答例を一通り貼っておきます。参考にしていただけると幸いです。

質問,ご指摘などがありましたら、コメントしてください。

言語処理100本ノック第1章

00. 文字列の逆順

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

query = 'stressed'
print(query[-1::-1])
desserts

pythonのスライスという機能を利用しました。
文字列の表示は、query[開始位置:終了位置:ステップ数] という感じでそれぞれをの値を指定します。

開始位置: -1で一番最後の文字を指定
終了位置: 何も指定しないことで、最後(先頭)の文字まで表示
ステップ数: -1を指定することで開始位置から逆順で1文字づつ表示

01. パタトクカシーー

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

query = 'パタトクカシーー'
print(query[0] + query[2] + query[4] + query[6])
パトカー

文字列の先頭を0番目として、任意の順番の文字列はquery[番号]で参照できます。

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

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

query1 = 'パトカー'
query2 = 'タクシー'
result = ''

for i in range(4):
    result += query1[i] + query2[i]
    
print(result)
パタトクカシーー

どちらの単語も4文字なので、forループ4回で終了です。

03. 円周率

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

text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
text = text.replace('.', '') #ピリオドを削除
text = text.replace(',', '') # カンマを削除
words = text.split(' ')     #空白で単語を分割

word_list = []
for word in words:
    word_list.append(len(word))

print(word_list)
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

正しく処理をすると、リストに円周率が格納される問題。

空白を区切りにして、単語に分割するだけだとカンマとピリオドが単語が直前の単語にくっついてしまいます。そのため、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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

text = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
words = text.split(" ") #空白で単語を分割

first_only = [1, 5, 6, 7, 6, 9, 15, 16, 19] #1文字目のみを取り出す単語の番号
number = 1 #番号をメモ
element = {}  #番号に対応した元素を格納する辞書

for word in words:
    if number in first_only:
        element[word[0]] = number

    else:
        element[word[0] + word[1]] = number
    
    number += 1
    
print(element)
{'H': 1, 'He': 2, 'Li': 3, 'Be': 4, 'B': 5, 'C': 6, 'N': 7, 'Ox': 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}

元素記号をkeyとして、元素番号を返す辞書を作成する問題。

まずは、単語を空白で区切り、リストにします。また、一文字目のみをkeyにするように指定されている番号をリスト first_onlyに格納にしておきます。その後、for文内でfirst_onlyに格納されている番号かを判別して、適切な元素記号をkeyとして、対応する番号が辞書 elementにされます。

05. n-gram

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

def word_n_gram(text, n): #単語nグラム, 引数: (文書, グラム数)
    word_list = text.split(" ") #空白で単語分割
    result = []  
    
    for i in range(len(word_list)-n+1):
        result.append(word_list[i:i+n:1]) #スライスを活用
        
    return result
            

def str_n_gram(text, n): #文字nグラム, 引数: (文書, グラム数)
    str_list = [ s for s in text] #一文字ずつリストに格納
    result = []  
    
    for i in range(len(str_list)-n+1):
        result.append(str_list[i:i+n:1]) #スライスを活用
        
    return result


print(word_n_gram("I am an NLPer", 2)) #単語bi-gram
print(str_n_gram("I am an NLPer", 2)) #文字bi-gram
[['I', 'am'], ['am', 'an'], ['an', 'NLPer']]
[['I', ' '], [' ', 'a'], ['a', 'm'], ['m', ' '], [' ', 'a'], ['a', 'n'], ['n', ' '], [' ', 'N'], ['N', 'L'], ['L', 'P'], ['P', 'e'], ['e', 'r']]

テキストから単語n-gramと文字n-gramを生成する関数を作成。引数にテキストとグラム数をとります。
単語分割はsplit(" ")を利用して、文字分割は内包表記を活用しました。

06. 集合

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

def str_n_gram(text, n): #文字nグラム, 引数: (文書, グラム数)
    result = []  
    
    for i in range(len(text)-n+1):
        result.append(text[i:i+n:1]) #スライスを活用
        
    return set(result) #文字n-gramを集合型で返す

query1 = "paraparaparadise"
query2 = "paragraph"

X = str_n_gram(query1, 2)
Y = str_n_gram(query2, 2)

print(X | Y) #和集合
print(X & Y) #積集合
print(X - Y) #差集合

# 'se'が集合X, Yに含まれるか
print('se' in X)
print('se' in Y)
{'gr', 'ap', 'ar', 'ag', 'di', 'ra', 'ad', 'is', 'se', 'pa', 'ph'}
{'pa', 'ap', 'ra', 'ar'}
{'is', 'di', 'ad', 'se'}
True
False

05で作成した文字n-gramを返す関数を少し書き換えて、set型で返すようにしました。

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

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

def make_template(x, y, z):
    return str(x) + '時の' + y + 'は' + str(z)

print(make_template(12, "気温", 22.4))
12時の気温は22.4

x, y, zを引数にとる関数を作成して終了。(yだけ文字列という点に注意)

08. 暗号文

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

英小文字ならば(219 - 文字コード)の文字に置換 その他の文字はそのまま出力 この関数を用い,英語のメッセージを暗号化・復号化せよ.

def cipher(word):
    result = ''
    
    for i in range(len(word)):
        if word[i].islower():
            result += chr(219 - ord(word[i]))
        else:
            result += word[i]

    return result

query = "'I can speak English' と彼女は言った"
output1 = cipher(query)
output2 = cipher(output1)

print(query) #元の文
print(output1) #暗号化
print(output2) #復号化
'I can speak English' と彼女は言った
'I xzm hkvzp Emtorhs' と彼女は言った
'I can speak English' と彼女は言った

Pythonord(文字))文字コードを求められます。文字コードからの復号はchr(文字コード)で可能です。
また、文字.islower()で英小文字かどうかの判別ができるため、これを利用して英語小文字の場合は、暗号化するように条件分岐を指定します。

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

query = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
words = query.split(" ")
result = "" 

for word in words:
    if len(word) > 4: #4文字以上はシャッフル
        shuffle_list = []
        
        for i in range(1, len(word)-1):
            shuffle_list.append(word[i]) #先頭と最後以外の文字をshuffle_listに格納
            
        random.shuffle(shuffle_list) #先頭と最後以外の文字をシャッフル
        shuffle_word = word[0] 
        
        for s in shuffle_list:
            shuffle_word += s
            
        shuffle_word += word[-1]
        result += shuffle_word 
    
    else: #4文字以下はそのまま
        result += word
    
    if word != words[-1]: #最後の単語以外は, 後ろにスペースも加える
        result += " "

print(query) #元の文書
print(result) #シャッフル後の文書
I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .
I coln'udt believe that I could atcullay uretsndand what I was rideang : the pnnohmaeel poewr of the hmaun mind .

まずは文字列を空白で区切り、リストに格納します。(今回は、ピリオドやコロンの前にも空白が入るようなので、03のような処理は不要です。)
その後、単語を一つずつ確認していき、5文字以上の単語は先頭と最後をのぞいてリストに格納し、random.shuffle()を用いて、リストをシャッフルします。
最後に、先頭の文字 + シャッフルしたリスト + 最後の文字 の順番で文字をつないで、単語を作ります。