用Python术语汇总连续范围

时间:2009-11-03 16:32:31

标签: python

我有一个sumranges()函数,它对元组元组中找到的所有连续数字的范围求和。举例说明:

def sumranges(nums):
    return sum([sum([1 for j in range(len(nums[i])) if
                     nums[i][j] == 0 or
                     nums[i][j - 1] + 1 != nums[i][j]]) for
                i in range(len(nums))])

>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sumranges(nums)
7

如您所见,它返回元组内连续数字的范围数,即:len((1,2,3,4),(1),(5,6),(19,20) ),(24),(29),(400))= 7.元组总是有序的。

我的问题是我的sumranges()非常糟糕。我讨厌看着它。我现在只是迭代元组和每个子元素,如果数字不是(1 +前一个数字),则分配1,并总计总和。我觉得我错过了一个更简单的方法来实现我声明的目标。有没有人知道更多的pythonic方法呢?

编辑:我已经对迄今为止给出的所有答案进行了基准测试。感谢大家的回答。

基准测试代码如下,使用100K的样本大小:

from time import time
from random import randrange
nums = [sorted(list(set(randrange(1, 10) for i in range(10)))) for
        j in range(100000)]

for func in sumranges, alex, matt, redglyph, ephemient, ferdinand:
    start = time()
    result = func(nums)
    end = time()
    print ', '.join([func.__name__, str(result), str(end - start) + ' s'])

结果如下。显示实际答案以验证所有函数是否返回正确答案:

sumranges, 250281, 0.54171204567 s
alex, 250281, 0.531121015549 s
matt, 250281, 0.843333005905 s
redglyph, 250281, 0.366822004318 s
ephemient, 250281, 0.805964946747 s
ferdinand, 250281, 0.405596971512 s

RedGlyph确实在速度方面有优势,但最简单的答案可能是费迪南德,而且可能是大多数蟒蛇的胜利。

7 个答案:

答案 0 :(得分:14)

我的2美分:

>>> sum(len(set(x - i for i, x in enumerate(t))) for t in nums)
7

它与Alex' post中描述的基本相同,但使用set代替itertools.groupby,导致表达式缩短。由于set是在C中实现的,并且一个集合的len()在恒定时间内运行,因此这也应该非常快。

答案 1 :(得分:9)

考虑:

>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> flat = [[(x - i) for i, x in enumerate(tu)] for tu in nums]
>>> print flat
[[1, 1, 1, 1], [1, 4, 4], [19, 19, 22, 26, 396]]
>>> import itertools
>>> print sum(1 for tu in flat for _ in itertools.groupby(tu))
7
>>> 

我们通过从值中减去索引来“平滑”所关注的“增加的斜坡”,将它们变成相同值的连续“运行”;然后我们确定并且可以与珍贵的itertools.groupby“运行”。这似乎是一个非常优雅(快速)的解决方案。

答案 2 :(得分:7)

只是为了展示更接近原始代码的内容:

def sumranges(nums):
    return sum( (1 for i in nums
                   for j, v in enumerate(i)
                   if j == 0 or v != i[j-1] + 1) )

这里的想法是:

  • 避免构建中间列表但使用生成器代替,它将节省一些资源
  • 当您已经选择了子元素(上面的i和v)时,避免使用索引。

但我的例子仍然需要剩下的sum()

答案 3 :(得分:1)

这是我的尝试:

def ranges(ls):
    for l in ls:
        consec = False
        for (a,b) in zip(l, l[1:]+(None,)):
            if b == a+1:
                consec = True
            if b is not None and b != a+1:
                consec = False
            if consec:
                yield 1

'''
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sum(ranges(nums))
7
'''

它成对地查看数字,检查它们是否是连续的对(除非它在列表的最后一个元素处)。每次有一对连续的数字,它就会产生1。

答案 4 :(得分:0)

这可能会以更紧凑的形式组合在一起,但我认为清晰度会受到影响:

def pairs(seq):
    for i in range(1,len(seq)):
        yield (seq[i-1], seq[i])

def isadjacent(pair):
    return pair[0]+1 == pair[1]

def sumrange(seq):
    return 1 + sum([1 for pair in pairs(seq) if not isadjacent(pair)])

def sumranges(nums):
    return sum([sumrange(seq) for seq in nums])


nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
print sumranges(nums)   # prints 7

答案 5 :(得分:0)

如果你有一个IntervalSet class,你可能会做得更好,因为那时你会扫描你的范围来构建你的IntervalSet,然后只使用集合成员的数量。

有些任务并不总是适合整洁的代码,特别是如果你需要编写性能代码。

答案 6 :(得分:0)

有一个公式,前n个数的和,1 + 2 + ... + n = n(n + 1)/ 2。那么如果你想得到i-j的总和那么它是(j(j + 1)/ 2) - (i(i + 1)/ 2)这个我肯定会简化,但你可以解决这个问题。它可能不是pythonic,但它是我会使用的。