我正在尝试通过组合元组列表和 1d numpy 数组来找到一种时间和内存性能的方式来创建字典。因此,每个字典应具有以下结构:
{"id": <first elem from tuple>, "name": <second elem from tuple>, "text": <third elem from tuple>, "value": <elem from array>}
元组列表如下所示:
list_of_tuples = [("id1", "name1", "text1"), ("id2", "name2", "text2")]
此外,numpy 数组具有与列表相同数量的元素,并且包含 np.float16 类型的元素:
value_array = np.ndarray([0.42, np.nan])
此外,所有 NaN 值都将被过滤掉。上面示例的结果应该是:
{"id": "id1", "name": "name1", "text": "text1", "value": 0.42}
我确实让它像这样工作:
[
dict(
dict(zip(["id", "name", "text"], list_of_tuples[index])),
**{"value": value},
)
for index, value in enumerate(value_array)
if not (math.isnan(value))
]
但是,对于许多条目来说,这非常慢,并且使用索引从列表中获取条目感觉错误/效率低下。
您绝对可以在不必显式使用索引的情况下工作。这应该会提高性能。
value_array_indices = np.argwhere(~np.isnan(value_array))
list_of_tuples = np.array(list_of_tuples)[value_array_indices[0]]
value_array = value_array[value_array_indices[0]]
[{"id": x[0], "name": x[1], "text": x[2], "value": v} for x,v in zip(list_of_tuples, value_array)]
在我写这篇文章的时候,看起来有人发布了类似的解决方案,但由于测量的时间和一些解释,我还是会发布它。
使用下面建议的代码,并且长度为一百万(包括单个 NaN(的测试输入,与问题中的代码相比,我看到它的时间下降到 30% 以下。
Time: 0.3486933708190918
{'id': 'id0', 'name': 'name0', 'text': 'text0', 'value': 0.0} {'id': 'id999999', 'name': 'name999999', 'text': 'text999999', 'value': 999999.0} 999999
Time: 1.2175893783569336
{'id': 'id0', 'name': 'name0', 'text': 'text0', 'value': 0.0} {'id': 'id999999', 'name': 'name999999', 'text': 'text999999', 'value': 999999.0} 999999
我认为这里的区别部分是不必索引元组列表,但我也怀疑其中很大一部分不必为每个元素实例化一个zip
对象。 您每次都要处理少量具有相同名称的字典键,因此您确实不需要zip
在这里提供的灵活性,只需从简单的显式表达式创建字典就更简单了。
(zip(list_of_tuples, value_array)
显然只为整个操作创建一个zip
对象,因此并不重要。
我还建议在这里from math import isnan
,而不是每次都进行属性查找以获取math.isnan
,尽管差异相对不重要。
from math import isnan
import numpy as np
import time
# construct some test data
n = 1000000
value_array = np.arange(n, dtype=np.float)
value_array[n // 2] = np.nan
list_of_tuples = [(f"id{i}", f"name{i}", f"text{i}")
for i in range(len(value_array))]
# timings for suggested alternative method
t0 = time.time()
l = [{"id": t[0],
"name": t[1],
"text": t[2],
"value": v}
for t, v in zip(list_of_tuples, value_array) if not isnan(v)]
t1 = time.time()
print("Time:", t1 - t0)
print(l[0], l[-1], len(l))
# timings for the method in the question
t0 = time.time()
l =
[
dict(
dict(zip(["id", "name", "text"], list_of_tuples[index])),
**{"value": value},
)
for index, value in enumerate(value_array)
if not (isnan(value))
]
t1 = time.time()
print("Time:", t1 - t0)
print(l[0], l[-1], len(l))
还尝试并拒绝了:创建一个not isnan
值的布尔数组,使用
not_isnan_array = np.logical_not(np.isnan(value_array))
然后在列表理解中,您可以执行以下操作:
... for t, v, not_isnan in zip(list_of_tuples, value_array, not_isnan_array) if not_isnan
但它对时序几乎没有影响,因此不能证明额外的内存使用是合理的。
更新
对混合版本的进一步实验(在问题的原始版本和建议的替代方案之间(表明,正如我所怀疑的那样,大部分差异来自避免在每次迭代时创建一个zip
对象。 避免显式索引元组列表仅占加速的一小部分。