【问题标题】:Python Nested For Loop Array Comparison - Possibility of Optimization?Python 嵌套循环数组比较 - 优化的可能性?
【发布时间】:2015-05-16 16:27:12
【问题描述】:

我正在尝试优化嵌套的 for 循环,该循环将数组中的元素与数组中的其余元素进行比较

有两部分,第一部分例如,一个Array有3个元素,每个元素是一个字典:

[{"someKey_1":"a"}, {"someKey_1":"b"}, {"somekey_1":"a"}]


第一次迭代(第一个元素与第二个元素比较):

两个元素的 "someKey" 的测试键,因为 a != b,那么我们什么也不做


第二次迭代(第一个元素与第三个元素比较):

两个元素的“someKey”的测试键,因为a == a,我们做一些逻辑


代码:

for idx, val in enumerate(set_of_pk_values):
    for idx_2, val_2 in enumerate(set_of_pk_values):
        if (val['someKey'] == val_2['someKey'] and idx != idx_2):
                #Some Logic

第二部分与前面的例子非常相似(列表中的 3 项),在同一个字典中,我们有一个与键关联的数组(现在有一个字典,数组的每个元素中都有两个键) ,比方说:

[{"someKey_1":[b,f]}{"someKey_2":a}, 
{"someKey_1":[e,f]}{"someKey_2":b}, 
{"somekey_1":[h,k]}{"someKey_2":c}]

第一次迭代(第一个元素与第二个元素比较):

使用键循环遍历数组:someKey_1

b==b(第二个元素的 someKey_2),然后做一些逻辑

f!=b(第二个元素的 someKey_2),没有做任何逻辑


第二次迭代(第一个元素与第三个元素比较):

使用键循环遍历数组:someKey_1

b==c(第三个元素的 someKey_2),然后做一些逻辑

f!=c(第三个元素的 someKey_2),没有做任何逻辑


代码:

for idx, val in enumerate(set_of_pk_values):
    for idx_2, val_2 in enumerate(set_of_pk_values):
        for pred in val['someKey_1']:
            if(val_2['someKey_2'] == pred):
                #Some Logic

目前第一个嵌套循环的运行时间:21 秒,第二个嵌套循环约为 19 秒。与其他进程相比,1-2 秒不等,这部分显然是一个瓶颈。

谁能指出我如何优化这段简单但极其耗时的代码的正确方向?

【问题讨论】:

  • 如果你在比较 1 和 2,你真的需要比较 2 和 1 吗?
  • 第一个嵌套循环没有,但第二个嵌套循环需要。我在想有什么办法可以使这个 O(n)
  • 你能合并嵌套循环吗?除了包含一个跳过列表然后重复解析之外,您难道不能简单地使用 if 语句跳过一个循环中的项目,并可能继续吗?
  • 字典没有索引 (0..n) (你不能可靠地使用 enumerate 因为顺序可能不同)。你能告诉我们你想用代码实现什么吗?
  • Set_of_pk_values 是一个包含字典的数组。字典中有我要比较其内容的键。

标签: python arrays


【解决方案1】:

首先,我认为这应该发布在 CodeReview 上,而不是 StackOverflow 上。

StackOverflow 用于获取有关不起作用的代码的帮助。
CodeReview 是为了获得关于确实有效的代码的帮助,但你想让它变得更好。

其次,这里有一些优化建议:

  • 不要在循环内enumerate()
  • 在第一种情况下使用切片以避免无意义的比较。

以下是我将如何重写您的第一个场景:

# Check if two dictionary with the key 'predecessor' are the same,
# and they are NOT the same index (otherwise it would be comparing themselves)
# set_of_pk_values is an array of dictionaries.
for idx, val in enumerate(set_of_pk_values):
    for val_2 in set_of_pk_values[idx+1:]:  # Note the slice and lack of enumerate
        if (val['predecessor'] == val_2['predecessor']):  # Don't waste time checking indexes
            # Do Something Here, also we don't want to compare itself, because it will be true

if in代替for\if

for idx, val in enumerate(set_of_pk_values):
    if val in set_of_pk_values[idx+1:]:
        # Do Something Here, also we don't want to compare itself, because it will be true.

如果你真的想枚举,因为你只是想要多次相同的枚举,我只会在循环外执行一次并将它存储在一个变量中,然后循环它。这就是我的意思:

我错了,下面的不行,看cmets。

# Doesn't work, see comments.
# from itertools import islice
# whatIEnumerate = enumerate(set_of_pk_values)
# for idx, val in whatIEnumerate:
    # for idx_2, val_2 in islice(whatIEnumerate, idx+1, None):
        # ...

