How to do an inverse `range`, i.e. create a compact range based on a set of numbers?

前端 未结 6 647
忘了有多久
忘了有多久 2021-02-06 11:35

Python has a range method, which allows for stuff like:

>>> range(1, 6)
[1, 2, 3, 4, 5]

What I’m looking for is kind of t

6条回答
  •  星月不相逢
    2021-02-06 11:50

    This is kind of elegant but also kind of disgusting, depending on your point of view. :)

    import itertools
    
    def rangestr(iterable):
        end = start = iterable.next()
        for end in iterable:
            pass
        return "%s" % start if start == end else "%s-%s" % (start, end)
    
    class Rememberer(object):
        last = None
    
    class RangeFinder(object):
        def __init__(self):
            self.o = Rememberer()
    
        def __call__(self, x):
            if self.o.last is not None and x != self.o.last + 1:
                self.o = Rememberer()
            self.o.last = x
            return self.o
    
    def magic(iterable):
        return [rangestr(vals) for k, vals in
                itertools.groupby(sorted(iterable), RangeFinder())]
    
    
    >>> magic([5,7,9,8,6, 21,20, 3,2,1, 22,23, 50])
    ['1-3', '5-9', '20-23', '50']
    

    Explanation: it uses itertools.groupby to group the sorted elements together by a key, where the key is a Rememberer object. The RangeFinder class keeps a Rememberer object as long as a consecutive bunch of items belongs to the same range block. Once you've passed out of a given block, it replaces the Rememberer so that the key won't compare equal and groupby will make a new group. As groupby walks over the sorted list, it passes the elements one-by-one into rangestr, which constructs the string by remembering the first and the last element and ignoring everything in between.

    Is there any practical reason to use this instead of 9000's answer? Probably not; it's basically the same algorithm.

提交回复
热议问题