2008年10月5日日曜日

Python の文字列はシーケンスの一種 (1)

以前に「Python の シーケンス型に慣れる」で書いたが、「文字列」と「シーケンス」の関係を改めて見直してみる。あれから 4 ヶ月近く経過しているので、そのときとはまた違った見方になっているかもしれない。頭の中を整理したい。てゆうか、自分で書いておきながらほとんど忘れてるので再学習を。 ^^;

 

そんなことを考えるキッカケとなった簡単な例題。

IP アドレス、例えば `192.168.0.1’ があったときに、`.’ の前に `\’ を入れたい。

ちなみに、これは「Google Analytics で自分がカウントされないようにする」ための設定で使った。(もちろん、実際には IP アドレスはグローバルな値だけれど。) 設定した値が正規表現と解釈されるので、特定の IP アドレスを指定したい場合は、`.’ をエスケープしなくてはいけない。

 

文字列のメソッドを使う場合

対象を文字列として見るなら、2.3.6.1 文字列メソッド replace を使って、

ip = "192.168.0.1 "

print ip.replace('.','\.')

以下、変数 ip を使用する。

 

正規表現を使う場合

正規表現 の sub メソッド を使うなら、

import re
print re.sub(r'\.', r'\\.', ip)

後方参照を使うなら、

print re.sub(r'(\.)', r'\\\1', ip)

 

シーケンスとして考える場合

for ループを使って

文字列はシーケンスなので、for ループに投入すると「文字」ごとの処理となる。(cf. 2.3.6 シーケンス型)

result = ""
for e in ip:
    if e == "." :
        result += "\\" + e
    else:
        result += e
print result

 

map, reduce を使って

文字列を「文字のリスト」として見て、map を適用するなら、

print "".join(map(lambda x: "\." if x == "." else x, ip))

文字列を一文字ずつ見て、`.’ なら '`\.’ に変換する。(cf. Python の map, filter, reduce とリスト内包表記)

 

map で書けるなら reduce でも、

print reduce(lambda a,b: a+b if b != "." else a+"\\"+b, ip, "")

文字列なので、空の文字を先頭要素として結合していく。そのため、最後で join を使わなくて済む。

 

リスト内包表記を使って

map, reduce を使えるなら、同じことをリスト内包表記を使って、

print "".join(x if x != "." else "\\" + x for x in ip)

これは lambda を使った以下と同じかな。

print "".join((lambda x: x if x != "." else "\\" + x)(x) for x in ip)

 

再帰で考える

リスト (シーケンス) として見たてるなら、お約束通り再帰を適用して、

def escape(S):
    if len(S) > 1:
        return escape(S[0]) + escape(S[1:])
    else:
        return S if not S == "." else "\\" + S
    
print escape(ip)

(cf. Python でリストに対する再帰的な関数の適用)

 

全く意味はないけれど、以前に見た proc を使った Ruby の書き方 を真似するなら、上記の再帰を条件式を使って書換えれば、

fn = lambda(S): fn(S[0]) + fn(S[1:]) if len(S) > 1 else S if not S == "." else "\\" + S
print fn(ip)

(cf. Python の条件式)

Python の lambda 内で使えるのは「式」だけだから、代入はできないので二行に。こんなコードあったら読みたくない… (@_@;) CCC


関連記事