辞書のキーと値を入れ替える
- 牌語備忘録: 続・辞書のキーと値を入れ替えをPythonでやってみたの速度を計測してみた (http://d.hatena.ne.jp/CortYuming/20080811/p2)
import timeit from timeit import Timer from operator import attrgetter a1 = """ def d_change_a(d): di = {} for k in d: di[d[k]] = k return dict(di) d_change_a(dict_data) """ a2 = """ def d_change_a2(d): for k in d: d[d.pop(k)] = k return d d_change_a2(dict_data) """ b= "dict((lambda d:[(d[k], k) for k in d])(dict_data))" c = """ def d_change_c(d): return dict([(d[k], k) for k in d]) d_change_c(dict_data) """ d1 = "dict((v, k) for k, v in dict_data.iteritems())" d2 = "dict([(v, k) for k, v in dict_data.items()])" stmts = [a1, a2, b, c, d1, d2] class T:pass ts = [T() for i in range(len(stmts))] Ns = [5, 30, 100, 500, 1000, 2000, 4000, 8000] for N in Ns: dict_data = dict(enumerate(range(N))) timeit.dict_data = dict_data for t, stmt in zip(ts, stmts): t.name = [k for k,v in globals().items() if v==stmt and k!='stmt'][0] t.time = Timer(stmt=stmt).timeit(number=1000) ts.sort(key=attrgetter('time')) print ('N=%s:\t' % N + ' < '.join(['%s']*len(ts)) % tuple(t.name for t in ts) + ' ' + ' < '.join(['%.4f']*len(ts)) % tuple(t.time for t in ts))
[実行結果] N=5: d1 < c < d2 < a1 < a2 < b 0.0033 < 0.0036 < 0.0037 < 0.0057 < 0.0057 < 0.0103 N=30: a1 < a2 < d1 < b < c < d2 0.0079 < 0.0083 < 0.0102 < 0.0113 < 0.0115 < 0.0123 N=100: a1 < a2 < d1 < c < b < d2 0.0215 < 0.0257 < 0.0285 < 0.0288 < 0.0293 < 0.0346 N=500: a1 < b < c < d1 < a2 < d2 0.0946 < 0.1175 < 0.1186 < 0.1226 < 0.1264 < 0.1477 N=1000: a1 < c < b < d1 < a2 < d2 0.1756 < 0.2226 < 0.2247 < 0.2444 < 0.2615 < 0.2802 N=2000: a1 < c < b < d1 < a2 < d2 0.3643 < 0.4457 < 0.4467 < 0.4887 < 0.5066 < 0.6096 N=4000: a1 < b < c < d1 < a2 < d2 0.6692 < 0.8921 < 0.8953 < 0.9524 < 1.0222 < 1.1993 N=8000: a1 < a2 < c < d1 < b < d2 1.4749 < 2.0738 < 2.1923 < 2.2093 < 2.2204 < 2.5486
確かに、内包表記よりもforループの方が早い。何でだろう?
追記1:
最速のやり方見つけた。
- 辞書Dのキーと値を"最速で"入れ替える
- V2.5.2
dict(izip(D.itervalues(), D.iterkeys()))
-
- V3.0b2
dict(zip(D.values(), D))
import timeit from timeit import Timer from operator import attrgetter from itertools import izip timeit.izip = izip a = """ di = {} for k in dict_data: di[dict_data[k]] = k dict(di) """ d = "dict((v, k) for k, v in dict_data.iteritems())" e1 = "dict(zip(dict_data.itervalues(), dict_data))" e2 = "dict(izip(dict_data.itervalues(), dict_data))" e3 = "dict(izip(dict_data.itervalues(), dict_data.iterkeys()))" stmts = [a, d, e1, e2, e3] class T:pass ts = [T() for i in range(len(stmts))] Ns = [5, 30, 100, 500, 1000, 2000, 4000, 8000] NUM=10000 print 'NUM=%s' % NUM for N in Ns: dict_data = dict(enumerate(range(N))) timeit.dict_data = dict_data for t, stmt in zip(ts, stmts): t.name = [k for k,v in globals().items() if v==stmt and k!='stmt'][0] t.time = Timer(stmt=stmt).timeit(number=NUM) ts.sort(key=attrgetter('time')) print ('N=%s:\t' % N + ' < '.join(['%s']*len(ts)) % tuple(t.name for t in ts) + ' ' + ' < '.join(['%.4f']*len(ts)) % tuple(t.time for t in ts))
[実行結果] NUM=10000 N=5: a < e2 < e3 < d < e1 0.0161 < 0.0218 < 0.0233 < 0.0332 < 0.0388 N=30: e2 < e3 < a < e1 < d 0.0570 < 0.0579 < 0.0724 < 0.0842 < 0.1028 N=100: e2 < e3 < e1 < a < d 0.1327 < 0.1330 < 0.1961 < 0.2105 < 0.2788 N=500: e2 < e3 < e1 < a < d 0.5405 < 0.5519 < 0.8065 < 0.9556 < 1.2728 N=1000: e3 < e2 < e1 < a < d 0.9927 < 1.0177 < 1.4842 < 1.7473 < 2.3828 N=2000: e3 < e2 < a < e1 < d 2.9085 < 2.9706 < 3.9566 < 3.9803 < 5.0063 N=4000: e3 < e2 < e1 < a < d 3.8316 < 3.8628 < 6.4000 < 6.8653 < 9.6651 N=8000: e3 < e2 < a < e1 < d 11.6122 < 11.6899 < 15.1079 < 16.6591 < 22.4529
やはり一般的には、一度リストを作って回すよりイテレータの方が早そう。あと自分のイメージとしては、シンプルな実装ほど早い。そういう意味ではfor文は割と早いが、繰り返す回数が増えればオーバーヘッドが大きくなっていくので、工夫した方法が速くなってくると思われる。
追記2:
dict(imap(reversed, D.iteritems()))
も思いついたが、かなり遅かった。恐らく、reversed()で毎回インスタンス作成するからだと思われる。