大多数pythonic计算匹配元素的方法是可迭代的

时间:2008-10-01 10:37:20

标签: python list-comprehension

我有一个可迭代的条目,我想收集一些简单的统计数据,比如可以被2整除的所有数字的数量以及可被3整除的所有数字的数量。

我的第一个选择,虽然只迭代列表一次并避免列表扩展(并记住split loop重构),看起来相当臃肿:

(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)

虽然最小(而且在我的书中可能是最优雅的)但它并不觉得它很好地表达了意图。

所以,我的问题是:

您最喜欢哪种方式来收集这些类型的统计数据?如果你有更好的东西,请随意提供自己的选择。

清除下面的一些混淆:

  • 实际上,我的过滤谓词比这个简单的测试更复杂。
  • 我迭代的对象比数字
  • 更大,更复杂
  • 我的过滤器功能更加不同,难以参数化为一个谓词

12 个答案:

答案 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)这样的病态案例,许多其他提供的解决方案都会失败。