Identify groups of continuous numbers in a list

前端 未结 13 1899
误落风尘
误落风尘 2020-11-22 01:12

I\'d like to identify groups of continuous numbers in a list, so that:

myfunc([2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20])

Returns:

         


        
相关标签:
13条回答
  • 2020-11-22 01:58

    Using groupby and count from itertools gives us a short solution. The idea is that, in an increasing sequence, the difference between the index and the value will remain the same.

    In order to keep track of the index, we can use an itertools.count, which makes the code cleaner as using enumerate:

    from itertools import groupby, count
    
    def intervals(data):
        out = []
        counter = count()
    
        for key, group in groupby(data, key = lambda x: x-next(counter)):
            block = list(group)
            out.append([block[0], block[-1]])
        return out
    

    Some sample output:

    print(intervals([0, 1, 3, 4, 6]))
    # [[0, 1], [3, 4], [6, 6]]
    
    print(intervals([2, 3, 4, 5]))
    # [[2, 5]]
    
    0 讨论(0)
  • 2020-11-22 01:59

    The "naive" solution which I find somewhat readable atleast.

    x = [2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 22, 25, 26, 28, 51, 52, 57]
    
    def group(L):
        first = last = L[0]
        for n in L[1:]:
            if n - 1 == last: # Part of the group, bump the end
                last = n
            else: # Not part of the group, yield current group and start a new
                yield first, last
                first = last = n
        yield first, last # Yield the last group
    
    
    >>>print list(group(x))
    [(2, 5), (12, 17), (22, 22), (25, 26), (28, 28), (51, 52), (57, 57)]
    
    0 讨论(0)
  • 2020-11-22 02:00

    more_itertools.consecutive_groups was added in version 4.0.

    Demo

    import more_itertools as mit
    
    
    iterable = [2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20]
    [list(group) for group in mit.consecutive_groups(iterable)]
    # [[2, 3, 4, 5], [12, 13, 14, 15, 16, 17], [20]]
    

    Code

    Applying this tool, we make a generator function that finds ranges of consecutive numbers.

    def find_ranges(iterable):
        """Yield range of consecutive numbers."""
        for group in mit.consecutive_groups(iterable):
            group = list(group)
            if len(group) == 1:
                yield group[0]
            else:
                yield group[0], group[-1]
    
    
    iterable = [2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20]
    list(find_ranges(iterable))
    # [(2, 5), (12, 17), 20]
    

    The source implementation emulates a classic recipe (as demonstrated by @Nadia Alramli).

    Note: more_itertools is a third-party package installable via pip install more_itertools.

    0 讨论(0)
  • 2020-11-22 02:00
    import numpy as np
    
    myarray = [2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20]
    sequences = np.split(myarray, np.array(np.where(np.diff(myarray) > 1)[0]) + 1)
    l = []
    for s in sequences:
        if len(s) > 1:
            l.append((np.min(s), np.max(s)))
        else:
            l.append(s[0])
    print(l)
    

    Output:

    [(2, 5), (12, 17), 20]
    
    0 讨论(0)
  • 2020-11-22 02:01

    Here's the answer I came up with. I'm writing the code for other people to understand, so I'm fairly verbose with variable names and comments.

    First a quick helper function:

    def getpreviousitem(mylist,myitem):
        '''Given a list and an item, return previous item in list'''
        for position, item in enumerate(mylist):
            if item == myitem:
                # First item has no previous item
                if position == 0:
                    return None
                # Return previous item    
                return mylist[position-1] 
    

    And then the actual code:

    def getranges(cpulist):
        '''Given a sorted list of numbers, return a list of ranges'''
        rangelist = []
        inrange = False
        for item in cpulist:
            previousitem = getpreviousitem(cpulist,item)
            if previousitem == item - 1:
                # We're in a range
                if inrange == True:
                    # It's an existing range - change the end to the current item
                    newrange[1] = item
                else:    
                    # We've found a new range.
                    newrange = [item-1,item]
                # Update to show we are now in a range    
                inrange = True    
            else:   
                # We were in a range but now it just ended
                if inrange == True:
                    # Save the old range
                    rangelist.append(newrange)
                # Update to show we're no longer in a range    
                inrange = False 
        # Add the final range found to our list
        if inrange == True:
            rangelist.append(newrange)
        return rangelist
    

    Example run:

    getranges([2, 3, 4, 5, 12, 13, 14, 15, 16, 17])
    

    returns:

    [[2, 5], [12, 17]]
    
    0 讨论(0)
  • 2020-11-22 02:01

    The versions by Mark Byers, Andrea Ambu, SilentGhost, Nadia Alramli, and truppo are simple and fast. The 'truppo' version encouraged me to write a version that retains the same nimble behavior while handling step sizes other than 1 (and lists as singletons elements that don't extend more than 1 step with a given step size). It is given here.

    >>> list(ranges([1,2,3,4,3,2,1,3,5,7,11,1,2,3]))
    [(1, 4, 1), (3, 1, -1), (3, 7, 2), 11, (1, 3, 1)]
    
    0 讨论(0)
提交回复
热议问题