ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

言語処理100本ノックをやってみた「第1章: 準備運動」

f:id:taxa_program:20190227105050p:plain

こんにちは。takapy(@takapy0210)です。

本記事は、転職カウントダウンカレンダー 7日目の記事です。

www.takapy.work

はじめに

3月から自然言語処理をやる予定もあり、今更感はありますが言語処理100本ノックを少しずつやっていこうと思います。

実行環境は下記です。

OS:Mac OS Mojave
Python:Python 3.6.0

00. 文字列の逆順

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

target = 'stressed'
print(target[::-1])

スライスは[start:stop:step]の形で範囲や増分を指定する。start, stopを省略すると全体を選択し、stepを-1とすると後ろから一つずつ要素を取得することになるので[::-1]とすると逆順に並べ替えられたオブジェクトが取得できる。

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

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

target1 = 'パトカー'
target2 = 'タクシー'
result = [char1 + char2 for char1, char2 in zip(target1, target2)]
print(''.join(result))

03. 円周率

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

target = 'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'
result = []
words = target.split()
for word in words:
    result.append(len(word.strip(',.')))
print(result)

strip()は文字列の先頭・末尾の余分な文字を削除するメソッド。引数に文字列を指定するとその文字列に含まれる文字が除去される。

詳細は下記参照
docs.python.jp

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

target = 'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.'
target = target.replace('.', '')
words = target.split()
words_index = {}
for i, word in enumerate(words):
    n = i + 1 #iは0から始まるため
    l = 1 if n in [1, 5, 6, 7, 8, 9, 15, 16, 19] else 2
    words_index[word[:l]] = n
print(words_index)

enumerate()関数を使うと、forループの中でリスト(配列)などのイテラブルオブジェクトの要素と同時にインデックス番号(カウント、順番)を取得できる。

05. n-gram

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

target = 'I am an NLPer'

# n-gramを取得する関数
def generate_ngrams(text, n_gram=1):
    ngrams = zip(*[text[i:] for i in range(n_gram)])
    return [''.join(ngram) for ngram in ngrams]

# 単語bi-gram
words_target = target.split(' ')
print(generate_ngrams(words_target, 2))

# 文字bi-gram
print(generate_ngrams(target, 2))

06. 集合

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

# n-gramを取得する関数
def generate_ngrams(text, n_gram=1):
    ngrams = zip(*[text[i:] for i in range(n_gram)])
    return [''.join(ngram) for ngram in ngrams]

target1 = 'paraparaparadise'
target2 = 'paragraph'

X = generate_ngrams(target1, 2)
Y = generate_ngrams(target2, 2)

print('和集合:{}'.format(set(X) | set(Y)))
print('積集合:{}'.format(set(X) & set(Y)))
print('差集合:{}'.format(set(X) - set(Y)))

if 'se' in X: print('Xに存在')
if 'se' in Y: print('Yに存在')

set()の扱い方については下記が詳しい。

note.nkmk.me

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

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

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

print(function07(12, '気温', 22.4))

08. 暗号文

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

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

target = 'I couldn\'t believe that I could actually understand what I was reading : the phenomenal power of the human mind .'

def cipher(target):
    result = ''
    for c in target:
        if c.islower():
            result += chr(219 - ord(c))
        else:
            result += c
    return result

# 暗号化
result = cipher(target)
print('暗号化:' + result)

# 復号化
result2 = cipher(result)
print('復号化:' + result2)

ord()は1 文字の Unicode 文字を表す文字列に対し、その文字の Unicode コードポイントを表す整数を返します。例えば、 ord('a') は整数 97 を返し、 ord('€') (ユーロ記号) は 8364 を返します。これは chr() の逆です。

docs.python.jp

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

target = 'I couldn\'t believe that I could actually understand what I was reading : the phenomenal power of the human mind .'
words = target.split()
result = ''
for word in words:
    if len(word) >=5:
        head = word[0]
        tail = word[-1]
        other = word[1:-1] # 先頭と末尾以外の文字
        random_other = ''.join(random.sample(other, len(other)))
        word = head + random_other + tail
    result += word + ' '
print(result)

文字列やタプルはイミュータブル(変更不可)なので、元のオブジェクトを変更するrandom.shuffle()を使うとエラーTypeErrorになる。

文字列やタプルをシャッフルしたい場合は新たなオブジェクトを生成するrandom.sample()を使う。引数に文字列やタプルを指定した場合もrandom.sample()はリストを返すので、文字列やタプルに戻すための処理が必要。再度ひとつの文字列にするにはjoin()メソッドを使う。

以上