किसी सूची में निरंतर संख्याओं के समूहों को पहचानें


93

मैं किसी सूची में निरंतर संख्याओं के समूहों की पहचान करना चाहता हूं, ताकि:

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

यह दिखाता है:

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

और सोच रहा था कि ऐसा करने का सबसे अच्छा तरीका क्या था (खासकर अगर पायथन में कुछ इनबिल्ट है)।

संपादित करें: ध्यान दें मैं मूल रूप से यह उल्लेख करना भूल गया था कि अलग-अलग संख्याओं को अलग-अलग संख्याओं के रूप में लौटाया जाना चाहिए, न कि श्रेणियों में।


3
क्या वह रिटर्न एक स्ट्रिंग है?
मार्क बायर्स

आदर्श रूप से ऐसी चीज को पसंद करेंगे जो रेंज बनाम स्टैंडअलोन नंबरों के लिए एक अलग प्रकार का उपयोग करता है।
मीकमेकाना

जवाबों:


52

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]

स्रोत कार्यान्वयन एक emulates क्लासिक नुस्खा (के रूप में @Nadia Alramli द्वारा प्रदर्शन)।

नोट: more_itertoolsथर्ड-पार्टी पैकेज इंस्टाल करने योग्य है pip install more_itertools


121

EDIT 2: ओपी की नई आवश्यकता का उत्तर देने के लिए

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 को रेंज या किसी अन्य कस्टम वर्ग के साथ बदल सकते हैं।


पायथन डॉक्स में इसके लिए एक बहुत ही स्वादिष्ट नुस्खा है:

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
)

लैम्ब्डा फ़ंक्शन तत्व सूचकांक को तत्व मूल्य से घटाता है। तो जब आप प्रत्येक आइटम पर लैम्ब्डा लागू करते हैं। आपको ग्रुपबी के लिए निम्नलिखित कुंजियाँ मिलेंगी:

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

ग्रुपबी समूह तत्वों को समान कुंजी मान द्वारा, इसलिए पहले 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]))

4
लगभग py3k में काम करता है, इसके अलावा इसकी आवश्यकता होती है lambda x:x[0]-x[1]
साइलेंटगॉस्ट

क्या आप मल्टी-कैरेक्टर चर नामों का उपयोग कर सकते हैं? मानचित्र () या ग्रुपबी () के साथ परिचित किसी के लिए, किलो, आई और एक्स के अर्थ स्पष्ट नहीं हैं।
मिकमेकाना

1
यह समान चर नामों के साथ अजगर दस्तावेजों से कॉपी किया गया था। मैंने अब नाम बदल दिए हैं।
नादिया अल्रामली

1
आपको दूसरी संख्या को xrange / रेंज में बढ़ाने की आवश्यकता होगी क्योंकि यह गैर-समावेशी है। दूसरे शब्दों में [2,3,4,5] == xrange(2,6), नहीं xrange(2,5)। यह एक नई समावेशी श्रेणी डेटा प्रकार को परिभाषित करने के लायक हो सकता है।
IceArdor

10
पायथन 3 पहले उदाहरण पर एक वाक्यविन्यास त्रुटि फेंकता है। यहां पहले 2 पंक्तियों को अजगर 3 पर काम करने के लिए अद्यतन किया गया है:for key, group in groupby(enumerate(data), lambda i: i[0] - i[1]): group = list(map(itemgetter(1), group))
derek73

16

"भोला" समाधान जो मुझे कुछ पठनीय लगता है।

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

मुझे यह उत्तर बहुत पसंद है क्योंकि यह अभी तक पढ़ने योग्य है। हालाँकि जो संख्याएँ श्रेणियों के बाहर होती हैं, उन्हें एकल अंकों के रूप में मुद्रित किया जाना चाहिए, न कि टुपल्स के रूप में (जैसा कि मैं आउटपुट को प्रारूपित करूँगा और अलग-अलग संख्याओं के लिए अलग-अलग
स्वरूपणों की

4
अन्य उत्तर सुंदर और बुद्धिमान लग रहे थे, लेकिन यह मेरे लिए अधिक समझ में आता है और मेरी तरह एक शुरुआतकर्ता को मेरी जरूरतों के अनुसार इसका विस्तार करने की अनुमति दी।
बेनी

गैर-श्रेणी के ट्यूपल्स को एकल अंकों के रूप में मुद्रित करने के लिए सूची समझ का उपयोग कर सकते हैं: print([i if i[0] != i[1] else i[0] for i in group(x)])
नेक्सस

14

मान लिया जाए कि आपकी सूची क्रमबद्ध है:

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

2
[j - i for i, j in enumerate(lst)]चालाक है :-)
जोहान रिट्जेल

9

यहां यह कुछ ऐसा है जो बिना किसी आयात के काम करना चाहिए:

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

6

