Python多维数组-计算非零条目数量的最有效方法

6

你好,今晚愉快。

我正在学习Python,感觉很有趣。

假设我有一个Python数组:

x = [1, 0, 0, 1, 3]

如何最快地计算列表中所有非零元素的数量(答案:3)?如果可能,我希望以最简洁明了的方式完成,不使用for循环,概念上类似于:

[counter += 1 for y in x if y > 0]

现在 - 我真正的问题是我有一个多维数组,我真正想避免的是做以下操作:

for p in range(BINS):
    for q in range(BINS):
        for r in range(BINS):
            if (mat3D[p][q][r] > 0): some_feature_set_count += 1

从我所见的少量Python代码来看,我的直觉是有一种非常简洁(且高效)的语法方式可以实现这一点。

有什么想法吗?

7个回答

12

对于单维情况:

sum(1 for i in x if i)

对于多维情况,您可以嵌套:

sum(sum(1 for i in row if i) for row in rows)

或者在一个结构体内完成所有操作:

sum(1 for row in rows
      for i in row if i)

1
+1 支持你的语法。@原帖作者:就效率而言,你的代码已经达到了最高效的水平。它具有任务所需的最小复杂度,无论是空间上还是时间上(如果你使用的是 Python < 3.0,请使用 xrange 而不是 range)。此外,我个人认为,尽管你的代码更长、不太符合 Python 风格,但比任何语法技巧都更清晰易懂。 - salezica
还有一种方法也可以工作(因为 True == 1),那就是 sum(bool(i) for i in x)...但它对于使用其他谓词进行计数更有用,比如 i>0 - Jochen Ritzel
@THC4k:它可以工作,但不是很高效(特别是对于稀疏矩阵),因为它必须为每个元素产生一个值。 - Marcelo Cantos
@Santiago:出生在智利,成长在澳大利亚,在美国度过了短暂而动荡的几年;目前居住在墨尔本。 - Marcelo Cantos
1
@Santiago:回到主题,列表推导式是 Python 的惯用语法,我认为用户应该从第一天开始就学习如何使用它们,因为它们可以产生更简洁、更高效的代码。我总是尽早教授这些东西给人们,并发现虽然他们必须经历一个简短的心理调整,但在这个过程中他们不可避免地成为更好的程序员,用更少的精力产生更好的代码。 - Marcelo Cantos
显示剩余4条评论

3

如果你正在使用Python中的多维数组,建议使用numpy,以下内容与@Marcelo的回答类似,但更加简洁:

>>> a = numpy.array([[1,2,3,0],[0,4,2,0]])
>>> sum(1 for i in a.flat if i)
5
>>>

如果你安装了numpy,你只需写np.sum(a)(假设按照传统的import numpy as np导入)。 - Marcelo Cantos

2
如果您选择使用numpy,且您的3D数组是一个numpy数组,那么这个一行代码就可以解决问题:
numpy.where(your_array_name != 0, 1, 0).sum()

示例:

In [23]: import numpy

In [24]: a = numpy.array([ [[0, 1, 2], [0, 0, 7], [9, 2, 0]], [[0, 0, 0], [1, 4, 6], [9, 0, 3]], [[1, 3, 2], [3, 4, 0], [1, 7, 9]] ])

In [25]: numpy.where(a != 0, 1, 0).sum()
Out[25]: 18

0

虽然可能不够简洁,但这是我解决任何维度问题的选择:

def sum(li):
  s = 0
  for l in li:
    if isinstance(l, list):
      s += sum(l)
    elif l:
      s += 1
  return s

2
你真的不应该遮蔽 sum。它是一个内置函数。 - aaronasterling

0
def zeros(n):
    return len(filter(lambda x:type(x)==int and x!=0,n))+sum(map(zeros,filter(lambda x:type(x)==list,n)))

不能确定这是否是最快的方法,但它是递归的,并且可以处理N维列表。

zeros([1,2,3,4,0,[1,2,3,0,[1,2,3,0,0,0]]]) => 10

0

我会稍微修改Marcelo的回答,改为以下内容:

len([x for x in my_list if x != 0])

上面的sum()把我忽悠了一秒钟,因为我以为它获取的是总值而不是计数,直到我看到开头的1。我宁愿使用len()显式地表示。


需要 O(N) 的空间,对于大型数组来说并不理想,而且这是无效的 Python 语法。我认为你的意思是 x != 0 - Marcelo Cantos
使用 sum 计算元素数量是相当惯用的。你应该习惯它,这样你就不必像在这里一样构造列表然后将它们丢弃。此外,我认为你的意思是 x 不等于 0,实际上应该是 x != 0,而这本身应该是 x - aaronasterling
两个观点都很中肯,感谢提醒。我已经修改了条件,但保留了长度,这样其他人就可以看到不应该做什么。列表推导式是生成器,对吧?我可能忘记了这一点。 - Josh Smeaton

0
使用链式操作减少数组查找次数:
from itertools import chain
BINS = [[[2,2,2],[0,0,0],[1,2,0]],
        [[1,0,0],[0,0,2],[1,2,0]],
        [[0,0,0],[1,1,1],[1,3,0]]]
sum(1 for c in chain.from_iterable(chain.from_iterable(BINS)) if c > 0)
14

我没有对此进行任何性能检查。但它不使用任何显著的内存。 请注意,它使用的是生成器表达式,而不是列表推导式。添加 [列表推导式] 语法将创建一个数组,而不是一个接一个地将一个数字提供给 sum。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接