転置インデックスによる検索システムを作ってみた
転置インデックスによる検索システムを作ってみよう! にインスパイアされて作ってみました。
検索記事は
[記事ID][SPC][記事内容]\n
検索対象ファイルとして
1 これはペンです 2 最近はどうですか? 3 ペンギン大好き 4 こんにちは。いかがおすごしですか? 5 ここ最近疲れ気味 6 ペンキ塗りたてで気味が悪いです 7 ペンペンペンペン
という内容のtest.txt用意しました。
インデックス
n-gramをkeyとして、各記事のtf(記事中のn-gram出現頻度)と記事IDのタプルをtf降順にsortしたリストを登録した辞書
index[n-gram] => [(tf, 記事ID), ...] #タプルはtf降順にsortしておく
をcPickleでシリアライズしたものをインデックスファイルとして使うことにします。
cPickleを使うと
Pythonのオブジェクト -> pickle -> 文字列
という変換をして、変換した文字列から
文字列 -> pickle -> Pythonのオブジェクト
といった具合に元のオブジェクトに戻すことができます。
具体的に書くと
#pickletest.py import cPickle a = range(5) print a obj = cPickle.dumps(a) print obj b = cPickle.loads(obj) print b a = {} for i in range(5): a[i] = str(i**10) print a obj = cPickle.dumps(a) print obj b = cPickle.loads(obj) print b
> python pickletest.py [0, 1, 2, 3, 4] (lp1 I0 aI1 aI2 aI3 aI4 a. [0, 1, 2, 3, 4] {0: '0', 1: '1', 2: '1024', 3: '59049', 4: '1048576'} (dp1 I0 S'0' sI1 S'1' sI2 S'1024' p2 sI3 S'59049' p3 sI4 S'1048576' p4 s. {0: '0', 1: '1', 2: '1024', 3: '59049', 4: '1048576'}
といったようなことが可能です。
以上のことを踏まえると、インデックスを作るプログラム、makeindex.pyを以下のようになります。
#!/usr/bin/python # -*- coding: utf-8 -*- import sys import codecs import cPickle # n-gramのn n = int(sys.argv[1]) # インデックス作成対象ファイルの名前 input_file = sys.argv[2] index = {} doc_num = 0 # 文字コードを指定してファイルを開き、一行ずつ読み込む for line in codecs.open(input_file, 'r', 'utf-8'): d = {} # 読み込んだ行を記事IDと記事に分解 l = line.split(' ') id = int(l[0]) doc = l[1][:-1] # 記事の文字数回ループ for i in range(len(doc)): # 記事のi文字目からn文字をn-gramとする ngram = doc[i:i+n] try: # 既に出現していたら出現回数+1 d[ngram] += 1 except: # 初めてなら出現回数1 d[ngram] = 1 # 記事に出現したn-gram全てに対して... for ng in d: # (tf, 記事ID) というタプルを作る # tf = n-gramが記事に出現した回数 / 記事の文字数 t = (1.0*d[ng]/len(doc), id) try: # 他の記事で既に出現していたら単純に追加 index[ng].append(t) # tf降順でsort index[ng].sort(reverse=True) except: # 初めて出現したn-gramならリストを作って自分を追加 index[ng] = [t] # 後でtf-idfを計算するために記事の数を覚えておく doc_num += 1 # indexに記事の数を登録 index['__DOC_NUM__'] = doc_num # indexをcPickleで文字列に変換してファイルに書き込む file(input_file + '-' + str(n) + 'g.idx', 'w').write(cPickle.dumps(index))
例えばtest.txtの2gram単位のインデックスファイルを作りたい場合は
> ./makeindex.py 2 test.txt
というように実行することで、test.txt-2g.idxというインデックスファイルが作成されます。
検索
検索を行うプログラム、search.pyは以下のようになります。
2007/12/05追記: 記事のスコアにtf-idfを加算するところを間違えていたので修正しました。すいません……。この修正により、最後の検索結果も変わります。
#!/usr/bin/python # -*- coding: utf-8 -*- import sys import codecs import cPickle import math # インデックスファイルの名前 index_file = sys.argv[1] # インデックスファイルを読み込み、cPickleで文字列から辞書に変換 index = cPickle.loads(file(index_file).read()) # インデックスファイルの名前からn-gramのnを決定 n = int(index_file.split('-')[-1][0]) # 標準入力の文字コードを指定 sys.stdin = codecs.getreader('utf-8')(sys.stdin) # 検索結果のスコアを登録する辞書を用意 result = {} # 標準入力から検索語を読み込む for line in sys.stdin: print line, # 検索語の文字数回ループ for i in range(len(line)): # 検索語のi文字目からn文字をn-gramとする ngram = line[i:i+n] # n-gramが出現した記事があるなら... if ngram in index: # indexから記事数取得 doc_num = index['__DOC_NUM__'] # n-gramが出現した記事のリスト取得 elems = index[ngram] # df算出 # df = n-gramが出現した記事数 = n-gramが出現した記事のリストの長さ df = len(elems) + 1 # n-gramが出現した記事のリストに対して... for tf, id in elems: # n-gramに対する記事のtf-idf算出 # tf-idf = tf * log(総記事数 / n-gramが出現した記事数) tfidf = tf * math.log(1.0*doc_num/df) try: # 既に他のn-gramでスコアが算出されていたらtf-idfを加算 # 修正前: # score = resul[id] <- resultのtypo(ここで例外発生)。タプルをそのままscoreとするのも間違い。 # result[id] += (score + tfidf, id) <- タプルにタプルを足している……! # 2007/12/05修正: # タプルの最初の要素をスコアとして、tfidfを加算するのが正しい score = result[id][0] result[id] = (score + tfidf, id) except: # 記事IDのスコアをtf-idfとして登録 result[id] = (tfidf, id) # 辞書resultを値だけのリストにして、スコア降順にソート for score, id in sorted(result.values(), reverse=True): # 結果出力 print 'ID:' + str(id), 'SCORE:' + str(score)
基本的には
という流れになっています。
実際に使ってみると以下のようになります。
> echo '最近ペンギンが好き' | ./search.py test.txt-2g.idx 最近ペンギンが好き ID:3 SCORE:0.178966138356 ID:7 SCORE:0.168236118311 ID:5 SCORE:0.105912232548 ID:2 SCORE:0.0941442067097 ID:1 SCORE:0.0480674623745 ID:6 SCORE:0.0224314824414
> echo 'ペンギン' | ./search.py test.txt-2g.idx ペンギン ID:3 SCORE:0.178966138356 ID:7 SCORE:0.168236118311 ID:1 SCORE:0.0480674623745 ID:6 SCORE:0.0224314824414
> echo 'ペン' | ./search.py test.txt-2g.idx ペン ID:7 SCORE:0.168236118311 ID:3 SCORE:0.0480674623745 ID:1 SCORE:0.0480674623745 ID:6 SCORE:0.0224314824414
> echo 'ペ' | ./search.py test.txt-2g.idx ペ
2gram単位のインデックスなので、一文字では検索できませんでした。こういう場合は…… えーっと、どうするのかな……。
2007/12/05追記: '最近ペンギンが好き' で検索しても 'ペンギン' で検索しても 記事ID:3のスコアが同じことから、上の結果はおかしいことがわかります。修正したsearch.pyで検索すると以下のような結果になります。
> echo '最近ペンギンが好き' | ./search.py test.txt-2g.idx 最近ペンギンが好き ID:3 SCORE:0.584965877444 ID:7 SCORE:0.168236118311 ID:5 SCORE:0.105912232548 ID:2 SCORE:0.0941442067097 ID:1 SCORE:0.0480674623745 ID:6 SCORE:0.0224314824414
> echo 'ペンギン' | ./search.py test.txt-2g.idx ペンギン ID:3 SCORE:0.405999739087 ID:7 SCORE:0.168236118311 ID:1 SCORE:0.0480674623745 ID:6 SCORE:0.0224314824414
> echo 'ペン' | ./search.py test.txt-2g.idx ペン ID:7 SCORE:0.168236118311 ID:3 SCORE:0.0480674623745 ID:1 SCORE:0.0480674623745 ID:6 SCORE:0.0224314824414
> echo 'ペ' | ./search.py test.txt-2g.idx ペ
簡単なProxyを作ってみた
参考資料
参考資料ではSimpleHTTPServer.SimpleHTTPRequestHandlerのdo_GETをオーバーライドしていますが、必要なものはdo_GETとcopyfileだけなのでBaseHTTPServer.BaseHTTPRequestHandlerでいいや、と思ったのでした。
# -*- coding: euc-jp -*- import BaseHTTPServer import os import shutil import StringIO import urllib class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): def encode(self, s, c): for i in ('euc-jp', 'sjis', 'utf-8'): try: return unicode(s, i).encode(c), i except: pass return s, None def do_GET(self): print 'get...', self.path f = urllib.urlopen(self.path) if 'text' in f.info().gettype() and os.path.splitext(self.path)[1] in ('', '.htm', '.html', '.cgi', '.php'): s, e = self.encode(f.read(), 'euc-jp') if e: s = s.replace('。', '(笑') f = StringIO.StringIO(self.encode(s, e)[0]) shutil.copyfileobj(f, self.wfile) port = 3128 print 'proxy at', port BaseHTTPServer.HTTPServer(('', port), ProxyHandler).serve_forever()
使い方
- python proxy.py で起動
- ブラウザの設定をいじって起動したproxyに繋ぐ
- 日本語のページを見たときに '。' が '(笑' に書き換わってイラッとする
ときどき書き換わらなかったりするのは仕様です。嘘です。よくわかりません。
処理の流れ
- ブラウザが何かをGETしようとするとdo_GETが呼ばれて、self.pathにブラウザがGETしようとしているURLが入る
- urllib.urlopenでself.pathをGET
- GETした結果得られるものはファイルオブジェクトっぽいものなのでreadで文字列だけを取り出して書き換える
- StringIOで文字列からファイルオブジェクトっぽいものに変換
- 変換したファイルオブジェクトっぽいものをshutil.copyfileobjでself.wfileにコピー
- 最終的にブラウザに表示されるものはself.wfileの中身
urllib
urllibを使ってみます。
- urllib.urlopenの引数にURLを入れる
- 返ってきたオブジェクトはファイルのように扱える
基本的にはこれだけです。簡単ですね。
# get.py import sys import urllib print urllib.urlopen(sys.argv[1]).read()
実行例は以下のようになります。
> python get.py http://d.hatena.ne.jp/pythonco/ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=euc-jp"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Content-Script-Type" content="text/javascript"> <title>pythonco(ぱいそんこ)の日記</title> <link rel="start" href="./" title="pythonco(ぱいそんこ)の日記"> <link rel="help" href="/help" title="ヘルプ"> <link rel="prev" href="/pythonco/?of=5" title="前の5日分"> ...
> python get.py http://www.hatena.ne.jp <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Content-Script-Type" content="text/javascript"> <link rel="start" href="http://www.hatena.ne.jp/" title="・ ・> <link rel="stylesheet" href="/css/base.css" type="text/css" media="all"> <link rel="search" type="application/opensearchdescription+xml" href="http://search.hatena.ne.jp/opensearch/all.xml" tit <link rel="search" type="application/opensearchdescription+xml" href="http://q.hatena.ne.jp/opensearch/question.xml" tit <title>・・・/title> <meta name="description" content="??・鋍腓障・・・吟㏍若・若х蕭罘純㏍違篋冴℡査膈篋阪罎膣・Q&A鐚純若激c・・若・RSS・若 <meta name="keywords" content="・・・hatena,篋阪罎膣↑純若激c・・若・㏍穐ゃ≪・蒔RSS,≪・祉壕В% ...
文字化けしてしまいました。仕方がないのでヘッダのcharsetを見てencodeしてみます。
# get.py import sys import urllib f = urllib.urlopen(sys.argv[1]) c = f.headers.getparam('charset') print c print unicode(f.read(), c).encode('euc-jp')
> python get.py http://www.hatena.ne.jp/ utf-8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Content-Script-Type" content="text/javascript"> <link rel="start" href="http://www.hatena.ne.jp/" title="はてな"> <link rel="stylesheet" href="/css/base.css" type="text/css" media="all"> <link rel="search" type="application/opensearchdescription+xml" href="http://search.hatena.ne.jp/opensearch/all.xml" tit <link rel="search" type="application/opensearchdescription+xml" href="http://q.hatena.ne.jp/opensearch/question.xml" tit <title>はてな</title> <meta name="description" content="株式会社はてなが運営。キーワードで繋がる高機能ブログ、人がたずね人が答える人力検索(Q& く便利になるサービスが揃っています。"> <meta name="keywords" content="はてな,hatena,人力検索,ソーシャルブックマーク,ブログ,ダイアリー,RSS,アクセス解析"> <link rel="stylesheet" href="/css/top.css" type="text/css" media="all"> <link rel="stylesheet" href="/css/minwidth.css" type="text/css" media="all"> <SCRIPT type="text/javascript"> <!-- var cookie_name = "PORTAL_TAB"; ...
これで完璧かな、などと思っていたのですが、charsetを返さないサーバもあるので困りました。
> python get.py http://www.google.co.jp None Traceback (most recent call last): File "get.py", line 17, in ? print unicode(f.read(), c).encode('euc-jp') TypeError: unicode() argument 2 must be string, not None
仕方がないので片っ端からunicodeに変換して、例外が出なかった文字コードを採用する、という方法でやってみます。
# get.py import sys import urllib import encodings def encode(s, c): for i in encodings.aliases.aliases: try: return unicode(s, i).encode(c), i except: pass return s, None s, e = encode(urllib.urlopen(sys.argv[1]).read(), 'euc-jp') print e print s
> python get.py http://www.google.co.jp mskanji <html><head><meta http-equiv="content-type" content="text/html; charset=Shift_JIS"><title>Google</title><style><!-- body,td,a,p,.h{font-family:arial,sans-serif} .h{font-size:20px} .h{color:#3366cc} .q{color:#00c} --></style> <script> <!-- function sf(){document.f.q.focus();} // --> </script> </head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onload=sf() topmargin=3 marginheight= idth=100%><font size=-1><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.co.jp/ig%3Fhl%3Dja&usg=__zzRfDx_8QtfWxWHbI https://www.google.com/accounts/Login?continue=http://www.google.co.jp/&hl=ja">ログイン</a></font></div><img alt="Google br><form action="/search" name=f><table border=0 cellspacing=0 cellpadding=4><tr><td nowrap><font size=-1><b>ウェブ</b>& o.jp/imghp?ie=Shift_JIS&oe=Shift_JIS&hl=ja&tab=wi">イメージ</a> <a class=q href="http://news.goog ...
まあとりあえずこれでいい、のかな……。
xmlrpclib
xmlrpclibを使ってみます。
基本的にはこれだけです。簡単ですね。以下ははてなの公開APIを使った例です。
import xmlrpclib s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc') r = s.hatena.getSimilarWord({'wordlist': ['Hatena', 'Python']}) print ', '.join(w['word'] for w in r['wordlist'])
実行結果
ReportLab, はてな伝説, ISBN:, 言語, アプリ, :detail, モジュール, VM, ビルド, Nokia, 付属, シンプル, Guido van Rossum, ...
再帰的にゲット(数が多くなるので最大でも10個ずつに限定)
# getword.py import sys import xmlrpclib def get(s, w, n, p=''): for i in s.hatena.getSimilarWord({'wordlist': w})['wordlist'][:10]: print p+i['word'].encode('euc-jp') if n: get(s, i['word'], n-1, p+'-') s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc') get(s, sys.argv[2:], int(sys.argv[1]))
実行例
> python getword.py 2 hatena apple GUI -MacOS --はてなダイアリーFAQ「その他のFAQ」 --2000年 --マルチタスク --NeXT --業界 --パーソナルコンピューター --Mac --リリース --クリエイター --Apple社 -1984年 --柴田あゆみ --Apple --Macintosh --6月13日 --金田美香 --10月7日 --ひまわり --水泳 --1949年 --3月29日 ...
# -*- coding: euc-jp -*- import xmlrpclib s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc') r = s.hatena.setKeywordLink({'body': 'はてなダイアリーのキーワードをリンクして!', 'score': 20, 'cname': ['book', 'movie'], 'a_target': '_blank', 'a_class': 'keyword'}) print r.encode('euc-jp')
実行結果
はてなダイアリーの<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ad%a1%bc%a5%ef%a1%bc%a5%c9">キーワード</a>をリンクして!
はてなダイアリーキーワード連想語APIで取得したキーワードをはてなダイアリーキーワード自動リンクAPIでリンクする(全カテゴリのキーワードをリンクするためにcnameを省略)
# getword_setlink.py import sys import xmlrpclib s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc') for w in s.hatena.getSimilarWord({'wordlist': sys.argv[1:]})['wordlist']: print s.hatena.setKeywordLink({'body': w['word'].encode('euc-jp'), 'a_target': '_blank', 'a_class': 'keyword'}).encode('euc-jp')
実行例
> python getword_setlink.py りんご <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%c5%a3%b5%dc%cd%fd%b7%c3">釘宮理恵</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ea%a5%f3%a5%b4">リンゴ</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/TOWER%20RECORDS">TOWER RECORDS</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/HIPHOP">HIPHOP</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ec%a5%d3%a5%e5%a1%bc">レビュー</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/2004%c7%af">2004年</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%a2%a5%eb%a5%d0%a5%e0">アルバム</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/ken">ken</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%c8%af%c9%bd">発表</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/bounce">bounce</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%c0%a5%e1%a5%ec%a5%b3">ダメレコ</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/MC">MC</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/Da%2eMe%2eRecords">Da.Me.Records</a> <a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ec%a1%bc%a5%d9%a5%eb">レーベル</a>
気がついたことなど
SimpleXMLRPCServer
SimpleXMLRPCServerを使ってみます。
- 引数で (アドレス, ポート) というタプルを与えてSimpleXMLRPCServerのインスタンスを作る
- register_instance、register_functionで公開するAPIをインスタンスに登録
- serve_foreverでlisten
基本的にはこれだけです。簡単ですね。
# server.py class Foo: def add(self, a, b): return a+b def get_list(self, *a): return [str(i)+'!' for i in a] def get_dict(self): return {'a': 'apple', 'b': 'banana', 'o': 'orange', } def hello(self): print 'hello' return True def sub(a, b): return a-b import SimpleXMLRPCServer SimpleXMLRPCServer.SimpleXMLRPCServer.allow_reuse_address=True s = SimpleXMLRPCServer.SimpleXMLRPCServer(('localhost', 12345)) s.register_instance(Foo()) s.register_function(sub) s.register_function(lambda x, y: x*y, 'mul') s.serve_forever()
# client.py import xmlrpclib s = xmlrpclib.ServerProxy('http://localhost:12345') print s.add(1, 2) print s.sub(10, 9) print s.mul(11, 11) print s.get_list(1, 2, 3, 4, 5, 6, 'foo', 'bar') print s.get_dict() s.hello()
実行結果は以下のようになります
> python server.py
> python client.py 3 1 121 ['1!', '2!', '3!', '4!', '5!', '6!', 'foo!', 'bar!'] {'a': 'apple', 'b': 'banana', 'o': 'orange'}
> python server.py localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 - localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 - localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 - localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 - localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 - hello localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 -
localhost - -... というのはアクセスログです。鬱陶しい場合はサーバインスタンス生成時に
s = SimpleXMLRPCServer.SimpleXMLRPCServer(('localhost', 12345), logRequests=False)
とすることでログを表示しないようになります。helloと表示されているのはクライアントがメソッド hello() を呼び出した結果です。
また、
SimpleXMLRPCServer.SimpleXMLRPCServer.allow_reuse_address=True
とすることで サーバ起動 -> 停止 -> サーバ起動 を短い時間で行ったときに出がちなエラー、Address already in use を殺しています。
気がついたことなど
- サーバに登録するインスタンスメソッド/関数はNone以外の値を返す必要がある
- hello()で返り値を書かなかったら呼び出し時に例外が起きた
- 文字列や数値だけではなく、リストや辞書なども結果として返すことができる
isinstance
isinstanceを使うと、オブジェクトが任意のクラスのインスタンスかどうかを調べることができます。
def exclam(o): if isinstance(o, str): return o + '!' elif isinstance(o, int): return reduce(lambda x, y: x*y, range(1, o+1), 1) print exclam('hello') # => hello! print exclam(10) # => 3628800
class Foo(object): pass class Bar(Foo): pass b = Bar() print type(b) # => <class '__main__.Bar'> print isinstance(b, object) # => True print isinstance(b, Bar) # => True print isinstance(b, Foo) # => True
親の親まで見に行ってくれるみたいです。
変数とクラスのシーケンスを渡して、変数のクラスがシーケンス中に一つでもあったらTrueを返す関数を書いてみました。
def type_check(var, types): return reduce(lambda x, y: x or isinstance(var, y), types, False) a = 'apple' print type_check(a, (int, long)) # => False b = 123 print type_check(b, (str, int, long)) # => True
しかし、これには落とし穴があったのです……。
a = 'apple' print isinstance(a, (int, long)) # => False b = 123 print isinstance(b, (str, int, long)) # => True
isinstanceは第二引数でシーケンスを受け取って、上で書いた関数と同じ結果を返すのでした……。
二次元リストを縦に足す
l = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print l # => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] # 転置 print zip(*l) # => [(1, 4, 7), (2, 5, 8), (3, 6, 9)] # 転置した後にタプルになるのが嫌? print map(list, zip(*l)) # => [[1, 4, 7], [2, 5, 8], [3, 6, 9]] # 転置した各行を足し合わせる print [reduce(lambda x, y: [x[0]+y], i, [0]) for i in zip(*l)] # => [[12], [15], [18]] # 要素一つならリストじゃなくてもいい? print [reduce(lambda x, y: x+y, i, 0) for i in zip(*l)] # => [12, 15, 18]
文字列から二次元リストに変換する。
a = '''1,2,3 4,5,6 7,8,9''' # 改行でsplitして得られたリストを','でsplitして、得られたリストの各要素をintに変換 l = [[int(i) for i in j.split(',')] for j in a.split('\n')] print l # => [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print [reduce(lambda x, y: [x[0]+y], i, [0]) for i in zip(*l)] # => [[12], [15], [18]]
intは意外と柔軟性があるみたいですね……。
a = '''11, 12, 13 14, 15 , 16 17,18 , 19''' print [[int(i) for i in j.split(',')] for j in a.split('\n')] # => [[11, 12, 13], [14, 15, 16], [17, 18, 19]]
>>> int(' 12 \r \n\n') 12 >>> int('\r\n 33\n') 33 >>> int('\r\n\n\n - \r\n\n 13 \n\n') -13 >>> int('2 7') Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: invalid literal for int(): 2 7