这是一个相当概念性的问题,但是我希望我能对此得到一些好的建议。我做的很多编程工作都是使用(NumPy)数组。我经常必须匹配大小不同的两个或多个数组中的项,而我要去的第一件事是一个for循环,甚至更糟的是嵌套的for循环。我想尽可能地避免for循环,因为它们很慢(至少在Python中是这样)。需要研究,但是当您必须迭代某些内容时,您(作为经验丰富的程序员)会想到一个一般的思考过程吗?想要避免这种情况:

small_array = np.array(["one", "two"])
big_array = np.array(["one", "two", "three", "one"])

for i in range(len(small_array)):
    for p in range(len(big_array)):
        if small_array[i] == big_array[p]:
            print "This item is matched: ", small_array[i]


我知道有多种方法可以实现这一目标,但是我对通用的思维方法(如果存在)感兴趣。

评论

您正在寻找函数式编程:lambda表达式,高阶函数,生成表达式等。Google那些。

我想尽可能避免for循环,因为它们很慢(至少在Python中如此)。听起来您在这里解决了错误的问题。如果需要迭代某些事物,则需要迭代某些事物。无论您使用哪种Python构造,都会对性能造成类似的影响。如果您的代码很慢,那不是因为您有for循环。这是因为您正在做不必要的工作,或者在Python方面进行了可以在C方面完成的工作。在您的示例中,您正在做额外的工作;您可以只用一个循环而不是两个循环来完成它。

@Doval不幸的是-在NumPy中。对于实际的数组大小,执行元素逐加的Python for循环很容易比矢量化NumPy运算符(不仅用C编写,而且使用SSE指令和其他技巧)慢几倍(!)。
上面的一些评论似乎误解了这个问题。在NumPy中进行编程时,如果可以对计算进行矢量化,则可获得最佳结果—也就是说,将Python中的显式循环替换为NumPy中的全数组操作。从概念上讲,这与Python中的普通编程有很大不同,并且需要花费一些时间来学习。因此,我认为OP寻求关于如何去做的建议是合理的。

@PieterB:是的,是的。 “向量化”与“选择最佳算法”不同。它们是提出有效实施方案的两个困难源,因此最好一次考虑一下。

#1 楼

这是学习有效使用NumPy时的常见概念难题。通常,Python中的数据处理最好用迭代器来表示,以保持较低的内存使用量,最大程度地提高与I / O系统并行的机会,并提供算法的重用和组合。

但是NumPy却把所有事情都彻底搞清楚了:最好的方法是将算法表达为一系列全数组操作,以最小化在慢速Python解释器中花费的时间,并最大化在快速编译的NumPy中花费的时间例程。

这是我采用的一般方法:


保留该函数的原始版本(您确信是正确的)以便可以对其进行测试
从内而外地工作:即从最里面的循环开始,看是否可以向量化;然后,当您完成此操作后,移出一个级别并继续。
花很多时间阅读NumPy文档。那里有很多功能和操作,而且它们的命名并不总是很漂亮,因此有必要了解它们。尤其是,如果您发现自己在想:“如果只有某项功能如此之类的话”,那么花十分钟寻找它就值得。它通常在某个地方。

不能替代练习,所以我将为您提供一些示例问题。每个问题的目标是重写函数,使其完全矢量化:也就是说,它由整个数组上的一系列NumPy操作组成,没有本机Python循环(没有forwhile语句,没有迭代器或理解) )。

问题1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result


问题2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result


问题3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)


下面的扰流板。如果您在研究我的解决方案之前先做好准备,就会得到最好的结果!

答案1


np.sum(x)* np.sum(y)


答案2


np.sum (np.searchsorted(np.sort(x),y​​))


答案3


np.where(x ==丢失,值x)


评论


等一下,最后一个答案是否有错字?还是NumPy修改了Python解释代码的方式?

–伊兹卡塔
2014年8月27日,0:05

@Izkata本身并不会进行任何修改,但是定义了应用于数组的逻辑操作以返回布尔数组。

– sapi
2014年8月27日在1:10

@sapi啊,我错过了doctest中发生的事情,以为这些都是简单的清单

–伊兹卡塔
2014年8月27日在1:45

也许应该有一种嵌入APL的方法?

–user251748
18-2-27在17:05

我爱你做功课。

–科雷·图吉(Koray Tugay)
18年6月6日在1:05

#2 楼

为了使事情更快,您必须仔细阅读数据结构并使用适当的数据结构。

对于小数组和大数组的非平凡大小(假设small = 100元素和big = 10.000元素)一种方法是对小型数组进行排序,然后对大型数组进行迭代,然后使用二进制搜索在小型数组中查找匹配的元素。 N log N)(对于小的小型数组和非常大的大型数组,它更接近O(N)),但是您的嵌套循环解为O(N ^ 2)

。哪种数据结构最有效取决于实际问题。

#3 楼

您可以使用字典来显着优化性能

这是另一个示例:

locations = {}
for i in range(len(airports)):
    locations[airports["abb"][i][1:-1]] = (airports["height"][i], airports["width"][i])

for i in range(len(uniqueData)):
    h, w = locations[uniqueData["dept_apt"][i]]
    uniqueData["dept_apt_height"][i] = h
    uniqueData["dept_apt_width"][i] = w


评论


显然,这仍在使用“ for-loop”思想流派。

– 8bittree
16-10-12在14:12