我有一个可迭代的条目,我想收集一些简单的统计数据,比如可以被2整除的所有数字的数量以及可被3整除的所有数字的数量。
我的第一个选择,虽然只迭代列表一次并避免列表扩展(并记住split loop重构),看起来相当臃肿:p>
(alt 1)
r = xrange(1, 10)
twos = 0
threes = 0
for v in r:
if v % 2 == 0:
twos+=1
if v % 3 == 0:
threes+=1
print twos
print threes
这看起来相当不错,但是有将表达式扩展到列表的缺点:
(alt 2)
r = xrange(1, 10)
print len([1 for v in r if v % 2 == 0])
print len([1 for v in r if v % 3 == 0])
我真正喜欢的是类似这样的功能:
(alt 3)
def count(iterable):
n = 0
for i in iterable:
n += 1
return n
r = xrange(1, 10)
print count(1 for v in r if v % 2 == 0)
print count(1 for v in r if v % 3 == 0)
但这看起来很像没有功能的事情。最后的变体是:
(alt 4)
r = xrange(1, 10)
print sum(1 for v in r if v % 2 == 0)
print sum(1 for v in r if v % 3 == 0)
虽然最小(而且在我的书中可能是最优雅的)但它并不觉得它很好地表达了意图。
所以,我的问题是:
您最喜欢哪种方式来收集这些类型的统计数据?如果你有更好的东西,请随意提供自己的选择。
清除下面的一些混淆:
答案 0 :(得分:17)
不得不多次遍历列表并不优雅恕我直言。
我可能会创建一个允许执行的功能:
twos, threes = countmatching(xrange(1,10),
lambda a: a % 2 == 0,
lambda a: a % 3 == 0)
起点可能是这样的:
def countmatching(iterable, *predicates):
v = [0] * len(predicates)
for e in iterable:
for i,p in enumerate(predicates):
if p(e):
v[i] += 1
return tuple(v)
顺便说一句,“itertools食谱”有一个像你的alt4一样的食谱。
def quantify(seq, pred=None):
"Count how many times the predicate is true in the sequence"
return sum(imap(pred, seq))
答案 1 :(得分:6)
替补4!但也许你应该将代码重构为一个函数,该函数接受一个应该包含可分数(两个和三个)的参数。然后你可以有一个更好的功能名称。
def methodName(divNumber, r):
return sum(1 for v in r if v % divNumber == 0)
print methodName(2, xrange(1, 10))
print methodName(3, xrange(1, 10))
答案 2 :(得分:3)
您可以使用filter
功能。
它过滤一个列表(或严格来说是一个可迭代的),生成一个新列表,其中只包含指定函数计算结果为true的项目。
r = xrange(1, 10)
def is_div_two(n):
return n % 2 == 0
def is_div_three(n):
return n % 3 == 0
print len(filter(is_div_two,r))
print len(filter(is_div_three,r))
这很好,因为它允许您将统计逻辑包含在函数中,并且filter
的意图应该非常明确。
答案 3 :(得分:2)
我会选择你的(alt 4)的一个小变种:
def count(predicate, list):
print sum(1 for x in list if predicate(x))
r = xrange(1, 10)
count(lambda x: x % 2 == 0, r)
count(lambda x: x % 3 == 0, r)
# ...
如果您想更改计数,请在一个地方更改其实施。
注意:由于您的谓词很复杂,您可能希望在函数中定义它们而不是lambdas。因此,您可能希望将所有这些放在一个类而不是全局命名空间中。
答案 4 :(得分:1)
那么你可以做一个列表理解/表达式来获得一组带有该stat测试的元组,然后将其减少以获得总和。
r=xrange(10)
s=( (v % 2 == 0, v % 3 == 0) for v in r )
def add_tuples(t1,t2):
return tuple(x+y for x,y in zip(t1, t2))
sums=reduce(add_tuples, s, (0,0)) # (0,0) is starting amount
print sums[0] # sum of numbers divisible by 2
print sums[1] # sum of numbers divisible by 3
使用生成器表达式等应该意味着你只会运行一次迭代器(除非reduce做什么奇怪的事情?)。基本上你会做map / reduce ......
答案 5 :(得分:1)
真正的布尔值被强制转换为单位整数,而假布尔值则强制为零整数。因此,如果您乐意使用scipy或numpy,请为序列的每个元素创建一个整数数组,每个数组包含每个测试的一个元素,并对数组求和。 E.g。
>>> sum(scipy.array([c % 2 == 0, c % 3 == 0]) for c in xrange(10))
array([5, 4])
答案 6 :(得分:0)
如果您只有数字,我肯定会查看numpy数组而不是可迭代列表。几乎可以肯定,你可以通过对阵列进行一些简洁的算术来做你想做的事。
答案 7 :(得分:0)
不像你想要的那样简洁,但效率更高,它实际上适用于任何迭代,而不仅仅是你可以循环多次的迭代,你可以扩展要检查的东西而不会使它进一步复杂化:
r = xrange(1, 10)
counts = {
2: 0,
3: 0,
}
for v in r:
for q in counts:
if not v % q:
counts[q] += 1
# Or, more obscure:
#counts[q] += not v % q
for q in counts:
print "%s's: %s" % (q, counts[q])
答案 8 :(得分:0)
from itertools import groupby
from collections import defaultdict
def multiples(v):
return 2 if v%2==0 else 3 if v%3==0 else None
d = defaultdict(list)
for k, values in groupby(range(10), multiples):
if k is not None:
d[k].extend(values)
答案 9 :(得分:0)
这里的想法是使用简化来避免重复迭代。此外,如果内存对您来说是个问题,这不会创建任何额外的数据结构。您可以从包含计数器({'div2': 0, 'div3': 0}
)的字典开始,然后沿着迭代递增它们。
def increment_stats(stats, n):
if n % 2 == 0: stats['div2'] += 1
if n % 3 == 0: stats['div3'] += 1
return stats
r = xrange(1, 10)
stats = reduce(increment_stats, r, {'div2': 0, 'div3': 0})
print stats
如果你想计算比除数更复杂的东西,那么使用更加面向对象的方法(具有相同的优点)是合适的,封装了统计提取的逻辑。
class Stats:
def __init__(self, div2=0, div3=0):
self.div2 = div2
self.div3 = div3
def increment(self, n):
if n % 2 == 0: self.div2 += 1
if n % 3 == 0: self.div3 += 1
return self
def __repr__(self):
return 'Stats(%d, %d)' % (self.div2, self.div3)
r = xrange(1, 10)
stats = reduce(lambda stats, n: stats.increment(n), r, Stats())
print stats
请指出任何错误。
@Henrik:我认为第一种方法的可维护性较差,因为你必须在一个地方控制字典的初始化并在另一个地方进行更新,以及必须使用字符串来引用每个stat(而不是具有属性)。在这种情况下,我不认为OO是矫枉过正的,因为你说谓词和对象在你的应用程序中会很复杂。事实上,如果谓词非常简单,我甚至不愿意使用字典,单个固定大小的列表就可以了。干杯:)
答案 10 :(得分:0)
受到上面的OO-stab的启发,我不得不亲自尝试一下(虽然这对我正试图解决的问题有点过分:)
class Stat(object):
def update(self, n):
raise NotImplementedError
def get(self):
raise NotImplementedError
class TwoStat(Stat):
def __init__(self):
self._twos = 0
def update(self, n):
if n % 2 == 0: self._twos += 1
def get(self):
return self._twos
class ThreeStat(Stat):
def __init__(self):
self._threes = 0
def update(self, n):
if n % 3 == 0: self._threes += 1
def get(self):
return self._threes
class StatCalculator(object):
def __init__(self, stats):
self._stats = stats
def calculate(self, r):
for v in r:
for stat in self._stats:
stat.update(v)
return tuple(stat.get() for stat in self._stats)
s = StatCalculator([TwoStat(), ThreeStat()])
r = xrange(1, 10)
print s.calculate(r)
答案 11 :(得分:0)
替代3,因为它没有使用与“命中”数量成比例的内存。鉴于像xrange(one_trillion)这样的病态案例,许多其他提供的解决方案都会失败。