Programing

목록에서 연속 숫자 그룹 식별

lottogame 2020. 10. 16. 06:59
반응형

목록에서 연속 숫자 그룹 식별


목록에서 연속적인 숫자 그룹을 식별하고 싶습니다.

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

보고:

[(2,5), (12,17), 20]

그리고이를 수행하는 가장 좋은 방법이 무엇인지 궁금했습니다 (특히 Python에 내장 된 것이있는 경우).

편집 : 원래 개별 숫자는 범위가 아닌 개별 숫자로 반환되어야한다는 것을 언급하는 것을 잊었습니다.


more_itertools.consecutive_groups 버전 4.0에 추가되었습니다.

데모

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]]

암호

이 도구를 적용하여 연속 된 숫자 범위를 찾는 생성기 함수를 만듭니다.

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]

소스 구현은 에뮬레이트 고전 레시피 (AS @Nadia Alramli 의해 설명 참조).

참고 : more_itertools은를 통해 설치할 수있는 타사 패키지 pip install more_itertools입니다.


편집 2 : OP 새로운 요구 사항에 응답하려면

ranges = []
for key, group in groupby(enumerate(data), lambda (index, item): index - item):
    group = map(itemgetter(1), group)
    if len(group) > 1:
        ranges.append(xrange(group[0], group[-1]))
    else:
        ranges.append(group[0])

산출:

[xrange(2, 5), xrange(12, 17), 20]

xrange를 범위 또는 다른 사용자 정의 클래스로 바꿀 수 있습니다.


Python 문서에는 이에 대한 매우 깔끔한 레시피 가 있습니다.

from operator import itemgetter
from itertools import groupby
data = [2, 3, 4, 5, 12, 13, 14, 15, 16, 17]
for k, g in groupby(enumerate(data), lambda (i,x):i-x):
    print map(itemgetter(1), g)

산출:

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

똑같은 출력을 얻으려면 다음을 수행하십시오.

ranges = []
for k, g in groupby(enumerate(data), lambda (i,x):i-x):
    group = map(itemgetter(1), g)
    ranges.append((group[0], group[-1]))

산출:

[(2, 5), (12, 17)]

편집 : 예제는 이미 문서에 설명되어 있지만 더 설명해야 할 수도 있습니다.

솔루션의 핵심은 연속 된 숫자가 모두 같은 그룹에 나타나도록 범위를 구분하는 것입니다.

데이터가 [2, 3, 4, 5, 12, 13, 14, 15, 16, 17]다음 groupby(enumerate(data), lambda (i,x):i-x)같으면 다음과 같습니다.

groupby(
    [(0, 2), (1, 3), (2, 4), (3, 5), (4, 12),
    (5, 13), (6, 14), (7, 15), (8, 16), (9, 17)],
    lambda (i,x):i-x
)

람다 함수는 요소 값에서 요소 인덱스를 뺍니다. 따라서 각 항목에 람다를 적용 할 때. groupby에 대해 다음 키가 제공됩니다.

[-2, -2, -2, -2, -8, -8, -8, -8, -8, -8]

groupby는 동일한 키 값으로 요소를 그룹화하므로 처음 4 개의 요소가 함께 그룹화됩니다.

나는 이것이 더 읽기 쉽게 만들기를 바랍니다.

python 3 버전은 초보자에게 도움이 될 수 있습니다.

먼저 필요한 라이브러리 가져 오기

from itertools import groupby
from operator import itemgetter

ranges =[]

for k,g in groupby(enumerate(data),lambda x:x[0]-x[1]):
    group = (map(itemgetter(1),g))
    group = list(map(int,group))
    ranges.append((group[0],group[-1]))

내가 적어도 어느 정도 읽을 수있는 "순진한"해결책.

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)]

목록이 정렬되었다고 가정합니다.

>>> from itertools import groupby
>>> def ranges(lst):
    pos = (j - i for i, j in enumerate(lst))
    t = 0
    for i, els in groupby(pos):
        l = len(list(els))
        el = lst[t]
        t += l
        yield range(el, el+l)


