पायथन - सूची नीरसता की जांच कैसे करें


82

सूची एकरसता की जाँच करने के लिए एक कुशल और पाइथोनिक तरीका क्या होगा ?
इसका मतलब यह है कि इसमें मूल्यों में वृद्धि या कमी आई है?

उदाहरण:

[0, 1, 2, 3, 3, 4]   # This is a monotonically increasing list
[4.3, 4.2, 4.2, -2]  # This is a monotonically decreasing list
[2, 3, 1]            # This is neither

5
किसी भी अस्पष्टता को छोड़ने के लिए "कड़ाई से बढ़ती" या "गैर घटती" शब्दों का उपयोग करना बेहतर है (और इसी तरह "सकारात्मक" से बचने के लिए बेहतर है और इसके बजाय "गैर नकारात्मक" या "सख्ती से सकारात्मक") का उपयोग करें
6502

14
@ 6502 शब्द मोनोटोनिक को परिभाषित मूल्यों के एक गैर-बढ़ते या गैर-घटते सेट के रूप में परिभाषित किया गया है, इसलिए प्रश्न में कोई अस्पष्टता नहीं थी।
ऑटोपेलेक्टिक

यदि आप कुछ निश्चित मोनोटोनिकता के साथ डेटा भाग को निकालने की तलाश कर रहे हैं , तो कृपया यहां देखें: github.com/Weilory/python-regression/blob/master/regression/…
Weilory

जवाबों:


160

"बढ़ती" या "घटती" जैसी अस्पष्ट शर्तों से बचना बेहतर है क्योंकि यह स्पष्ट नहीं है कि समानता स्वीकार्य है या नहीं। आपको हमेशा या तो "गैर-बढ़ती" (स्पष्ट रूप से समानता को स्वीकार किया जाता है) या "सख्ती से घटने" (स्पष्ट रूप से समानता को स्वीकार नहीं किया जाता है) के लिए उपयोग करना चाहिए।

def strictly_increasing(L):
    return all(x<y for x, y in zip(L, L[1:]))

def strictly_decreasing(L):
    return all(x>y for x, y in zip(L, L[1:]))

def non_increasing(L):
    return all(x>=y for x, y in zip(L, L[1:]))

def non_decreasing(L):
    return all(x<=y for x, y in zip(L, L[1:]))

def monotonic(L):
    return non_increasing(L) or non_decreasing(L)

15
यह स्पष्ट है, मुहावरेदार पायथन कोड, और इसकी जटिलता O (n) है जहां छांटने वाले उत्तर सभी O (n log n) हैं। एक आदर्श उत्तर सूची में केवल एक बार पुनरावृत्ति करेगा ताकि यह किसी भी पुनरावृत्त पर काम करे, लेकिन यह आमतौर पर काफी अच्छा होता है और यह अब तक का सबसे अच्छा उत्तर है। (मैं एक सिंगल-पास समाधान की पेशकश करूंगा, लेकिन ओपी समय से पहले एक उत्तर को स्वीकार करता है किसी भी आग्रह पर अंकुश लगाने के लिए मुझे ऐसा करना पड़ सकता है ...)
ग्लेन मेनार्ड

2
बस जिज्ञासा से बाहर हल के उपयोग के खिलाफ अपने कार्यान्वयन का परीक्षण किया। आपका स्पष्ट रूप से बहुत धीमा है [मैंने L = श्रेणी (10000000) का उपयोग किया है]। ऐसा लगता है कि सभी की जटिलता ओ (एन) है, और मुझे ज़िप का कार्यान्वयन नहीं मिला।
एस्टेरिस्क

4
यदि सूची पहले से ही क्रमबद्ध है तो सॉर्ट विशेष है। क्या आपने बेतरतीब ढंग से फेरबदल सूची के साथ गति की कोशिश की? यह भी ध्यान दें कि क्रमबद्ध रूप से आप सख्ती से बढ़ते और घटते हुए के बीच अंतर नहीं कर सकते हैं। यह भी विचार करें कि अजगर के साथ 2.x का उपयोग itertools.izipकरने के बजाय zipआप एक जल्दी बाहर निकल सकते हैं (अजगर 3 में zipपहले से ही एक पुनरावृत्ति की तरह काम करता है)
6502

