【问题标题】:converting a list of integers into range in python在python中将整数列表转换为范围
【发布时间】:2011-06-05 10:15:06
【问题描述】:

python中是否存在可以将增加的整数列表转换为范围列表的东西

例如给定集合 {0, 1, 2, 3, 4, 7, 8, 9, 11} 我想得到 { {0,4}, {7,9}, {11,11} }。

我可以写一个程序来做这个,但是想知道python中是否有内置函数

【问题讨论】:

  • 好吧,我可以自信地说,我不知道这样的功能。很难自信地说我不知道​​的东西不存在....
  • stackoverflow.com/questions/3429510/… 中提出和回答了几乎相同的问题
  • 我认为你提出的结果应该是一个范围列表.. cf。我的回答如下!

标签: python list range integer


【解决方案1】:

使用itertools.groupby() 会产生一个简洁但棘手的实现:

import itertools

def ranges(i):
    for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
        b = list(b)
        yield b[0][1], b[-1][1]

print(list(ranges([0, 1, 2, 3, 4, 7, 8, 9, 11])))

输出:

[(0, 4), (7, 9), (11, 11)]

【讨论】:

  • 这真的很有用,我想知道你能否解释一下这个方法是如何工作的,这样我才能理解它的功能。如果可能的话,这会很棒。
  • 要使用 'sorted(set(i))' 处理非唯一和未排序的输入环绕 'i',请参阅:stackoverflow.com/a/43091576/1201614
  • 这个食谱也可以在more_itertools.consecutive_groups 中找到。见演示here
【解决方案2】:

您可以使用list comprehensiongenerator expression 以及enumerate()itertools.groupby() 的组合:

>>> import itertools
>>> l = [0, 1, 2, 3, 4, 7, 8, 9, 11]
>>> [[t[0][1], t[-1][1]] for t in
... (tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x))]
[[0, 4], [7, 9], [11, 11]]

首先,enumerate() 将根据列表项及其各自的索引构建元组:

>>> [t for t in enumerate(l)]
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 7), (6, 8), (7, 9), (8, 11)]

然后groupby() 将使用它们的索引和它们的值之间的差异对这些元组进行分组(对于连续值,这将是相等的):

>>> [tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x)]
[((0, 0), (1, 1), (2, 2), (3, 3), (4, 4)), ((5, 7), (6, 8), (7, 9)), ((8, 11),)]

从那里,我们只需要从每个组的第一个和最后一个元组的值构建列表(如果组只包含一个项目,这将是相同的)。

您还可以使用[(t[0][1], t[-1][1]) ...] 来构建范围元组列表而不是嵌套列表,甚至可以使用((t[0][1], t[-1][1]) ...) 将整个表达式转换为可迭代的generator,这将在运行中懒惰地构建范围元组。

【讨论】:

  • lambda 参数解包在哪些 Python 版本中起作用? python3.9 -c 'fn1 = lamba (a, b): a + b' SyntaxError: invalid syntax --- 我认为它只存在于过时的 Python 2 中。请参阅 stackoverflow.com/questions/21892989/… --- 好的,我已经找到了 PEP:python.org/dev/peps/pep-3113 --- 恕我直言,答案应该是固定的。
【解决方案3】:

这是对非常优雅的answer 的改进。这个涵盖了non-uniquenon-sorted输入,并且也兼容python3

import itertools

def to_ranges(iterable):
    iterable = sorted(set(iterable))
    for key, group in itertools.groupby(enumerate(iterable),
                                        lambda t: t[1] - t[0]):
        group = list(group)
        yield group[0][1], group[-1][1]

例子:

>>> x
[44, 45, 2, 56, 23, 11, 3, 4, 7, 9, 1, 2, 2, 11, 12, 13, 45]

>>> print( list(to_ranges(x))) 
[(1, 4), (7, 7), (9, 9), (11, 13), (23, 23), (44, 45), (56, 56)]

