Python-목록 단 조성을 확인하는 방법


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라는 용어는 증가하지 않거나 감소하지 않는 정렬 된 값 집합으로 정의되므로 질문에 모호성이 없었습니다.
Autoplectic

당신이 찾고있는 경우 특정 조성을와 데이터 부분을 추출 : 봐 제발 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
이것은 명확하고 관용적 인 Python 코드이며 그 복잡성은 O (n)이며 정렬 답변은 모두 O (n log n)입니다. 이상적인 대답은 목록을 한 번만 반복하여 모든 반복자에서 작동하지만 일반적으로 충분하며 지금까지 가장 좋은 대답입니다. (저는 단일 패스 솔루션을 제공 할 것입니다. 그러나 응답을 조기에 수락하는 OP는 내가 그렇게해야 할 수있는 모든 충동을 억제합니다 ...)
Glenn Maynard

2
호기심에서 sorted를 사용하여 구현을 테스트했습니다. 당신은 분명히 훨씬 느립니다 [I used L = range (10000000)]. 모든 복잡성이 O (n) 인 것 같고 zip 구현을 찾을 수 없습니다.
Asterisk

4
목록이 이미 정렬 된 경우 정렬이 전문화됩니다. 무작위로 섞인 목록으로 속도를 시도 했습니까? 또한 정렬을 사용하면 엄격하게 증가하는 것과 감소하지 않는 것을 구별 할 수 없습니다. 또한 Python 2.x를 사용하는 itertools.izip대신에 zip조기 종료를 얻을 수 있음을 고려하십시오 (python 3에서는 zip이미 반복자처럼 작동 함)
6502

3
@ 6502 : 하나의 함수 만 필요 : import 연산자; def monotone (L, op) : return all (op (x, y) for x, y in zip (L, L [1 :])) 그런 다음 원하는 것을 입력하십시오 : operator.le 또는 .ge 또는 무엇이든
akira

5
zip과 slice 연산자는 모두 전체 목록을 반환하여 all ()의 단축키 기능을 제거합니다. 이는 itertools.izip 및 itertools.islice를 사용하여 크게 개선 될 수 있습니다. 엄격하게 증가하거나 엄격하게 감소하는 경우 바로 가기가 매우 일찍 실패해야하기 때문입니다.
Hugh Bothwell

37

숫자 목록이 많은 경우 numpy를 사용하는 것이 가장 좋을 수 있으며 다음과 같은 경우에는

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 () 호출은 항상 False를 반환합니다.
Ryan

@Ryan, 왜 것 dx[0]NaN? 입력 배열은 무엇입니까?
DilithiumMatrix 2015

1
N / m, 출력의 모양이 입력과 일치하도록 np.diff()첫 번째 요소 를 만든 것으로 생각 NaN했지만 실제로는 저를 깨 물었던 다른 코드 조각이었습니다. :)
Ryan

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
The Correct (TM) 솔루션 IMO. 승리를위한 기능적 패러다임!
mike3996

2
일반 생성기 대신 itertools를 사용하는 이유는 무엇입니까?
6502

3
기능적 패러다임은 일반적으로 파이썬에서 "승리"가 아닙니다.
Glenn Maynard

@ 6502 습관, 대부분. 반면에 map추상화가 정확히 필요한데 생성기 표현식으로 다시 생성하는 이유는 무엇입니까?
Michael J. Barber

3
쌍을 계산 O(N)하는 것도 마찬가지입니다. 당신은 만들 수 pairs = itertools.izip(lst, itertools.islice(lst, 1, None))있습니다.
Tomasz Elendt

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

단조 감소 (≤) :

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

나는 이와 같은 솔루션에 대해 생각하고 있었지만 목록이 단조롭게 증가하고 처음 두 요소가 같으면 실패합니다.
Hugh Bothwell

@Hugh Bothwell : 트렌드를 파악하기 위해 첫 번째와 마지막 요소를 확인합니다. 만약 그들이 같으면 다른 모든 요소도 동일해야하고 operator.le과 operator.ge 모두에서 작동합니다
akira

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의 답변 과 더 빠른 성능을 테스트했습니다 .

사례 True : [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

두 번째 요소의 경우 False :[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보다 큰 경우 정렬 속도가 가장 느 렸습니다. 물론 목록이 순서가 맞지 않을수록 결과가 느려집니다.
  • 목록이 대부분 단조롭게 증가하거나 완전히 무작위 인 경우 Michael J. Barbers 방법이 가장 빠릅니다.

시도해 볼 수있는 코드는 다음과 같습니다.

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. 분류

(스타 맵 또는 zip이 가장 빠른지 여부는 실행에 따라 달라졌 고 패턴을 식별 할 수 없었습니다. 스타 맵은 일반적으로 더 빠른 것으로 보였습니다)

목록이 완전히 무작위 인 경우 ( list_method == 3), 가장 빠르거나 가장 느린 목록은 다음과 같습니다.

  1. 스타 맵
  2. 지퍼
  3. 표준
  4. 정렬 됨 (매우 나쁨)

이 목록의 최대 항목의 지식이 필요 나는 @Assem Chelli의 방법을 시도하지 않았다
마태 복음 Moisen

타이밍 비교는 또한 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
이것은 알고리즘 적으로 좋지 않습니다. 이 솔루션은 O (n log n)이며,이 문제는 O (n)에서 사소하게 수행 될 수 있습니다.
Glenn Maynard

@all은 건설적인 비판에 감사드립니다.
Asterisk

1
이 스레드의 모든 솔루션을 여기 에서 테스트 했으며 목록이 실제로 단조롭게 증가하는 경우 정렬 된 방법이 실제로 가장 좋습니다. 목록에 순서가 잘못된 항목이 있으면 가장 느려집니다.
마태 복음 Moisen

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)과 strictness를 결정합니다. 독자를 돕기 위해 인라인 주석이 제공됩니다. 마지막에 제공된 테스트 케이스와 유사합니다.

    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-throughshort-circuit접근 방식을 사용합니다 . 예 : Edge-sequences (예 : 빈 또는 단일 항목 시퀀스, 또는 모두 동일한 항목이있는 시퀀스); 증가 또는 감소하는 단조, 엄격함 등을 식별합니다. 도움이되기를 바랍니다.


왜 반대 투표입니까? 잘 설명되어 있으며 독자에게 다른 사람에 대한 대안적인 접근 방식을 제공합니다.
NYCeyes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.