【问题标题】:Efficiently loop over a pair of lists to compare elements有效地循环遍历一对列表来比较元素
【发布时间】:2021-04-28 14:10:27
【问题描述】:

我有 2 个列表。其中之一是列表列表。更具体地说:

lst1 = [[4, 5, 6], [19, 20, 24, 25]]
lst2 = [8, 21]

每个列表列表的数字代表索引

我想要做的是从lst2 中删除任何元素 这不在lst1 中每个列表的索引之间。

例如,lst2 中的数字 8 应该被删除,因为它不在 lst1 的两个列表(元素)的第一个和最后一个数字之间。不应删除数字 21,因为它位于 lst2 的第二个列表(元素)的第一个和最后一个元素之间。

目前我写的代码是:

for elem2 in lst2:
    count = 0
    for elem1 in lst1:
        minsubelem = min(elem1)
        maxsubelem = max(elem1)
                
        condition = not ((elem2 > minsubelem) and (elem2 < maxsubelem))
        
        if condition:
            count += 1
            
    if count == len(lst1):
        index = lst2.index(elem2)
        lst2.pop(index)

print(lst2)

返回:

[21]

这可行,但正如您可以想象的那样,使用 2 个 for 循环并不是最优的,如果列表很长,计算时间可能会显着增加。

为了提高效率,该代码的潜在替代品是什么?

说明:

  1. lst2 中的索引 永远不等于 中的任何索引 lst1
  2. 我已经阅读了itertools 文档,但我可能是 缺乏将建议的方法与我的用例联系起来的经验。

【问题讨论】:

  • 问题不在于这两个循环;您将 必须 遍历这两个列表以获取您需要的信息。一个问题是您对lst1 的迭代次数与lst2 中的元素一样多。想想你如何只能迭代 lst1 一次。
  • 一个轻微的优化是,如果您找到包含elem2 的最小值和最大值,那么您可以退出内部循环,因为您知道不必从lst2 中删除elem2。此外,您不应在迭代 lst2 时对其进行修改。也许最好列出所有幸存元素的新列表。
  • @Ben 这是我想的一部分:)。我想也许我可以使用集合,但在 lst1 的列表元素和 lst2 的元素之间永远不会出现相同的数字。我正在阅读 itertools 文档,因为不知何故我怀疑应该有一些功能可以帮助这个用例,但我还不能链接它。
  • @quamrana 这就是为什么我使用 count 变量来确保在检查 lst1 中所有列表的条件后删除数字。实际上我将幸存者保存在一个新列表中,但理想情况下我想避免分配/创建一个新列表。
  • 这就是我的意思:您总是在检查所有列表的条件。有时您不必检查所有列表。一旦找到保留元素的原因,就可以保存它,您可以跳过检查其他列表。

标签: python list


【解决方案1】:

如果您保证lst1 中的子列表将被排序,那么您无需查找每个子列表的最小值和最大值。只需抓住每个子列表的第一个和最后一个元素,并将它们用作您的最小值和最大值。此外,一般而言,保留所需元素比清除不想要的元素更容易和更清晰 - 而不是弹出您不想要的元素,只需构建一个新列表,只保留您确实想要的那些元素:

lst1 = [[4, 5, 6], [19, 20, 24, 25]]
lst2 = [8, 21]

def predicate(value):
    return any(l[0] < value < l[-1] for l in lst1)

print(list(filter(predicate, lst2)))

输出:

[21]
>>> 

编辑 - 为了解决您对无法使用 filter 的担忧(因为您需要将 lst1 作为参数传递给 predicate),您可以使用 functools.partial

from functools import partial

lst1 = [[4, 5, 6], [19, 20, 24, 25]]
lst2 = [8, 21]

def predicate(value, ranges):
    return any(l[0] < value < l[-1] for l in ranges)

print(list(filter(partial(predicate, ranges=lst1), lst2)))

注意,最后这仍然基本上等同于使用两个循环。一个“循环”遍历lst2 中的所有项目,对每个项目调用谓词,另一个循环遍历lst1 中的每个子列表(在predicate 中称为ranges)。不过,由于发生在 any 中的短路评估,您确实可以提前退出。

【讨论】:

  • 感谢您的建议!但是,想象一下,为了简单起见,我在这里手动创建了 lst1 和 lst2。在您的建议中,谓词函数“知道” lst1 对我来说并非如此。换句话说,我需要以某种方式将 lst1 包含在谓词函数所需的参数中,这意味着我可能无法使用过滤器函数,因为谓词需要 2 个参数。
  • 另外,子列表总是排序的。
  • 我找到了如何克服我上面提到的关于过滤器函数参数的问题。这个post 帮助了我。它可以与 functools 中的部分函数结合使用。
  • @wannabedatasth 我已经编辑了我的问题 - 看看。
  • 这个溶胶的时间复杂度不是 O(n^2) 吗? 'any' 将返回一个生成器对象,因此如果该值是它将返回的范围。这就像在嵌套的 for 循环中添加一个中断。我在这里错过了什么吗? @PaulM。
【解决方案2】:

遍历列表一以获取最小值和最大值,并将其余部分抽象为 pandas。不过不知道性能。

import pandas as pd 

lst1 = [[4, 5, 6], [19, 20, 24, 25]]
lst2 = [8, 21]

lst1_min_max = [[min(x), max(x)] for x in lst1]
lst1_min_max_df = pd.DataFrame(lst1_min_max, columns=['min', 'max'])
lst1_min_max_df['key'] = 1

lst12_df = pd.DataFrame(lst2, columns=['values_to_check'])
lst12_df['key'] = 1

lst1_min_max_df_join = lst1_min_max_df.merge(lst12_df, how='inner')

lst1_min_max_df_join['value_to_check_in_range'] = lst1_min_max_df_join['values_to_check'].between(lst1_min_max_df_join['min'], lst1_min_max_df_join['max'])

res = lst1_min_max_df_join.groupby('values_to_check')['value_to_check_in_range'].any()
list(res.index[res.values])

【讨论】:

  • 感谢@M.Hahn 的建议!我其实想过这样做。但是,我提供的列表示例被简化了。对于我的用例的规模,我想我宁愿避免转换为 pandas。您的建议在您已经使用 pandas 的不同情况下会派上用场。
【解决方案3】:

您可以创建一个白名单,然后检查 lst 2 中的号码是否在白名单中。由于 lst2 中的索引永远不会相等,您甚至可以使用 range(l[0]+1, l[-1]) 进一步减少白名单中的条目,如果 lst0 中的范围可以重叠,您可以通过使用白集而不是列表来减少重复项。

lst0 = [[4, 5, 6], [19, 20, 24, 25]]
lst1 = [i for l in lst0 for i in list(range(l[0]+1, l[-1]))]
lst2 = [8, 21]

print([n for n in lst2 if n in lst1])
#[21]

【讨论】:

  • 感谢@Andreas 的回复!但是,您的建议不是再次运行 2 个 for 循环吗?
猜你喜欢
  • 2014-01-23
  • 1970-01-01
  • 2013-09-13
  • 2018-07-11
  • 1970-01-01
  • 1970-01-01
  • 2018-09-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多