[英]Fastest way to search a list of named tuples?
我有一个名为元组的列表。 每个命名元组都是我创建的DataPoint
类型,如下所示:
class DataPoint(NamedTuple):
data: float
location_zone: float
analysis_date: datetime
error: float
在我的代码中的各个点,我必须通过特定属性获取列表中的所有DataPoints
。 这是我为analysis_date
做的方式,我对其他属性有类似的功能:
def get_data_points_on_date(self, data_points, analysis_date):
data_on_date = []
for data_point in data_points:
if data_point.analysis_date == analysis_date:
data_on_date.append(data_point)
return data_on_date
在具有数千个点的列表上称为> 100,000次,因此它显着减慢了我的脚本速度。
而不是列表,我可以为一个显着的加速做一个字典,但因为我需要搜索多个属性,没有一个明显的关键。 我可能会选择占用时间最多的函数(在本例中为analysis_date
),并将其用作关键字。 但是,这会增加我的代码的复杂性。 除了哈希之外还有什么东西可以用来干扰我吗?
如果数据可以预先计算一次,那么你想要避免做过100,000次线性搜索是正确的。 为什么不使用多个词典,每个词典都由不同的感兴趣属性键入?
每个字典都会预先计算一次:
self.by_date = defaultdict(list)
for point in data_points:
self.by_date[point.analysis_date].append(point)
现在你的get_data_points_for_date
函数变成了一个单行:
def get_data_points_for_date(self, date):
return self.by_date[date]
您可以完全删除此方法,只需使用self.by_date[date]
。
这不会增加代码的复杂性,但它确实可以预先转移一些簿记负担。 你可以通过一个预先计算你想要的所有字典的set_data方法来解决这个问题:
from collections import defaultdict
from operator import attrgetter
def set_data(self, data_points):
keygetter):
d = defaultdict(list)
for point in data_points:
d[key(point)].append(point)
return d
self.by_date = make_dict(attrgetter('analysis_date'))
self.by_zone = make_dict(self.zone_code)
def zone_code(self, data_point):
return int(data_point.location_zone // 0.01)
类似于zone_code
东西是将float
s转换为整数所必需的,因为依赖float
s作为键并不是一个好主意。
也许内存中的SQLite数据库(带有列索引)可能有所帮助。 它甚至有一种方法可以将行映射到命名元组,因为python sqlite中的映射结果行到namedtuple描述。
有关更完整的解决方案,请参阅http://peter-hoffmann.com/2010/python-sqlite-namedtuple-factory.html 。
基于以上两个链接的基本示例:
from typing import NamedTuple
from datetime import datetime
import sqlite3
class DataPoint(NamedTuple):
data: float
location_zone: float
analysis_date: datetime
error: float
def datapoint_factory(cursor, row):
return DataPoint(*row)
def get_data_points_on_date(cursor, analysis_date):
cursor.execute(
f"select * from datapoints where analysis_date = '{analysis_date}'"
)
return cursor.fetchall()
conn = sqlite3.connect(":memory:")
c = conn.cursor()
c.execute(
"create table datapoints "
"(data real, location_zone real, analysis_date text, error timestamp)"
)
c.execute(
"create index if not exists analysis_date_index on datapoints (analysis_date)"
)
timestamp = datetime.now().isoformat()
data_points = [
DataPoint(data=0.5, location_zone=0.1, analysis_date=timestamp, error=0.0)
]
for data_point in data_points:
c.execute(f"insert into datapoints values {tuple(data_point)}")
conn.commit()
c.close()
conn.row_factory = datapoint_factory
c = conn.cursor()
print(get_data_points_on_date(c, timestamp))
# [DataPoint(data=0.5, location_zone=0.1, analysis_date='2019-07-19T20:37:38.309668', error=0)]
c.close()
numpy和pandas针对这些东西进行了优化,它们非常快。
我在下面的代码中为你做了一个简单的比较测试,看看pandas DataFrame如何在速度中占主导地位:
码
import pandas as pd
import numpy as np
from time import perf_counter
# init
a = np.array([0 if 500 < i < 510 else 1 for i in range(100, 1000000)])
data_points = {'data': np.arange(100, 1000000),
'location_zone': np.arange(100, 1000000),
'analysis_date': np.arange(100, 1000000) * a,
'error': np.arange(100, 1000000)}
df = pd.DataFrame(data_points)
# speed of dataframe
t0 = perf_counter()
b = df[df['analysis_date'] == 0]
print("pandas DataFrame took: {:.4f} sec".format(perf_counter() - t0))
print(b)
# speed normal python code
t0 = perf_counter()
indices = [d for d in range(data_points['analysis_date'].shape[0]) if data_points['analysis_date'][d] == 0]
print("normal python code took: {:.4f} sec".format(perf_counter() - t0))
print(indices)
产量
pandas DataFrame took: 0.0049 sec
analysis_date data error location_zone
401 0 501 501 501
402 0 502 502 502
403 0 503 503 503
404 0 504 504 504
405 0 505 505 505
406 0 506 506 506
407 0 507 507 507
408 0 508 508 508
409 0 509 509 509
normal python code took: 0.2782 sec
[401, 402, 403, 404, 405, 406, 407, 408, 409]
pandas DataFrame参考: 链接
关于DataFrames的一个很好的教程: 链接
以下代码:
def get_data_points_on_date(self, data_points, analysis_date):
data_on_date = []
for data_point in data_points:
if data_point.analysis_date == analysis_date:
data_on_date.append(data_point)
return data_on_date
可以重构为:
def get_data_points_on_date(self, data_points, analysis_date):
return (p for p in data_points if p.analysis_date == analysis_date)
您可以在for循环中访问该返回的值,或者将其作为包含list(returned_value)
。
如果您有这样的DataPoints列表,可以使用pandas
和MultiIndex通过O(1)查找来访问它们:
import pandas as pd
datapoints_series = pd.DataFrame(
{
"data": pt.data,
"location_zone": pt.location_zone,
"analysis_date": pt.analysis_date,
"error": pt.error,
"data_point": pt
}
for pt in data_points_list
).set_index([
"data",
"location_zone",
"analysis_date",
"error"
]).squeeze() # send to Series
要访问特定日期:
def date_accessor(date):
idx = pd.IndexSlice[:, :, date, :]
date = "2019-07-01"
datapoints_series.loc[date_accessor(date)]
如果您想再次在列表中使用数据点,则可以简单地将.tolist()
方法调用追加到该最后一行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.