3
@ 6502: सिर्फ एक फ़ंक्शन की आवश्यकता: आयात ऑपरेटर; def monotone (L, op): x, y के लिए zip (L, L [1:]) में सभी (op (x, y) लौटाएं) और फिर जो आप चाहते हैं उसमें फीड करें: operator.le या .ge या जो भी हो
अकीरा

5
जिप और स्लाइस ऑपरेटर दोनों पूरी सूची लौटाते हैं, सभी की शॉर्टकट क्षमताओं को मानते हुए (); itertools.izip और itertools.islice का उपयोग करके इसे बहुत बेहतर बनाया जा सकता है, क्योंकि या तो सख्ती से या कम या सख्ती से शॉर्टकट को बहुत जल्दी विफल होना चाहिए।
ह्यूग बोथवेल

37

यदि आपके पास संख्याओं की बड़ी सूची है, तो यह संख्या का उपयोग करने के लिए सबसे अच्छा हो सकता है, और यदि आप हैं:

import numpy as np

def monotonic(x):
    dx = np.diff(x)
    return np.all(dx <= 0) or np.all(dx >= 0)

चाल चलनी चाहिए।


ध्यान दें कि dx [0] np.nan है। आप इसका उपयोग करना चाह सकते हैं: dx = np.diff (x) [1:] इसे अतीत को छोड़ने के लिए। अन्यथा, कम से कम मेरे लिए, np.all () कॉल हमेशा झूठी लौटाता है।
रयान

@Ryan, क्यों होता dx[0]हो NaN? आपका इनपुट ऐरे क्या है?
दिल्लियोटमैट्रिक्स 1

1
एन / एम, मैं सोच रहा था कि np.diff()पहला तत्व बनाया गया NaNताकि आउटपुट का आकार इनपुट से मेल खाता हो, लेकिन यह वास्तव में कोड का एक अलग टुकड़ा था जिसने मुझे थोड़ा सा किया। :)
रेयान

24
import itertools
import operator

def monotone_increasing(lst):
    pairs = zip(lst, lst[1:])
    return all(itertools.starmap(operator.le, pairs))

def monotone_decreasing(lst):
    pairs = zip(lst, lst[1:])
    return all(itertools.starmap(operator.ge, pairs))

def monotone(lst):
    return monotone_increasing(lst) or monotone_decreasing(lst)

यह दृष्टिकोण O(N)सूची की लंबाई में है।


3
सही (TM) समाधान IMO। जीत के लिए कार्यात्मक प्रतिमान!
माइक

2
सादे जनरेटर के बजाय इटर्टूल का उपयोग क्यों करें?
6502

3
कार्यात्मक प्रतिमान आमतौर पर पायथन में "जीत" नहीं होते हैं।
ग्लेन मेनार्ड

@ 6502 आदत, ज्यादातर। दूसरी ओर, mapक्या वास्तव में अमूर्तता की आवश्यकता है, यहाँ, तो क्यों इसे एक जनरेटर अभिव्यक्ति के साथ फिर से बनाया जाए?
माइकल जे। नाईट

3
जोड़े की गणना के O(N)रूप में अच्छी तरह से है। आप बना सकते हैं pairs = itertools.izip(lst, itertools.islice(lst, 1, None))
टॉमस एलेंड

18

@ 6502 में सूचियों के लिए सही कोड है, मैं सिर्फ एक सामान्य संस्करण जोड़ना चाहता हूं जो सभी अनुक्रमों के लिए काम करता है:

def pairwise(seq):
    items = iter(seq)
    last = next(items)
    for item in items:
        yield last, item
        last = item

def strictly_increasing(L):
    return all(x<y for x, y in pairwise(L))

def strictly_decreasing(L):
    return all(x>y for x, y in pairwise(L))

def non_increasing(L):
    return all(x>=y for x, y in pairwise(L))

def non_decreasing(L):
    return all(x<=y for x, y in pairwise(L))

6

पांडा पैकेज इस सुविधाजनक बनाता है।

import pandas as pd

निम्न आदेश पूर्णांक या फ़्लोट की सूची के साथ काम करते हैं।

