Python-リストの単調性をチェックする方法


82

リストの単調性をチェックするための効率的でPythonの方法は何でしょうか?
つまり、値が単調に増加または減少しているということですか?

例:

[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 2011

あなたが探している場合は、特定の単調性を持つデータの一部を抽出:、見ていてください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)です。理想的な答えは、リストを1回だけ繰り返すため、どのイテレーターでも機能しますが、通常はこれで十分であり、これまでの回答の中で断然最良の回答です。(私はシングルパスソリューションを提供しますが、OPが回答を時期尚早に受け入れることで、私がそうしなければならないかもしれない衝動を抑えます...)
Glenn Maynard

2
好奇心から、sortedを使用して実装をテストしました。あなたのものは明らかにずっと遅いです[私はL = range(10000000)を使用しました]。すべての複雑さはO(n)のようで、zipの実装が見つかりませんでした。
アスタリスク

4
リストがすでにソートされている場合、ソートは特殊化されます。ランダムにシャッフルされたリストでスピードを試しましたか?また、並べ替えでは、厳密に増加するものと減少しないものを区別できないことにも注意してください。また、itertools.izip代わりにPython 2.xを使用するとzip、早期終了を取得できることも考慮してください(Python 3ではzipすでにイテレータのように機能します)
6502

3
@ 6502:必要な関数は1つだけです:インポート演算子。def monotone(L、op):all(op(x、y)for x、y in zip(L、L [1:]))を返し、必要なものを入力します:operator.leまたは.geなど
akira 2011

5
zipとslice演算子はどちらもリスト全体を返し、all()のショートカット機能を不要にします。これは、itertools.izipとitertools.isliceを使用することで大幅に改善される可能性があります。これは、strictly_increasingまたはstrictly_decreasingのいずれかが非常に早い段階でショートカットに失敗するためです。
Hugh Bothwell 2011

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を返します。
ライアン

@ライアン、なぜだろdx[0]NaN?入力配列は何ですか?
DilithiumMatrix 2015年

1
N / m、出力の形状が入力と一致するように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
Correct(TM)ソリューションIMO。勝利のための機能的パラダイム!
mike3996 2011

2
プレーンジェネレータの代わりにitertoolsを使用するのはなぜですか?
6502

3
関数型パラダイムは通常、Pythonでは「勝利」ではありません。
グレンメイナード

@ 6502習慣、ほとんど。一方、mapここではまさに抽象化が必要なので、なぜジェネレータ式でそれを再作成するのでしょうか。
Michael J. Barber

3
ペアの計算O(N)も同様です。あなたは作ることができますpairs = itertools.izip(lst, itertools.islice(lst, 1, None))
Tomasz Elendt 2011

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

私はこのような解決策を考えていましたが、リストが単調に増加し、最初の2つの要素が等しい場合は失敗します。
Hugh Bothwell 2011

@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の答えとそのより速いものに対してその性能をテストしました。

ケース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

2番目の要素からのケース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. zip

リストがほぼ単調に増加している場合(list_method == 2)、最も速いものから最も遅いものは次のとおりです。

  1. 星図
  2. zip
  3. 正常
  4. ソート済み

(スターマップまたはzipが最速であるかどうかは実行に依存し、パターンを識別できませんでした。スターマップは通常、より高速であるように見えました)

リストが完全にランダムである場合(list_method == 3)、最も速いものから最も遅いものは次のとおりです。

  1. 星図
  2. zip
  3. 正常
  4. ソート済み(非常に悪い)

リスト内の最大項目の知識が必要だったため、@ Assem Chelliの方法を試しませんでした
Matthew Moisen 2016年

タイミング比較はまた、サイズに強く依存するnリストの、および100000からかなり変わる可能性があります
nealmcb

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

L == sorted(L, reverse=True)

sorted()それが実際に何もソートしなかったなら、私は行きました、ただチェックしてください。ひどい名前-そうでないときは述語のように聞こえます。
mike3996 2011

13
次は何ですか??のsorted(L)[0]代わりに使用 min
6502

4
これはアルゴリズム的に貧弱です。この問題がO(n)で簡単に実行できる場合、このソリューションはO(n log n)です。
グレンメイナード

@allは、建設的な批判に感謝し、皆さんに同意します。
アスタリスク

1
ここでこのスレッドのすべてのソリューションをテストしたところ、リストが実際に単調に増加している場合は、ソートされた方法が実際に最適であることがわかりました。リストに故障しているアイテムがある場合、それは最も遅くなります。
Matthew Moisen 2016年

1

@ 6502にはこのためのエレガントなPythonコードがあります。これは、より単純なイテレータを使用し、潜在的に高価な一時スライスを使用しない代替ソリューションです。

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、それは(例えば、中間コレクションを作成回避するので、それはトリッキーだlistgenexpsなど)。また、fall/trickle-throughshort-circuitアプローチを使用して、さまざまなケースをフィルタリングします。たとえば、エッジシーケンス(空のシーケンスや1つのアイテムのシーケンス、またはすべて同じアイテムのシーケンスなど)。単調性、厳密性などの増加または減少を識別します。お役に立てば幸いです。


なぜ反対票を投じるのですか?それはよく説明されており、読者に他の人への代替アプローチを提供します。
NYCeyes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.