>>> lst = [2, 3, 4, 5, 12, 13, 14, 15, 16, 17]
>>> list(ranges(lst))
[range(2, 6), range(12, 18)]

여기에 가져올 필요없이 작동해야하는 것이 있습니다.

def myfunc(lst):
    ret = []
    a = b = lst[0]                           # a and b are range's bounds

    for el in lst[1:]:
        if el == b+1: 
            b = el                           # range grows
        else:                                # range ended
            ret.append(a if a==b else (a,b)) # is a single or a range?
            a = b = el                       # let's start again with a single
    ret.append(a if a==b else (a,b))         # corner case for last single/range
    return ret

사용하는 코드 groupby는 Python 3에서 제공된대로 작동하지 않으므로 이것을 사용하십시오.

for k, g in groupby(enumerate(data), lambda x:x[0]-x[1]):
    group = list(map(itemgetter(1), g))
    ranges.append((group[0], group[-1]))

이것은 표준 함수를 사용하지 않습니다. 입력에 대해 반복하지만 작동해야합니다.

def myfunc(l):
    r = []
    p = q = None
    for x in l + [-1]:
        if x - 1 == q:
            q += 1
        else:
            if p:
               if q > p:
                   r.append('%s-%s' % (p, q))
               else:
                   r.append(str(p))
            p = q = x
    return '(%s)' % ', '.join(r)

입력에는 오름차순으로 양수 만 포함되어야합니다. 입력의 유효성을 검사해야하지만 명확성을 위해이 코드는 생략되었습니다.


여기 제가 생각해 낸 답이 있습니다. 다른 사람들이 이해할 수 있도록 코드를 작성하고 있으므로 변수 이름과 주석이 상당히 장황합니다.

먼저 빠른 도우미 기능 :

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] 

그리고 실제 코드 :

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

실행 예 :

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

보고:

[[2, 5], [12, 17]]

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)

산출:

[(2, 5), (12, 17), 20]

Using numpy + comprehension lists:
With numpy diff function, consequent input vector entries that their difference is not equal to one can be identified. The start and end of the input vector need to be considered.

import numpy as np
data = np.array([2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20])

d = [i for i, df in enumerate(np.diff(data)) if df!= 1] 
d = np.hstack([-1, d, len(data)-1])  # add first and last elements 
d = np.vstack([d[:-1]+1, d[1:]]).T

print(data[d])

Output:

 [[ 2  5]   
  [12 17]   
  [20 20]]

Note: The request that individual numbers should be treated differently, (returned as individual, not ranges) was omitted. This can be reached by further post-processing the results. Usually this will make things more complex without gaining any benefit.


A short solution that works without additional imports. It accepts any iterable, sorts unsorted inputs, and removes duplicate items:

def ranges(nums):
    nums = sorted(set(nums))
    gaps = [[s, e] for s, e in zip(nums, nums[1:]) if s+1 < e]
    edges = iter(nums[:1] + sum(gaps, []) + nums[-1:])
    return list(zip(edges, edges))

Example:

>>> ranges([2, 3, 4, 7, 8, 9, 15])
[(2, 4), (7, 9), (15, 15)]

>>> ranges([-1, 0, 1, 2, 3, 12, 13, 15, 100])
[(-1, 3), (12, 13), (15, 15), (100, 100)]

>>> ranges(range(100))
[(0, 99)]

>>> ranges([0])
[(0, 0)]

>>> ranges([])
[]

This is the same as @dansalmo's solution which I found amazing, albeit a bit hard to read and apply (as it's not given as a function).

Note that it could easily be modified to spit out "traditional" open ranges [start, end), by e.g. altering the return statement:

    return [(s, e+1) for s, e in zip(edges, edges)]

I copied this answer over from another question that was marked as a duplicate of this one with the intention to make it easier findable (after I just now searched again for this topic, finding only the question here at first and not being satisfied with the answers given).


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]]

참고URL : https://stackoverflow.com/questions/2154249/identify-groups-of-continuous-numbers-in-a-list

반응형