尽可能快地从字典中提取数据

时间:2018-03-14 17:38:46

标签: python python-2.7 performance dictionary

我有一个字典d,其中包含大约500个主键(name1name2等)。每个值本身都是一个小字典,有5个键,分别叫ppty1ppty2等,而相应的值是浮点数转换为字符串。

我希望比以前更快地提取数据,基于['name1', 'ppty3','ppty4']形式的列表列表(name1可以由任何其他nameXppty3ppty4可以是任何其他pptyX)。

在我的应用程序中,我有很多词典,但它们的区别仅在于ppty1,...,ppty5字段的值。所有键都是“静态的”。我不在乎是否有一些初步操作,我只想要一个字典的处理时间,理想情况下,比现在快得多。我的糟糕实现,包括循环遍历每个字段大约需要3毫秒。

以下是生成dfields的代码;这只是模拟虚拟数据,不需要改进:

import random
random.seed(314)

# build dictionary
def make_small_dict():
    d = {}
    for i in range(5):
        key = "ppty" + str(i)
        d[key] = str(random.random())
    return d

d = {}
for i in range(100):
    d["name" + str(i)] = make_small_dict()

# build fields
def make_row():
    line = ['name' + str(random.randint(0,100))]
    [line.append('ppty' + str(random.randint(0,5))) for i in range(2)]
    return line

fields = [0]*300
for i in range(300):
    fields[i] = [make_row() for j in range(3)]

例如,fields[0]返回

[['name420', 'ppty1', 'ppty1'],
 ['name206', 'ppty1', 'ppty2'],
 ['name21', 'ppty2', 'ppty4']]

所以输出的第一行应该是

[[d['name420']['ppty1'], d['name420']['ppty1'],
 [d['name206']['ppty1'], d['name206']['ppty2']],
 [d['name21']['ppty2'], d['name21']['ppty4']]]]

我的解决方案:

start = time.time()
data = [0] * len(fields)
i = 0
for field in fields:
    data2 = [0] * 3
    j = 0
    for row in field:
        lst = [d[row[0]][key] for key in [row[1], row[2]]]
        data2[j] = lst
        j += 1
    data[i] = data2
    i += 1
print time.time() - start

我的主要问题是,如何改进我的代码?另外几个问题:

  • 稍后,我需要进行一些操作,例如列提取,对data的某些条目的基本操作:您是否建议将提取的值直接存储在np.array中?
  • 如何避免多次提取相同的值(fields有一些冗余行,例如['name1', 'ppty3', 'ppty4'])?
  • 我读过像i += 1这样的事情需要一点时间,我该如何避免它们呢?

1 个答案:

答案 0 :(得分:1)

这很难阅读,所以我开始把功能分解成功能。然后我可以测试,看看是否只使用列表理解。它已经更快了,超过10000次运行与timeit的比较表明,这段代码的运行时间大约是原始代码的64%。

在这种情况下,我保留列表中的所有内容以强制执行,因此它可以直接比较,但您可以使用生成器或映射,并且将计算推回到数据实际消耗时。

def row_lookup(name, key1, key2):
     return (d[name][key1], d[name][key2]) # Tuple is faster to construct than list

def field_lookup(field):
    return [row_lookup(*row) for row in field]

start = time.time()
result = [field_lookup(field) for field in fields]
print(time.time() - start)
print(data == result)

# without dupes in fields
from itertools import groupby
result = [field_lookup(field) for field, _ in groupby(fields)]

仅将结果分配行更改为:

result = map(field_lookup, fields)

并且运行时变得可以忽略不计,因为map是一个生成器,所以在你询问结果之前它实际上并不会计算数据。这不是一个公平的比较,但如果您不打算消费所有数据,那么您可以节省时间。将函数中的列表推导更改为生成器,您也可以获得相同的好处。在这种情况下,多处理和asyncio没有改善性能时间。

如果您可以更改结构,则可以将字段预处理为仅包含行[['namex', 'pptyx', 'pptyX']..]的列表。在这种情况下,您可以将其更改为单个列表推导,这使您可以将其降低到原始运行时的大约29%,忽略预处理以缩小字段。

from itertools import groupby, chain
slim_fields = [row for row, _ in groupby(chain.from_iterable(fields))]
results = [(d[name][key1], d[name][key2]) for name, key1, key2 in slim_fields]

在这种情况下,结果只是一个包含值的元组列表:[(value1, value2)..]