नीरस रूप से वृद्धि (():

pd.Series(mylist).is_monotonic_increasing

सख्ती से नीरस वृद्धि (>):

myseries = pd.Series(mylist)
myseries.is_unique and myseries.is_monotonic_increasing

एक अनिर्दिष्ट निजी विधि का उपयोग कर वैकल्पिक:

pd.Index(mylist)._is_strictly_monotonic_increasing

नीरस रूप से कम (reasing):

pd.Series(mylist).is_monotonic_decreasing

सख्ती से कम होने वाली (<):

myseries = pd.Series(mylist)
myseries.is_unique and myseries.is_monotonic_decreasing

एक अनिर्दिष्ट निजी विधि का उपयोग कर वैकल्पिक:

pd.Index(mylist)._is_strictly_monotonic_decreasing

4
import operator, itertools

def is_monotone(lst):
    op = operator.le            # pick 'op' based upon trend between
    if not op(lst[0], lst[-1]): # first and last element in the 'lst'
        op = operator.ge
    return all(op(x,y) for x, y in itertools.izip(lst, lst[1:]))

मैं इस तरह के एक समाधान के बारे में सोच रहा था - लेकिन यह विफल हो जाता है अगर सूची नीरस रूप से बढ़ रही है और पहले दो तत्व समान हैं।
ह्यूग बोथवेल

Bothwell @Hugh: मैं अब जाँच पहली और आखिरी प्रवृत्ति पाने के लिए: यदि वे बराबर तो कर रहे हैं सभी अन्य तत्व अच्छी तरह से जो तब दोनों operator.le और operator.ge लिए काम करता है के रूप में बराबर होना चाहिए
अकीरा

3

यहाँ reduceजटिलता का उपयोग कर एक कार्यात्मक समाधान है O(n):

is_increasing = lambda L: reduce(lambda a,b: b if a < b else 9999 , L)!=9999

is_decreasing = lambda L: reduce(lambda a,b: b if a > b else -9999 , L)!=-9999

9999अपने मानों की शीर्ष सीमा और -9999नीचे की सीमा के साथ बदलें । उदाहरण के लिए, यदि आप अंकों की सूची का परीक्षण कर रहे हैं, तो आप उपयोग कर सकते हैं 10और -1


मैंने @ 6502 के उत्तर और इसके तेज के खिलाफ इसके प्रदर्शन का परीक्षण किया ।

मामला सच: [1,2,3,4,5,6,7,8,9]

# my solution .. 
$ python -m timeit "inc = lambda L: reduce(lambda a,b: b if a < b else 9999 , L)!=9999; inc([1,2,3,4,5,6,7,8,9])"
1000000 loops, best of 3: 1.9 usec per loop

# while the other solution:
$ python -m timeit "inc = lambda L: all(x<y for x, y in zip(L, L[1:]));inc([1,2,3,4,5,6,7,8,9])"
100000 loops, best of 3: 2.77 usec per loop

2 तत्व से मामला गलत[4,2,3,4,5,6,7,8,7] :

# my solution .. 
$ python -m timeit "inc = lambda L: reduce(lambda a,b: b if a < b else 9999 , L)!=9999; inc([4,2,3,4,5,6,7,8,7])"
1000000 loops, best of 3: 1.87 usec per loop

# while the other solution:
$ python -m timeit "inc = lambda L: all(x<y for x, y in zip(L, L[1:]));inc([4,2,3,4,5,6,7,8,7])"
100000 loops, best of 3: 2.15 usec per loop

2

मैंने इस प्रश्न के सभी उत्तर अलग-अलग परिस्थितियों में दिए, और पाया कि:

  • सॉर्टिंग एक लंबे शॉट द्वारा सबसे तेज़ थी यदि सूची पहले से ही मोनोटोनिक रूप से बढ़ रही थी
  • सॉर्टिंग एक लंबे शॉट द्वारा सबसे धीमी थी यदि सूची को फेरबदल / यादृच्छिक किया गया था या यदि आदेश से बाहर तत्वों की संख्या ~ 1 से अधिक थी। पाठ्यक्रम की सूची के बाहर अधिक धीमी गति के परिणाम से मेल खाती है।
  • माइकल जे। नाइयों पद्धति सबसे तेज़ थी यदि सूची ज्यादातर नीरस रूप से बढ़ रही थी, या पूरी तरह से यादृच्छिक थी।

यहाँ इसे आज़माने का कोड है:

import timeit

setup = '''
import random
from itertools import izip, starmap, islice
import operator

def is_increasing_normal(lst):
    for i in range(0, len(lst) - 1):
        if lst[i] >= lst[i + 1]:
            return False
    return True

def is_increasing_zip(lst):
    return all(x < y for x, y in izip(lst, islice(lst, 1, None)))

def is_increasing_sorted(lst):
    return lst == sorted(lst)

def is_increasing_starmap(lst):
    pairs = izip(lst, islice(lst, 1, None))
    return all(starmap(operator.le, pairs))

if {list_method} in (1, 2):
    lst = list(range({n}))
if {list_method} == 2:
    for _ in range(int({n} * 0.0001)):
        lst.insert(random.randrange(0, len(lst)), -random.randrange(1,100))
if {list_method} == 3:
    lst = [int(1000*random.random()) for i in xrange({n})]
'''

n = 100000
iterations = 10000
list_method = 1

timeit.timeit('is_increasing_normal(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

timeit.timeit('is_increasing_zip(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

timeit.timeit('is_increasing_sorted(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

timeit.timeit('is_increasing_starmap(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

यदि सूची पहले से ही नीरस रूप से बढ़ रही थी ( list_method == 1), सबसे तेज़ सबसे धीमी थी:

  1. क्रमबद्ध
  2. स्टार्मप
  3. साधारण
  4. ज़िप

यदि सूची ज्यादातर नीरस रूप से बढ़ रही थी ( list_method == 2), सबसे तेज़ सबसे धीमी थी:

  1. स्टार्मप
  2. ज़िप
  3. साधारण
  4. क्रमबद्ध

(स्टैरमैप या जिप तेजी से निष्पादन पर निर्भर था या नहीं और मैं एक पैटर्न की पहचान नहीं कर सका। स्ट्रैपैप आमतौर पर दिखाई देता है)

यदि सूची पूरी तरह से यादृच्छिक थी ( list_method == 3), सबसे तेज़ सबसे धीमी थी:

  1. स्टार्मप
  2. ज़िप
  3. साधारण
  4. क्रमबद्ध (अत्यंत खराब)

मैंने @ एसेम चेली के तरीके की कोशिश नहीं की क्योंकि इसके लिए सूची में अधिकतम वस्तु का ज्ञान आवश्यक था
मैथ्यू मोइसन

समय की तुलना भी nसूची के आकार पर दृढ़ता से निर्भर करेगी , और जो 100000 से काफी भिन्न हो सकती है
nealmcb

1
L = [1,2,3]
L == sorted(L)

L == sorted(L, reverse=True)

sorted()अगर यह वास्तव में कुछ भी नहीं छाँटता, तो मैं जाँच के लिए जाता। बुरी तरह से नामित - जब यह नहीं है एक विधेय की तरह लगता है।
mike3996

13
आगे क्या होगा? के sorted(L)[0]बजाय का उपयोग कर min?
6502

4
यह एल्गोरिदमिक रूप से गरीब है; यह समाधान ओ (एन लॉग एन) है, जब यह समस्या ओ (एन) में तुच्छ रूप से की जा सकती है।
ग्लेन मेयार्ड

@ सभी आप से सहमत हैं, रचनात्मक आलोचना के लिए धन्यवाद।
एस्टेरिस्क

1
मैं इस थ्रेड के सभी समाधान का परीक्षण यहां , और पाया कि क्रमबद्ध विधि वास्तव में सबसे अच्छा है ... अगर सूची वास्तव में होगा- बढ़ रही है। यदि सूची में कोई आइटम क्रम से बाहर है, तो यह सबसे धीमा हो जाता है।
मैथ्यू मोइसेन

1

@ 6502 में इसके लिए सुरुचिपूर्ण अजगर कोड है। यहां सरल पुनरावृत्तियों के साथ एक वैकल्पिक समाधान है और संभावित रूप से महंगी अस्थायी स्लाइस नहीं हैं:

def strictly_increasing(L):
    return all(L[i] < L[i+1] for i in range(len(L)-1))

def strictly_decreasing(L):
    return all(L[i] > L[i+1] for i in range(len(L)-1))

def non_increasing(L):
    return all(L[i] >= L[i+1] for i in range(len(L)-1))

def non_decreasing(L):
    return all(L[i] <= L[i+1] for i in range(len(L)-1))

def monotonic(L):
    return non_increasing(L) or non_decreasing(L)


-1

यहां एक ऐसा संस्करण है जो भौतिक और गैर-भौतिक दोनों अनुक्रमों को स्वीकार करता है । यह स्वचालित रूप से निर्धारित करता है कि क्या है या नहीं monotonic, और यदि ऐसा है, तो इसकी दिशा (यानी increasingया decreasing) और strictनेस। पाठक को मदद करने के लिए इनलाइन टिप्पणियाँ प्रदान की जाती हैं। इसी तरह अंत में प्रदान किए गए परीक्षण-मामलों के लिए।

    def isMonotonic(seq):
    """
    seq.............: - A Python sequence, materialized or not.
    Returns.........:
       (True,0,True):   - Mono Const, Strict: Seq empty or 1-item.
       (True,0,False):  - Mono Const, Not-Strict: All 2+ Seq items same.
       (True,+1,True):  - Mono Incr, Strict.
       (True,+1,False): - Mono Incr, Not-Strict.
       (True,-1,True):  - Mono Decr, Strict.
       (True,-1,False): - Mono Decr, Not-Strict.
       (False,None,None) - Not Monotonic.
    """    
    items = iter(seq) # Ensure iterator (i.e. that next(...) works).
    prev_value = next(items, None) # Fetch 1st item, or None if empty.
    if prev_value == None: return (True,0,True) # seq was empty.

    # ============================================================
    # The next for/loop scans until it finds first value-change.
    # ============================================================
    # Ex: [3,3,3,78,...] --or- [-5,-5,-5,-102,...]
    # ============================================================
    # -- If that 'change-value' represents an Increase or Decrease,
    #    then we know to look for Monotonically Increasing or
    #    Decreasing, respectively.
    # -- If no value-change is found end-to-end (e.g. [3,3,3,...3]),
    #    then it's Monotonically Constant, Non-Strict.
    # -- Finally, if the sequence was exhausted above, which means
    #    it had exactly one-element, then it Monotonically Constant,
    #    Strict.
    # ============================================================
    isSequenceExhausted = True
    curr_value = prev_value
    for item in items:
        isSequenceExhausted = False # Tiny inefficiency.
        if item == prev_value: continue
        curr_value = item
        break
    else:
        return (True,0,True) if isSequenceExhausted else (True,0,False)
    # ============================================================

    # ============================================================
    # If we tricked down to here, then none of the above
    # checked-cases applied (i.e. didn't short-circuit and
    # 'return'); so we continue with the final step of
    # iterating through the remaining sequence items to
    # determine Monotonicity, direction and strictness.
    # ============================================================
    strict = True
    if curr_value > prev_value: # Scan for Increasing Monotonicity.
        for item in items:
            if item < curr_value: return (False,None,None)
            if item == curr_value: strict = False # Tiny inefficiency.
            curr_value = item
        return (True,+1,strict)
    else:                       # Scan for Decreasing Monotonicity.
        for item in items: 
            if item > curr_value: return (False,None,None)
            if item == curr_value: strict = False # Tiny inefficiency.
            curr_value = item
        return (True,-1,strict)
    # ============================================================


# Test cases ...
assert isMonotonic([1,2,3,4])     == (True,+1,True)
assert isMonotonic([4,3,2,1])     == (True,-1,True)
assert isMonotonic([-1,-2,-3,-4]) == (True,-1,True)
assert isMonotonic([])            == (True,0,True)
assert isMonotonic([20])          == (True,0,True)
assert isMonotonic([-20])         == (True,0,True)
assert isMonotonic([1,1])         == (True,0,False)
assert isMonotonic([1,-1])        == (True,-1,True)
assert isMonotonic([1,-1,-1])     == (True,-1,False)
assert isMonotonic([1,3,3])       == (True,+1,False)
assert isMonotonic([1,2,1])       == (False,None,None)
assert isMonotonic([0,0,0,0])     == (True,0,False)

मैं इस अधिक हो सकता है लगता है Pythonic, लेकिन यह मुश्किल है क्योंकि यह मध्यवर्ती संग्रह (जैसे बनाने से बचा जाता है list, genexpsआदि); विभिन्न मामलों के माध्यम से फ़िल्टर करने के लिए एक एप्रोच fall/trickle-throughऔर short-circuitएप्रोच नियोजित करता है : जैसे एज-सीक्वेंस (जैसे खाली या एक-आइटम सीक्वेंस; या सभी समान वस्तुओं के साथ अनुक्रम); एकरसता, सख्ती और इसी तरह बढ़ती या घटती पहचान। मुझे उम्मीद है यह मदद करेगा।


क्यों होता है पतन? यह अच्छी तरह से समझाया गया है और पाठक को दूसरों के लिए एक वैकल्पिक दृष्टिकोण प्रदान करता है।
NYCeyes
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.