【问题标题】:Python: How do I efficiently nest 4 list of dictionaries into one?Python:如何有效地将 4 个字典列表嵌套到一个中?
【发布时间】:2021-11-11 07:41:09
【问题描述】:

我有一个 MSSQL 存储过程,它返回 4 个选项给我:EntitiesCertificatesContactsLogs。我需要在 Pyton 中组合这 4 个选项,我将所有 EntitiesContactsLogs 放在它们的 Certificate 下。这些选择中的每一个都有一个 EntityId 我可以用于合并。

输入是简单的基本数据类列表,其中包含来自 SQL 的信息。我们在合并函数中将这些数据类转换为字典。

当我最初编写代码时,我不知道选择可能非常大(Certificates 的 100.000 个及其所有其他记录)。不幸的是,由于循环内列表推导的许多不必要的迭代,这使得下面的代码非常低效。最多可能需要 70 秒。我确信有一种方法可以使这更快。如何提高性能以尽可能高效?

from dataclasses import asdict

def cert_and_details(entities: List[Entity], 
                    certificates: List[Certificate], 
                    req_logs: List[DocumentRequestHistory], 
                    recipients: List[Recipient]) -> List[dict]:

    entities = [asdict(ent) for ent in entities] 
    certificates = [asdict(cert) for cert in certificates]
    req_logs = [asdict(log) for log in req_logs]
    recipients = [asdict(rec) for rec in recipients]

    results = []
    for cert_dict in certificates:

        cert_entity_id = cert_dict["entityid"]

        logs_under_cert = [log for log in req_logs if log["entityid"] == cert_entity_id]
        cert_dict["logs"] = logs_under_cert

        entities_under_cert = [ent for ent in entities if ent["entityid"] == cert_entity_id]
        cert_dict["linkedentity"] = entities_under_cert

        recipients_under_cert = [rec for rec in recipients if rec["entityid"] == cert_entity_id]
        cert_dict["recipients"] = recipients_under_cert

        results.append(cert_dict)

    return results

【问题讨论】:

  • 可能最好的起点是改进 SQL 查询。您可以进行查询,以便 logsentsrecipients 已经针对 entityid 进行过滤,然后您不需要在 for 循环中执行所有列表推导。
  • 这是一种可能性,也是我们想到的。但是,无论我们走哪条路,我仍然想知道在 Python 中解决此类问题的最佳答案是什么。
  • 列表推导可能在不使用任何库的情况下尽可能快。使用numpy 过滤列表可能有更优化的方法,但是,我对此并不十分熟悉。在代码结构方面,您可以通过创建一个dict 来优化此代码,其中entityid 作为键,其余的值作为您需要的值。然后,您只需要在开始时对所有列表进行一次迭代,并且在主循环中,您可以在 O(1) 时间内简单地访问每个字段。如果这对您有帮助,请告诉我,以便我可以将其升级为答案。
  • 这可能会有所帮助。如果没有人给出一个 numpy 的答案,我很乐意看看它。
  • 与许多软件开发一样,“最佳答案”通常与上下文相关。换句话说,这取决于。您说 70 秒对于处理数据来说太长了,但什么是可接受的目标,您是否创建了一个基准来测试替代方案? logs_under_certentities_under_certrecipients_under_cert 的赋值看起来像是代码中的连接,而不是 SQL 查询的一部分。大多数情况下,选择正确的算法和数据结构是您获得最佳答案的地方。

标签: python performance rest record python-dataclasses


【解决方案1】:

所提供代码的主要问题是其计算复杂性:它在O(C * (L + E + R)) 中运行,其中C 是证书数量,L 日志数量,E 实体数量和 R 收件人数量。如果L+E+R 很小,这很好,但如果不是这种情况,那么代码会很慢。

您可以编写一个在O(C + L + E + R) 时间运行的实现。这个想法是首先建立一个索引,以按实体 ID 对日志/实体/收件人进行分组。这是一个简短的例子:

# Note: defaultdict should help to make this code smaller (and possibly faster)
logIndex = dict()
for log in req_logs:
    entityId = log["entityid"]
    if entityId in logIndex:
        logIndex[entityId].append(log)
    else:
        logIndex[entityId] = [log]

此代码在(摊销)O(L) 中运行。然后,您可以使用 logIndex[entityId] 检索具有给定实体 ID 的 req_log 中的所有项目。

提供的代码中还有另一个问题:字典列表效率低下:字典索引速度很慢,而且字典也没有内存效率。存储和计算数据的更好方法是使用 dataframes(例如,使用 Pandas,它还提供了相对优化的 groupby 函数)。

【讨论】:

    【解决方案2】:

    下面也可能是另一种复杂度排序的方法(2*C+L+E+R)。

    警告:我没有尝试过运行它,它只是模拟代码,并没有尽可能地提高效率。我也只是模拟了它,从概念上思考如何使它成为线性复杂性,它可能有一些我错过的基本“糟糕”。

    但它基于循环遍历每个 C、L、E 和 R 的概念一次。这是通过首先使certificates 成为字典而不是列表来完成的。关键是它是entityid。存储每个证书日志、实体和接收者的列表也是在那个时候创建​​的。

    然后你可以只循环一次L,E和R,并通过查找entityid直接将它们的条目添加到证书字典中。

    最后一步(为什么复杂性中的 2*C)是循环遍历证书字典并将其转换为列表以匹配所需的输出类型。

    from dataclasses import asdict
    
    def cert_and_details(entities: List[Entity], 
                        certificates: List[Certificate], 
                        req_logs: List[DocumentRequestHistory], 
                        recipients: List[Recipient]) -> List[dict]:
    
        certs = {}
    
        for cert in certificates:
            cert_dict = asdict(cert)
            cert_id = cert_dict['entityid']
    
            certs[cert_id] = cert_dict
            certs['logs'] = []
            certs['recipients'] = []
            certs['linkedentity'] = []
    
        for log in logs:
            log_dict = asdict(log)
            log_id = log_dict['entityid']
            certs[log_id]['logs'].append(log_dict)
    
    
        for ent in entities:
            ent_dict = asdict(ent)
            ent_id = ent_dict['entityid']
            certs[ent_id]['linkedentity'].append(ent_dict)
    
        for rec in recipients:
            rec_dict = asdict(rec)
            rec_id = rec_dict['entityid']
            certs[rec_id]['recipients'].append(rec_dict)
    
        # turn certs back into list, not dictionary
        certs = [cert for cert in certs.values()]
    
        return certs
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-08
      • 1970-01-01
      • 1970-01-01
      • 2011-07-31
      • 2021-02-10
      • 2020-12-30
      • 1970-01-01
      相关资源
      最近更新 更多