代码之家  ›  专栏  ›  技术社区  ›  Henrik Gustafsson

计算某个不可测物中匹配元素的绝大多数方法

  •  13
  • Henrik Gustafsson  · 技术社区  · 16 年前

    我有一个不可数的条目,我想在上面收集一些简单的统计数据,比如所有可以被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 回复  |  直到 12 年前
        1
  •  16
  •   Anders Waldenborg    16 年前

    不得不多次重复列表并不优雅。

    我可能会创建一个允许执行以下操作的函数:

    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))
    
        2
  •  6
  •   Ekelund    16 年前

    ALT 4!但也许您应该将代码重构为一个函数,该函数接受一个包含可除数字(2和3)的参数。然后你可以有一个更好的函数名。

    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))
    
        3
  •  3
  •   David Webb    16 年前

    你可以用 filter 功能。

    它过滤一个列表(或者严格地说是一个iterable),生成一个只包含指定函数计算结果为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))
    

    这很好,因为它允许您将统计逻辑保存在函数中,并且 滤波器 应该很清楚。

        4
  •  2
  •   Sébastien RoccaSerra    16 年前

    我会选择你的一个小变种(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)
    # ...
    

    如果要更改Count的功能,请在一个位置更改其实现。

    注意:由于谓词很复杂,您可能希望在函数中而不是lambda中定义它们。因此,您可能希望将所有这些内容放在类中,而不是全局命名空间中。

        5
  •  1
  •   John Montgomery    16 年前

    好吧,你可以做一个列表理解/表达式来得到一组元组,在它们中进行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有什么奇怪的地方?)基本上你会做地图/缩小…

        6
  •  1
  •   Alex Coventry    16 年前

    真布尔值被强制为单位整数,假布尔值被强制为零整数。因此,如果您愿意使用scipy或numpy,请为序列的每个元素创建一个整数数组,每个数组包含每个测试的一个元素,并对数组求和。例如。

    >>> sum(scipy.array([c % 2 == 0, c % 3 == 0]) for c in xrange(10))
    array([5, 4])
    
        7
  •  0
  •   Simon    16 年前

    我肯定会看到 numpy 如果您只有数字,则数组而不是一个可iterable列表。几乎可以肯定,您可以在数组上使用一些简单的算术来完成您想要的工作。

        8
  •  0
  •   Thomas Wouters    16 年前

    不是像你所寻找的那样简单,而是更有效,它实际上与任何一个不可重复的,而不仅仅是你可以循环多次的iterables一起工作,并且你可以在不进一步复杂化的情况下扩展要检查的内容:

    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])
    
        9
  •  0
  •   community wiki ironfroggy    16 年前
    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)
    
        10
  •  0
  •   Henrik Gustafsson    16 年前

    受上述OO刺伤的启发,我也不得不试着用一个(尽管这对我要解决的问题来说太过分了:)

    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
  •   Kirk Strauser    16 年前

    alt 3,因为它不使用与“点击数”成比例的内存。考虑到像xrange(1万亿)这样的病态案例,其他许多提供的解决方案都将严重失败。

        12
  •  0
  •   seuvitor    16 年前

    这里的想法是使用约简来避免重复的迭代。此外,如果内存对您来说是一个问题,那么这不会创建任何额外的数据结构。你从一本字典开始计算你的计数器( {'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是多余的,因为您说过谓词和对象在您的应用程序中会很复杂。事实上,如果谓词真的很简单,我甚至不用费心使用字典,一个固定大小的列表就可以了。干杯: