加快嵌套循环比较

Posted

技术标签:

【中文标题】加快嵌套循环比较【英文标题】:Speeding up nested loop comparison 【发布时间】:2020-07-13 20:26:34 【问题描述】:

我有两个数组arr1arr2,大小为(90000,1)(120000,1)。我想知道arr1axis=0 的任何元素是否存在于arr2 上。然后将他们的位置写到列表中,然后将其删除。这将确保两个列表中的任何元素都不能在另一个列表中找到。目前,我正在使用for 循环:

list_conflict=[]
for i in range (len(arr1)):
    for j in range (len(arr2)):
        if (arr1[i]==arr2[j]):
            list_conflict.append([i,j])

fault_index_pos = np.unique([x[0] for x in list_conflict])
fault_index_neg = np.unique([x[1] for x in list_conflict])

X_neg = np.delete(X_neg,fault_index_neg,axis=0)
X_pos = np.delete(X_pos,fault_index_pos,axis=0)

它在外部循环中获取arr1 的元素,并将其与arr2 的每个元素进行详尽的比较。如果找到匹配项,则附加索引list_conflict,第一个元素为arr1 位置,第二个元素为arr2。然后fault_index_posfault_index_neg 被压缩到唯一的元素中,因为arr1 的元素可能在arr2 的多个位置,并且列表将具有循环位置。最后,通过将fault_index 列表作为要删除的索引,使用np.delete 删除匹配的元素。

我正在寻找一种更快的冲突比较方法,将其称为multiprocessingvectorization 或其他任何名称。您可以说这不会花费太多时间,但实际上数组的维度是 (x,8,10),但为了清楚起见,我将它们缩短了。

【问题讨论】:

【参考方案1】:

忽略 numpy 部分,在纯 Python 中可以更快地找到冲突的索引对,所花费的时间与 len(a) 加上 len(b) 加上冲突的数量成正比,而不是嵌套循环所花费的时间与向量长度的乘积

def conflicts(a, b):
    from collections import defaultdict
    elt2ix = defaultdict(list)
    for i, elt in enumerate(a):
        elt2ix[elt].append(i)
    for j, elt in enumerate(b):
        if elt in elt2ix:
            for i in elt2ix[elt]:
                yield i, j

然后,例如,

for pair in conflicts([1, 2, 4, 5, 2], [2, 3, 8, 4]):
    print(pair)

展示

(1, 0)
(4, 0)
(2, 3)

它们是 2 和 4 匹配出现的索引。

【讨论】:

如何从 both 输入创建反向字典(可能称它们为elt2ix_aelt2ix_b)然后执行:for elt in set(elt2ix_a) & set(elt2ix_b): yield from itertools.product(elt2ix_a[elt], elt2ix_b[elt]) 会工作,占用更多内存,也没有计时。注意set(dict1) & set(dict2)也可以写成dict1.keys() & dict2.keys()。也没有计时;-) 谢谢,我不知道你可以用 dict_keys 做到这一点。 Python 3 的“dict 视图”在几个方面表现得像集合。这里非常简短的概述:***.com/questions/47273297/… 如果 a 和 b 是列表列表而不是整数列表怎么办?我得到不可散列的类型:elt2ix[elt].append(i)的列​​表错误【参考方案2】: 我会使用 numpy 上的 pandas 来创建布尔掩码 需要 4 行代码
import numpy as np
import pandas as pd

# create test data
np.random.seed(1)
a = np.random.randint(10, size=(10, 1))
np.random.seed(1)
b = np.random.randint(8, 15, size=(10, 1))

# create dataframe
df_a = pd.DataFrame(a)
df_b = pd.DataFrame(b)

# find unique values in df_a
unique_a = df_a[0].unique().tolist()

# create a Boolean mask and return only values of df_b not found in df_a
values_not_in_a = df_b[~df_b[0].isin(unique_a)].to_numpy()

价值观

a = array([[5],
           [8],
           [9],
           [5],
           [0],
           [0],
           [1],
           [7],
           [6],
           [9]])

b = array([[13],
           [11],
           [12],
           [ 8],
           [ 9],
           [11],
           [13],
           [ 8],
           [ 8],
           [ 9]])

# final output array
values_not_in_a = array([[13],
                         [11],
                         [12],
                         [11],
                         [13]])

仅使用 numpy

​​>
import numpy

# create test data
np.random.seed(1)
a = np.random.randint(10, size=(10, 1))
np.random.seed(1)
b = np.random.randint(8, 15, size=(10, 1))

ua = np.unique(a)  # unique values of a
ub = np.unique(b)  # unique values of b

mask_b = np.isin(b, ua, invert=True)
mask_a = np.isin(a, ub, invert=True)

b_values_not_in_a = b[mask_b]
a_values_not_in_b = a[mask_a]

# b_values_not_in_a
array([13, 11, 12, 11, 13])

# a_values_not_in_b
array([5, 5, 0, 0, 1, 7, 6])

timeit

# using the following arrays
np.random.seed(1)
a = np.random.randint(10, size=(90000, 1))
np.random.seed(1)
b = np.random.randint(8, 15, size=(120000, 1))

%%timeit
5.6 ms ± 151 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

【讨论】:

@0x5453 确实如此,正如我在帖子的第一个项目符号中所说的那样。但是,使用 pandas,我不需要转置数组的形状来创建遮罩。 @colt.exe 这改变了原始问题的范围。 (90000, 80) 是 90k 行和 80 列。现在尚不清楚您要完成什么。您现在是否尝试从 2d 数组的每一列中删除 1d 数组中的所有值? @colt.exe 但是,如果我理解正确,如果您有一个 1d 数组,您希望从 2d 数组的每一列中删除其值,您可以按照我的说明进行操作,但使用循环将掩码应用于二维数组的每一列。但是,我认为您不能将它们组合回一个数组,因为列的长度必须相同。【参考方案3】:

请阅读一些关于 NumPy 的向量功能以及 Python 的序列包含运算符的教程。您正在尝试编写一个大型应用程序,该应用程序非常需要您尚未学习的语言工具。

也就是说,最快的方法可能是将每个转换为set 并采取设置的交叉点。对于 N 个元素的序列/集合,所涉及的操作是 O(n);您的嵌套循环是 O(N*M) (在两个序列大小上)。

任何有关 Python 集的教程都会引导您完成此操作。

【讨论】:

嗯,我当然想到了集合,但没有受过教育的猜测可能有更多更快的方法来实现这一点。但是我完全同意我必须学习一些教程,并且当我有空闲时间时肯定会这样做。欣赏您的建议。【参考方案4】:

正如@Prune 建议的那样,这是一个使用sets 的解决方案:

overlap = np.array(list(set(arr1) & set(arr2)))  # Depending on array shapes you may need to flatten or slice first
arr1 = arr1[~np.isin(arr1, overlap)]
arr2 = arr2[~np.isin(arr2, overlap)]

【讨论】:

我没有以这种方式使用掩码,因为它确实适用于 OP 呈现的数据的形状。 arrarys 是 np 数组,在将它们重塑为 (-1,80) 重叠后会抛出不可散列的类型:列表。

以上是关于加快嵌套循环比较的主要内容,如果未能解决你的问题,请参考以下文章

为啥多处理会减慢嵌套的 for 循环?

如何加快大数据集中的两个嵌套 for 循环

Python: Pandas - 嵌套循环需要很长时间才能完成。如何加快速度?

如何使用熊猫来加快这个嵌套循环的速度?

优化四重嵌套“for”循环

R摆脱嵌套的for循环