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

自然言語処理の問題集として有名な言語処理100本ノックの2020年版の解答例です。 この記事では、以下の第1章から第10章のうち、「第1章: 準備運動」を解いてみた結果をまとめています。

環境設定

コードの実行はGoogle Colaboratoryで行います。 以降の解答の実行結果をすべて含むノートブックは、以下のリンクから直接参照することができます。

Open In Colab

第1章: 準備運動

00. 文字列の逆順

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

スライスは[開始位置:終了位置:移動幅]を指定して文字列を抽出します。このとき、開始位置を省略すると先頭から、終了位置を省略すると末尾までの範囲となります。また、移動幅にマイナスを指定すると、終了位置から順番に抽出されます。

str = 'stressed'
ans = str[::-1]  # 「先頭」から「末尾」まで「逆順の移動幅1」で
ans
--- 出力 ---
desserts

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

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

str = 'パタトクカシーー'
ans = str[::2]  # 「先頭」から「末尾」まで「移動幅2」で
ans
--- 出力 ---
パトカー

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

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

str1 = 'パトカー'
str2 = 'タクシー'
ans = ''.join([i + j for i, j in zip(str1, str2)])  # str1とstr2を同時にループ
ans
--- 出力 ---
パタトクカシーー

03. 円周率

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

import re

str = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
str = re.sub('[,\.]', '', str)  # ,と.を除去
splits = str.split()  # スペースで区切って単語ごとのリストを作成
ans = [len(i) for i in splits]
ans
--- 出力 ---
[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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

str = 'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.'
splits = str.split()
one_ch = [1, 5, 6, 7, 8, 9, 15, 16, 19]  # 1文字を取り出す単語の番号リスト
ans = {}
for i, word in enumerate(splits):
  if i + 1 in one_ch:
    ans[word[:1]] = i + 1  # リストにあれば1文字を取得
  else:
    ans[word[:2]] = i + 1  # なければ2文字を取得
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}

05. n-gram

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

ここでは、[lst[i:] for i in range(n)]で、1要素目始まり、2要素目始まり、…、n要素目始まりの要素列のリストを作成し、それらをzip関数に渡して前から順番に要素を抽出することで、連続するn要素からなる組を作っています。 なお、[lst[i:] for i in range(n)]の結果は1つのリストであるため、前に*をつけることでリストを展開し、それぞれの要素列に分けた上でzip関数に渡す必要があります。

def ngram(n, lst):
  # ex.
  # [str[i:] for i in range(2)] -> ['I am an NLPer', ' am an NLPer']
  # zip(*[str[i:] for i in range(2)]) -> zip('I am an NLPer', ' am an NLPer')
  return list(zip(*[lst[i:] for i in range(n)]))

str = 'I am an NLPer'
words_bi_gram = ngram(2, str.split())
chars_bi_gram = ngram(2, str)

print('単語bi-gram:', words_bi_gram)
print('文字bi-gram:', chars_bi_gram)
--- 出力 ---
単語bi-gram: [('am', 'an'), ('I', 'am'), ('an', 'NLPer')]
文字bi-gram: [('I', ' '), (' ', 'N'), ('e', 'r'), ('a', 'm'), (' ', 'a'), ('n', ' '), ('L', 'P'), ('m', ' '), ('P', 'e'), ('N', 'L'), ('a', 'n')]

06. 集合

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

前問で作成した関数ngramを利用します。また、返り値をset型に変換することで、重複があれば削除され、集合として扱うことができるようになります。

str1 = 'paraparaparadise'
str2 = 'paragraph'
X = set(ngram(2, str1))
Y = set(ngram(2, str2))
union = X | Y
intersection = X & Y
difference = X - Y

print('X:', X)
print('Y:', Y)
print('和集合:', union)
print('積集合:', intersection)
print('差集合:', difference)
print('Xにseが含まれるか:', {('s', 'e')} <= X)
print('Yにseが含まれるか:', {('s', 'e')} <= Y)
--- 出力 ---
X: {('a', 'r'), ('a', 'p'), ('s', 'e'), ('p', 'a'), ('r', 'a'), ('i', 's'), ('d', 'i'), ('a', 'd')}
Y: {('p', 'h'), ('a', 'r'), ('a', 'p'), ('p', 'a'), ('g', 'r'), ('r', 'a'), ('a', 'g')}
和集合: {('p', 'h'), ('a', 'r'), ('a', 'p'), ('s', 'e'), ('p', 'a'), ('g', 'r'), ('r', 'a'), ('i', 's'), ('a', 'g'), ('d', 'i'), ('a', 'd')}
積集合: {('p', 'a'), ('r', 'a'), ('a', 'r'), ('a', 'p')}
差集合: {('d', 'i'), ('i', 's'), ('a', 'd'), ('s', 'e')}
Xにseが含まれるか: True
Yにseが含まれるか: False

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

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

def generate_sentence(x, y, z):
  print(f'{x}時のとき{y}は{z}')

generate_sentence(12, '気温', 22.4)
--- 出力 ---
12時のとき気温は22.4

08. 暗号文

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

def cipher(str):
  rep = [chr(219 - ord(x)) if x.islower() else x for x in str]
  
  return ''.join(rep)

message = 'the quick brown fox jumps over the lazy dog'
message = cipher(message)
print('暗号化:', message)
message = cipher(message)
print('復号化:', message)
--- 出力 ---
暗号化: gsv jfrxp yildm ulc qfnkh levi gsv ozab wlt
復号化: the quick brown fox jumps over the lazy dog

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 shuffle(words):
  result = []
  for word in words.split():
    if len(word) > 4:  # 長さが4超であればシャッフル
      word = word[:1] + ''.join(random.sample(word[1:-1], len(word) - 2)) + word[-1:]
    result.append(word)

  return ' '.join(result)

words = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
ans = shuffle(words)
ans
--- 出力 ---
I conuld't bivelee that I cloud actlaluy utnensardd what I was radineg : the pheanomnel peowr of the hmaun mind .

理解を深めるためのオススメ教材

全100問の解答はこちら