読者です 読者をやめる 読者になる 読者になる

歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Python】R言語を勉強するために「言語処理100本ノック」をPythonで解いてみる

自然言語処理 Python

会社の先輩から「これ使ったらいい感じにデータ取ってこれるよ」と渡されたものが、 dplyrとかstringrとかよくわからないパッケージをガンガン導入した自由奔放なRのコードで困っています。 R言語自体にも慣れていないため、 「コードを使って何を表現したいのか」と「そのRのパッケージで何をできるのか(またどういう使いどころなのか)」を両方読み解かなければならず、けっこう大変です。

そのコードの中で「いくつか関数の入ったリストを高階関数に渡してfilterかける」ような操作をしていてしっかり読み解いてみたいのですが、 そのコードで取ってきたデータを使いたいだけなので、どうしても後回しにしてしまいます。 というわけで、Rの勉強は家でやることにしました。

「言語処理100本ノック」として、ちょうど勉強したいライブラリ(dplyrなど)をいい感じに使ってるRのコードがあったので、 これを題材にしようと思います。 こちらのリンク先にRのコードがあります。

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

東北大学の研究室が例題集のようです。 Pythonで同じ例題を解いた後、そのライブラリが何をやるものなのか考えてみます。

ちなみにPythonのバージョンは3.4.3です。

00. 文字列の逆順

TASK_STRING_00 = "stressed"
print(TASK_STRING_00[::-1])

Pythonは文字列もリストっぽく扱え、リストと同様にスライスを使うことができます。

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

from functools import reduce

TASK_STRING_01 = "パタトクカシーー"
TASK_INDEX_01 = [0, 2, 4, 6]

# パイプ(%>%)前までは文字列のリストを作っているらしい
before_pipe = [TASK_STRING_01[x] for x in TASK_INDEX_01]
# リスト内の文字を結合
print(reduce(lambda x,y: x+y, before_pipe))

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

自分はこんなふうに解きました。

from functools import reduce

TASK_VEC_02 = ["パトカー", "タクシー"]
text_list = [x + y 
               for i_x, x in enumerate(TASK_VEC_02[0]) 
               for i_y, y in enumerate(TASK_VEC_02[1]) 
               if i_x == i_y]
# => ['パタ', 'トク', 'カシ', 'ーー']

reduce(lambda x,y: x+y, text_list)

元のRのコードだと、

     [,1] [,2] [,3] [,4]
[1,] "パ" "ト" "カ" "ー"
[2,] "タ" "ク" "シ" "ー"

ってマトリックスをstringr::str_cに渡して実現してるので、stringr::str_cの挙動を深く知ってないと読めない気がします…。

03. 円周率

TASK_STRING_03 = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

# ドットとコンマを削除
before_pipe = re.sub(r'[,\.]', '', TASK_STRING_03)

# スペースで区切って各単語の長さをとる
print([len(x) for x in before_pipe.split(' ')])

04. 元素記号

import re

TASK_INDEX_04 = [0, 4, 5, 6, 7, 8, 14, 15, 18]
TASK_STRING_04 = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."

# ドットとコンマを削除する(必要無いけど)
txt_remove_dots = re.sub(r'[,\.]', '', TASK_STRING_04)

# インデックスによって先頭の1文字か2文字を返す関数
def get_head_string(string, index):
    if index in TASK_INDEX_04:
        return string[0]
    else:
        return string[:2]

# リスト内包表記で連想配列に適用
answer = {i: get_head_string(x, i) for i, x in enumerate(txt_remove_dots.split(' '))}
print(answer)

05. n-gram

import re

# 文字bi-gramを得る関数
def char_bi_gram(string):
    # 空白等を削除
    string_sub = re.sub(r'[,\. ]', '', string)
    return [[x , y]
            for i_x, x in enumerate(string_sub)
            for i_y, y in enumerate(string_sub)
            if i_x == i_y - 1]
print(char_bi_gram('I am an NLPer'))
# => [['I', 'a'], ['a', 'm'], ['m', 'a'], ['a', 'n'], ['n', 'N'], ['N', 'L'], ['L', 'P'], ['P', 'e'], ['e', 'r']]

# 単語bi-gramを得る関数
def str_bi_gram(string):
    # 空白で区切る
    string_split = string.split(' ')
    return [[x , y]
            for i_x, x in enumerate(string_split)
            for i_y, y in enumerate(string_split)
            if i_x == i_y - 1]
print(str_bi_gram('I am an NLPer'))
# => [['I', 'am'], ['am', 'an'], ['an', 'NLPer']]

06. 集合

Pythonはset型があるからチートだと思う…。

from functools import reduce

TASK_STRING_06_X = "paraparaparadise"
TASK_STRING_06_Y = "paragraph"

# 文字bi-gramを得る関数(さっき定義したやつ)
def char_bi_gram(string):
    # 空白等を削除
    string_sub = re.sub(r'[,\. ]', '', string)
    return [[x , y]
            for i_x, x in enumerate(string_sub)
            for i_y, y in enumerate(string_sub)
            if i_x == i_y - 1]

# 文字bi-gramを得る関数の戻り値を['a', 'b'] => 'ab'に変換
def make_bigram_set(string):
    return set(reduce(lambda x,y: x+y, str_list) 
               for str_list in char_bi_gram(string))
X = make_bigram_set(TASK_STRING_06_X)
Y = make_bigram_set(TASK_STRING_06_Y)

# 和集合
print(X | Y)
# => {'ap', 'ag', 'is', 'ph', 'gr', 'ad', 'ra', 'pa', 'di', 'se', 'ar'}

# 積集合
print(X & Y)
# => {'ra', 'pa', 'ap', 'ar'}

# 差集合
print(set(filter(lambda x: not x in Y, X)))
# => {'di', 'se', 'ad', 'is'}

# XとYそれぞれに"se"が含まれるかどうか
print('se' in X)
# => True
print('se' in Y)
# => False

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

def displayTimeMessage(x, y, z):
    return "%d時の%sは%s" %(int(x), str(y), str(z))

x = 12
y = "気温"
z = 22.4

print(displayTimeMessage(x,y,z))
# => 12時の気温は22.4

08. 暗号文

import re
def cipher(string):
    def cipher_char(chara):
        if re.match(r'[a-z]', chara):
            return chr(219-ord(chara))
        else:
            return chara
    return reduce(lambda x,y: x+y, map(cipher_char, string))

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

09. Typoglycemia

import random
from functools import reduce

# 乱数種の指定
random.seed(1)

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

def shuffle_word(word):
    # 長さが4以下の単語は並び替えない
    if len(word) <= 4:
        return word
    # 文字列を並べ替える(破壊的操作)
    word_list = list(word)
    random.shuffle(word_list)
    return reduce(lambda x,y: x+y, word_list)

print(' '.join(map(shuffle_word, EX_STRING_09.split())))
# => I l'ontcdu lebeevi that I udolc luaaclty radtnsendu what I was nragdei : the olhpeneanm erpwo of the uhman mind .

TODO::

先輩、Pythonのほうが楽じゃないっすか…?

後半Rのコードのほうをちゃんと終えてないので、もう一回読んでみます。

Pythonで書いたコードでいうと、こっちのコードのほうがイケてる。 qiita.com

差集合って X - Y で出せるんだ。