【讨论】:

  • 您好,感谢您的回复!下次如果我有代码优化,我会发布到 CodeReview。问题是,如果考虑所有键,则 set_of_pk_values 中的每个 val 都是唯一的。但是在所有字典中都会有相同键的重复值。第一部分是比较是否有任何键相同。这一行: if val in set_of_pk_values[idx+1:]: 似乎比较字典,而不是 key['predecessors'] 中的项目。
  • @user1157751:我提供了多项优化,因为我不确定哪些是重要的细节,哪些不是。如果if in 不起作用,请使用其他两种优化之一。
  • @ArtOfWarfare:有一件事阻止我对这篇文章投赞成票,那就是你使用whatIEnumerate 的方式。这不会像这样工作,因为它是一个迭代器(在 python3 中,或在 python2 中的枚举对象),因此不可下标。但是,您不在第一个 for 循环中枚举的想法是有效的。
  • @Cilyan:通过将切片符号替换为 islice() 来修复。
  • @ArtOfWarfare 仍然没有:bpaste.net/show/1dd12178faea。作为一个迭代器,它被消耗
【解决方案2】:

第一部分的优化

原创

伙计,这很糟糕:

for idx, val in enumerate(set_of_pk_values):
    for idx_2, val_2 in enumerate(set_of_pk_values):
        if (val['someKey'] == val_2['someKey'] and idx != idx_2):
            do_stuff()

第一步

只需跳过您已经尝试过的元素的索引(== 是可交换的):

for idx, val in enumerate(set_of_pk_values[:-1]):
    for val_2 in set_of_pk_values[idx+1:]
        if (val['someKey'] == val_2['someKey']):
            do_stuff()

步骤 0.1

重命名。它很丑。

for idx, first_dic in enumerate(set_of_pk_values[:-1]):
    for second_dic in set_of_pk_values[idx+1:]
        if (first_dic['someKey'] == second_dic['someKey']):
            do_stuff()

第 2 步

现在,每次循环迭代中的if 都很麻烦。通过过滤缩减列表来替换它:

hits = []
for idx, first_dic in enumerate(set_of_pk_values[:-1]):
    hits += (first_dic['someKey'], filter(lambda dic: dic['someKey'] == first_dic['someKey'], set_of_pk_values[idx:1]) ) 

hits 现在包含一个匹配元组列表:hits[i] = ( mathing first element , list of match that has idx > first element)

第三步

字典查找成本很高。使用operator.itemgetter替换它们:

from operator import itemgetter
getter = itemgetter("someKey")
hits = []
for idx, first_dic in enumerate(set_of_pk_values[:-1]):
    hits += (getter(first_dic), filter(lambda dic: getter(dic) == getter(first_dic), set_of_pk_values[idx:1]) )

第四步

坐下来看看。 for 循环的迭代并不真正依赖于上次迭代的状态。是时候进行列表推导了。

from operator import itemgetter
getter = itemgetter("someKey")
hits = [ ( getter(first_dic), filter(lambda dic: getter(dic) == getter(first_dic), set_of_pk_values[idx:-1]) ) for idx, first_dic in enumerate(set_of_pk_values[:-1])]

【讨论】:

  • 第3步,second_dic从哪里来?
  • @user1157751:错字,已修复。
  • 感谢您的回复! hits 是否有可能给我在 set_of_pk_values 中匹配的索引?同样对于第 4 步,左括号似乎与结尾括号不匹配? ==> (getter(first_dic).
  • @user1157751:感谢您发现这个错字。当然,是的;只需交换元组的第一个元素,即将( getter(first_dic) ,... 替换为( idx, ...
  • 再次感谢。这可能是不可能的,但是是否可以获得 set_of_pk_values[idx:1] 的索引,用于在步骤 3 中与 first_dic 进行比较?
【解决方案3】:

Python 中的迭代比 C 中的迭代慢。最好使用 Python 库在 C 中进行迭代。有趣的是这里没有人提到itertools...

itertools.combinations 在 C 中创建唯一的组合,然后返回组合的生成器:

import itertools
import operator
getter = operator.itemgetter('someKey_1')

for a, b in itertools.combinations(set_of_pk_values, 2):
    if getter(a) == getter(b):
        # logic?

【讨论】:

  • 您是否尝试过timeit 您的答案和其他答案?只是好奇它的性能有多好。
  • @ArtOfWarfare:这里尝试为解决方案计时。我必须说,我对结果感到非常惊讶......欢迎对我的代码发表任何评论/批评。 gist.github.com/Cilyan/50b9ee3e2dad67bb8a6b
  • @Cilyan:等等,那么我的解决方案是所有解决方案中最快的?
  • @ArtOfWarfare Err... 除非来自 Marcus Müller 的那个开始工作并且被证明更快,是的!我很惊讶双倍作为内部加速的组合效果更好,但这就是结果显示的...... :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-28
  • 1970-01-01
  • 2021-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多