辞書のキーと値を入れ替える

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()で毎回インスタンス作成するからだと思われる。