कृपया ध्यान दें कि groupbyपायथन 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]))

3

यह एक मानक फ़ंक्शन का उपयोग नहीं करता है - यह सिर्फ इनपुट पर दोहराता है, लेकिन यह काम करना चाहिए:

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)

ध्यान दें कि यह आवश्यक है कि इनपुट में आरोही क्रम में केवल सकारात्मक संख्याएं हों। आपको इनपुट को मान्य करना चाहिए, लेकिन यह कोड स्पष्टता के लिए छोड़ा गया है।


1

यहाँ जवाब मैं के साथ आया हूँ। मैं अन्य लोगों को समझने के लिए कोड लिख रहा हूं, इसलिए मैं चर नामों और टिप्पणियों के साथ काफी क्रियाशील हूं।

पहले एक त्वरित सहायक समारोह:

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

>>> getranges([2, 12, 13])आउटपुट: [[12, 13]]। क्या वह जानबूझकर था?
साइलेंटगॉस्ट

हां, मुझे अलग-अलग संख्याओं के लिए (पृष्ठ पर अधिकांश उत्तरों के अनुसार) ठीक करने की आवश्यकता है। अभी इस पर काम चल रहा है।
मिकमेकाना

वास्तव में मैं नादिया के जवाब को पसंद करता हूं, ग्रुपबी () मानक समारोह की तरह लगता है जो मैं चाहता था।
मिकमेकाना

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

1

का उपयोग करते हुए groupbyऔर countसे itertoolsहमें एक छोटी समाधान देता है। विचार यह है कि, बढ़ते क्रम में, सूचकांक और मूल्य के बीच का अंतर समान रहेगा।

सूचकांक का ट्रैक रखने के लिए, हम एक itertools.count का उपयोग कर सकते हैं , जो कोड क्लीनर का उपयोग करता है 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

कुछ नमूना उत्पादन:

print(intervals([0, 1, 3, 4, 6]))
# [[0, 1], [3, 4], [6, 6]]

print(intervals([2, 3, 4, 5]))
# [[2, 5]]

0

Numpy + समझने की सूचियों का उपयोग करना:
numpy diff फ़ंक्शन के फलस्वरूप, इनपुट इनपुट वेक्टर प्रविष्टियाँ जो कि उनके अंतर के बराबर नहीं हैं, को पहचाना जा सकता है। इनपुट वेक्टर की शुरुआत और अंत पर विचार करने की आवश्यकता है।

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

आउटपुट:

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

नोट: अनुरोध है कि अलग-अलग संख्याओं को अलग-अलग व्यवहार किया जाना चाहिए, (व्यक्तिगत के रूप में लौटाया जाता है, सीमाएं नहीं) को छोड़ दिया गया था। परिणामों को आगे पोस्ट-प्रोसेसिंग द्वारा इस तक पहुँचा जा सकता है। आमतौर पर यह किसी भी लाभ के बिना चीजों को और अधिक जटिल बना देगा।


0

एक छोटा समाधान जो अतिरिक्त आयात के बिना काम करता है। यह किसी भी पुनरावृत्ति को स्वीकार करता है, अनसुने इनपुट्स को सॉर्ट करता है, और डुप्लिकेट आइटम को हटाता है:

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

उदाहरण:

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

यह @ dansalmo के समाधान के समान है जो मुझे अद्भुत लगा, पढ़ने और लागू करने के लिए थोड़ा कठिन है (क्योंकि यह एक फ़ंक्शन के रूप में नहीं दिया गया है)।

ध्यान दें कि इसे आसानी से "पारंपरिक" खुली सीमाओं को बाहर करने के लिए संशोधित किया जा सकता है [start, end), उदाहरण के लिए रिटर्न स्टेटमेंट को बदलकर:

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

मैंने इस उत्तर को एक और प्रश्न से कॉपी किया था, जिसे इस के डुप्लिकेट के रूप में चिह्नित किया गया था ताकि इसे आसानी से खोजा जा सके (क्योंकि मैंने अभी इस विषय के लिए फिर से खोज की थी, केवल पहली बार में यहां प्रश्न ढूंढना और उत्तरों से संतुष्ट नहीं होना दिया हुआ)।


0

द्वारा संस्करणों मार्क बायर्स , एंड्रिया अम्बु , SilentGhost , नादिया Alramli , और truppo सरल और तेजी से कर रहे हैं। 'Truppo' संस्करण ने मुझे एक ऐसा संस्करण लिखने के लिए प्रोत्साहित किया, जो 1 के अलावा अन्य चरण आकारों को संभालते हुए एक ही फुर्तीला व्यवहार रखता है (और एकल तत्वों के रूप में सूचीबद्ध करता है जो किसी दिए गए चरण आकार के साथ 1 से अधिक चरण का विस्तार नहीं करते हैं)। यह यहाँ दिया गया है

>>> 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)]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.