言語処理100本ノック2020版が公開されたのでこの機会に解いていく。
自分のjupyter notebookをmarkdownに起こしたものなので,説明は少なくなってしまっています。少しでも参考になればと思い,ブログとGitHubにまとめる予定のものをQiitaにも投稿してみました。
こんな素晴らしい教材を提供していただける先生方に頭が上がりません。
<他の章>
五章以降は終了次第追加予定。
第1章:準備運動
(2020/04/15 訂正)
"言語処理100本ノック2020年版が公開!どこが変わったの?"の作者である@hi-asanoさんのご指摘で改正しました。
04の内包表記も時間があるときに追記しようと思います。
00.文字列の逆順
文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
def reverse_strings(s):
return s[::-1]
print(reverse_strings("stressed"))
desserts
01.パタトクカシーー
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ
def extract_strings(s):
return s[::2]
print(extract_strings("パタトクカシーー"))
パトカー
02.「パトカー」+「タクシー」=「パタトクカシーー」
「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.
# 同じ長さの文字列でしか使えない
def connect_strings(sone, stwo):
result = "".join(s1+s2 for s1,s2 in zip(sone, stwo))
return result
print(connect_strings("パトカー", "タクシー"))
パタトクカシーー
03. 円周率
“Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.”という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.
出力結果が円周率になる。
綺麗に書くのに迷ったが,正規表現でコンマとピリオドを除去するこでできるだけ短くしたつもり。
文字数カウントはmap
を用いてfor文を回さずに処理。
import re
def circumference(s):
splited = re.split('\s', re.sub(r'[,.]', '', s))
words_len = list(map(len, splited))
return words_len
sentence = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
print(circumference(sentence))
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]
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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ
zipや内包表記を使ってもっと綺麗に書けるはず。いい方法あれば教えていただけると幸いです。。.
def element_symbol(s, number):
out_dict = {}
splited = re.split('\s', s)
for i, w in enumerate(splited, start=1):
if i in number:
out_dict[w[:1]] = i
else :
out_dict[w[:2]] = i
return out_dict
sentence = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
only_first_number = [1, 5, 6, 7, 8, 9, 15, 16, 19]
print(element_symbol(sentence, only_first_number))
{'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}
05.n-gram
与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,”I am an NLPer”という文から単語bi-gram,文字bi-gramを得よ.
N-gramとは,wikiによると下記
検索対象を単語単位ではなく文字単位で分解し、後続の N-1 文字を含めた状態で出現頻度を求める方法。 Nの値が1なら「ユニグラム(英: uni-gram)」、2なら「バイグラム(英: bi-gram)」、3なら「トライグラム(英: tri-gram)」と呼ばれる。
今回は,単語n-gramと文字n-gramを別の関数として実装することにした。 与える文字列をsplit
で区切り,リストとして渡せば汎用的に使えた。
def generate_ngram(sentence, N):
return [sentence[i:i+N] for i in range(len(sentence) - N + 1)]
input_text = "I am an NLPer"
print("単語bi-gram : " + str(generate_ngram(input_text.split(' '), 2)))
print("文字bi-gram : " + str(generate_ngram(input_text, 2)))
単語bi-gram : [['I', 'am'], ['am', 'an'], ['an', 'NLPer']]
文字bi-gram : ['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
06. 集合
“paraparaparadise”と”paragraph”に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,’se’というbi-gramがXおよびYに含まれるかどうかを調べよ.
X_text = "paraparaparadise"
Y_text = "paragraph"
X = set(generate_ngram(X_text, 2))
Y = set(generate_ngram(Y_text, 2))
print("和集合 : " + str(X.union(Y)))
print("積集合 : " + str(X.intersection(Y)))
print("差集合 : " + str(X.difference(Y)))
print("seがXに含まれるか : " + str('se' in X))
print("seがYに含まれるか : " + str('se' in Y))
和集合 : {'pa', 'di', 'ph', 'gr', 'is', 'ag', 'ra', 'ad', 'se', 'ap', 'ar'}
積集合 : {'ap', 'pa', 'ra', 'ar'}
差集合 : {'se', 'is', 'di', 'ad'}
seがXに含まれるか : True
seがYに含まれるか : False
07.テンプレートによる文生成
引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y=”気温”, z=22.4として,実行結果を確認せよ.
python 3.6以前だとf-string
を用いることができないので注意.逆にいうとそれ以前の記事だとf-string
を用いていない。基本情報はここ。
def generate_temp(x, y, z):
return f"{x}時の{y}は{z}"
print(generate_temp(12, "気温", 22.4))
12時の気温は22.4
08.暗号文
与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.
・ 英小文字ならば(219 - 文字コード)の文字に置換
・その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.
組み込み関数ord()
で文字のUnicodeコードポイントを取得できるらしいので使ってみた。
小文字に一致するものを変換すればいいので,正規表現で置換するのが早い。
def chipher(s):
result = ""
for character in s:
result += re.sub(r'[a-z]', chr(219 - ord(character)), character)
return result
sentence = "Hi, Thank you for reading my article!!"
print(chipher(sentence))
print(chipher(chipher(sentence)))
Hr, Tszmp blf uli ivzwrmt nb zigrxov!!
Hi, Thank you for reading my article!!
@suzu6さんがlambda
式を使い,もっと綺麗に解かれていた。というより,正規表現で置換するのにfor
ループを回すのはあまり良くなかった。
以下の例では,mがマッチオブジェクトであり,マッチした文字列を取得するのにgroup()
を使用する必要がある。group(0)
でマッチした文字列全体を取得する。
def cipher(src):
return re.sub(r'[a-z]', lambda m: chr(219 - ord(m.group(0))), src)
text = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
# 暗号化
print(cipher(text))
Hr Hv Lrvw Bvxzfhv Blilm Clfow Nlg Ocrwrav Foflirmv. Nvd Nzgrlmh Mrtsg Aohl Srtm Pvzxv Svxfirgb Cozfhv. Aigsfi Krmt Czm.
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
def mixing_word(sentence):
splited = sentence.split(" ")
randomed_list = [ s[0] + ''.join(random.sample(s[1:-1], len(s)-2)) + s[-1] if len(s) >= 4 else s for s in splited]
return " ".join(randomed_list)
input_text = "I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind."
mixing_word(input_text)
'I cndolu’t beveile taht I colud aulltacy unndtserad what I was raednig : the penhaneoml pwoer of the hmuan mdin.'
最後に
競プロをやっていない独学でプログラミングを学習したバイオ系の人間なので,どこかおかしいところがありましたら教えていただけると幸いです。