【问题标题】:Speed up search of array element in second array加快在第二个数组中搜索数组元素
【发布时间】:2019-09-25 23:09:33
【问题描述】:

我有一个非常简单的操作,涉及两个不太大的数组:

  1. 对于第一个(更大的)数组中的每个元素,位于位置i
  2. 查找它是否存在于第二个(较小的)数组中
  3. 如果是,则在第二个数组中找到它的索引:j
  4. 将取自第三个数组(与第一个数组长度相同)的浮点数存储在位置i,第四个数组的位置j(与第二个数组长度相同)

下面的 for 块可以工作,但对于不太大的数组 (>10000) 会变得非常慢。

这个实现可以更快吗?

import numpy as np
import random

##############################################
# Generate some random data.
#'Nb' is always smaller then 'Na
Na, Nb = 50000, 40000

# List of IDs (could be any string, I use integers here for simplicity)
ids_a = random.sample(range(1, Na * 10), Na)
ids_a = [str(_) for _ in ids_a]
random.shuffle(ids_a)
# Some floats associated to these IDs
vals_in_a = np.random.uniform(0., 1., Na)

# Smaller list of repeated IDs from 'ids_a'
ids_b = random.sample(ids_a, Nb)
# Array to be filled
vals_in_b = np.zeros(Nb)
##############################################

# This block needs to be *a lot* more efficient
#
# For each string in 'ids_a'
for i, id_a in enumerate(ids_a):
    # if it exists in 'ids_b'
    if id_a in ids_b:
        # find where in 'ids_b' this element is located
        j = ids_b.index(id_a)
        # store in that position the value taken from 'ids_a'
        vals_in_b[j] = vals_in_a[i]

【问题讨论】:

  • 是的,这是一个多项式时间算法。当你增加输入的大小时,它会变得越来越慢。使用更好的算法,可能使用支持恒定时间(而不是线性时间)成员资格测试的不同数据结构。
  • 关于更好的算法胡安有什么建议吗?
  • 创建ids_b 中项目的字典到它们的索引,然后只使用该字典而不是检查数组上的成员资格。
  • 您可以 1) 连接 ids_a 和 ids_b,2) 使用关键字 return_inverse=True 应用 np.unique,3) 将逆拆分为 inv_a 和 inv_b,4) 映射值以匹配唯一性的顺序: vals[inv_a] = vals_in_a,和 5) 使用 inv_b 选择正确的值:result = vals[inv_b]
  • 明天早上我会尝试这两种方法,看看结果如何。谢谢。

标签: python arrays performance numpy


【解决方案1】:

为了捍卫我的方法,这里是权威的实现:

import itertools as it

def pp():
    la,lb = len(ids_a),len(ids_b)
    ids = np.fromiter(it.chain(ids_a,ids_b),'<S6',la+lb)
    unq,inv = np.unique(ids,return_inverse=True)
    vals = np.empty(la,vals_in_a.dtype)
    vals[inv[:la]] = vals_in_a
    return vals[inv[la:]]

(juanpa()==pp()).all()
# True

timeit(juanpa,number=100)
# 3.1373191522434354
timeit(pp,number=100)
# 2.5256317732855678

也就是说,@juanpa.arrivillaga 的建议还可以更好地实施:

import operator as op

def ja():
    return op.itemgetter(*ids_b)(dict(zip(ids_a,vals_in_a)))

(ja()==pp()).all()
# True
timeit(ja,number=100)
# 2.015202699229121

【讨论】:

  • 确实,您的答案的这种实现按预期工作。此外,您对 Juan 答案的实施是最快的。非常感谢保罗!
【解决方案2】:

我尝试了 juanpa.arrivillaga 和 Paul Panzer 的方法。第一个是迄今为止最快的。也是最简单的。第二个比我原来的方法快,但比第一个慢得多。它还有一个缺点,即vals[inv_a] = vals_in_a 这一行将浮点数存储到U5 数组中,从而将它们转换为字符串。它可以在最后转换回浮点数,但我会丢失数字(当然,除非我遗漏了一些明显的东西。

以下是实现:

def juanpa():
    dict_ids_b = {_: i for i, _ in enumerate(ids_b)}
    for i, id_a in enumerate(ids_a):
        try:
            vals_in_b[dict_ids_b[id_a]] = vals_in_a[i]
        except KeyError:
            pass

    return vals_in_b


def Paul():
    # 1) concatenate ids_a and ids_b
    ids_ab = ids_a + ids_b
    # 2) apply np.unique with keyword return_inverse=True
    vals, idxs = np.unique(ids_ab, return_inverse=True)
    # 3) split the inverse into inv_a and inv_b
    inv_a, inv_b = idxs[:len(ids_a)], idxs[len(ids_a):]
    # 4) map the values to match the order of uniques: vals[inv_a] = vals_in_a
    vals[inv_a] = vals_in_a
    # 5) use inv_b to pick the correct values: result = vals[inv_b]
    vals_in_b = vals[inv_b].astype(float)

    return vals_in_b

【讨论】:

  • try ... except 有成本;您可能应该用更简单的if id_a in dict_ids_b 替换它。
  • 我一直认为try..except 块比if..else 块便宜。你的意思是这不是真的?
  • @OneLyner,Gabriel,在这种情况下,if...else 表达式使用了更多的字典查找;因为这些并不便宜,这通常会打破平衡。
猜你喜欢
  • 2021-11-13
  • 1970-01-01
  • 1970-01-01
  • 2017-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-05
  • 1970-01-01
相关资源
最近更新 更多