【讨论】:

    【解决方案4】:

    生成范围对:

    def ranges(lst):
        s = e = None
        r = []
        for i in sorted(lst):
            if s is None:
                s = e = i
            elif i == e or i == e + 1:
                e = i
            else:
                r.append((s, e))
                s = e = i
        if s is not None:
            r.append((s, e))
        return r
    

    例子:

    >>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30]
    >>> print repr(ranges(lst))
    [(1, 1), (5, 7), (12, 12), (15, 18), (30, 30)]
    

    作为生成器:

    def gen_ranges(lst):
        s = e = None
        for i in sorted(lst):
            if s is None:
                s = e = i
            elif i == e or i == e + 1:
                e = i
            else:
                yield (s, e)
                s = e = i
        if s is not None:
            yield (s, e)
    

    例子:

    >>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30]
    >>> print repr(','.join(['%d' % s if s == e else '%d-%d' % (s, e) for (s, e) in gen_ranges(lst)]))
    '1,5-7,12,15-18,30'
    

    【讨论】:

      【解决方案5】:

      这个生成器:

      def ranges(p):
          q = sorted(p)
          i = 0
          for j in xrange(1,len(q)):
              if q[j] > 1+q[j-1]:
                  yield (q[i],q[j-1])
                  i = j
          yield (q[i], q[-1])
      
      sample = [0, 1, 2, 3, 4, 7, 8, 9, 11]
      print list(ranges(sample))
      print list(ranges(reversed(sample)))
      print list(ranges([1]))
      print list(ranges([2,3,4]))
      print list(ranges([0,2,3,4]))
      print list(ranges(5*[1]))
      

      产生这些结果:

      [(0, 4), (7, 9), (11, 11)]
      [(0, 4), (7, 9), (11, 11)]
      [(1, 1)]
      [(2, 4)]
      [(0, 0), (2, 4)]
      [(1, 1)]
      

      请注意,重复数字的运行会被压缩。我不知道这是否是你想要的。如果不是,请将> 更改为!=

      我明白你的问题。我查看了itertools 并试图想出一个可以在几行 Python 中完成的解决方案,它可以被称为 “几乎是内置的”,但我不能来什么都有。

      【讨论】:

        【解决方案6】:

        由于已经有 2 年左右没有新的答案了,这里是僵尸爱好者的答案!

        如果您不想使用 itertools 或生成器,以下使用逻辑(!)。它使用一个集合(参见问题!)作为输入并返回一个适当范围的列表;不过,调整代码以适应它很容易。

        def ranges(l_set: set) ->list:
            rb_set = sorted(l_set - {i +1 for i in l_set})
            re_set = sorted(l_set - {i -1 for i in l_set})
            return [range(rb_set[i], re_set[i]+1) for i in range(len(rb_set))]
        

        例如:

        >>>ranges({6, 9, 10, 7, 8, 2, 3, 14})
        [range(2, 4), range(6, 11), range(14, 15)]
        
        >>>ranges({6, 7, 3, 15, 8, 5, 12, 0, 12, 7, 15, 6, 14, 8, 16})
        [range(0, 1), range(3, 4), range(5, 9), range(12, 13), range(14, 17)]
        

        【讨论】:

        • 作为一种改进,我建议使用rb_set = sorted(l_set.difference(i+1 for i in l_set)),因为它可以避免在内存中创建另一个临时集。同样对于最终列表,您可以使用[range(b, e+1) for b, e in zip(rb_set, re_set)],或者如果您想使用元组而不是list(zip(rb_set, re_set))
        【解决方案7】:

        没有内置的,或者在我知道的任何库中。不是很有帮助,我知道,但我从来没有遇到过你想要的东西。

        以下是至少对您的程序的一些想法(在 C++ 中,但它可以为您提供一些其他想法):

        Converting sets of integers into ranges

        【讨论】:

          【解决方案8】:

          如果python中没有这样的功能,这里有一个实现

          p = []
          last = -2                                                            
          start = -1
          
          for item in list:
              if item != last+1:                        
                  if start != -1:
                      p.append([start, last])
                  start = item
              last = item
          
          p.append([start, last])
          

          【讨论】:

            【解决方案9】:

            短一点:

            ranges=lambda l:map(lambda x:(x[0][1],x[-1][1]),map(lambda (x,y):list(y),itertools.groupby(enumerate(l),lambda (x,y):x-y)))
            

            【讨论】:

            • 在我看来,更短没有任何改进。
            【解决方案10】:

            我认为其他答案很难理解,而且可能效率低下。希望这更容易和更快。

            def ranges(ints):
                ints = sorted(set(ints))
                range_start = previous_number = ints[0]
                for number in ints[1:]:
                    if number == previous_number + 1:
                        previous_number = number
                    else:
                        yield range_start, previous_number
                        range_start = previous_number = number
                yield range_start, previous_number
            

            【讨论】:

            • 为什么要重新发明轮子?
            【解决方案11】:

            相关问题for the case when step sizes other than 1 are of interest 和此问题here 的几乎重复。 here.

            【讨论】:

              猜你喜欢
              • 2012-07-13
              • 2017-10-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-09-18
              • 1970-01-01
              相关资源
              最近更新 更多