効率的でPythonicリストの単調性をチェックする方法は何でしょうか?
i.e値が単調に増加または減少していること
例:
[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
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)
数の大きなリストがある場合は、numpyを使用するのが最善かもしれません。
import numpy as np
def monotonic(x):
dx = np.diff(x)
return np.all(dx <= 0) or np.all(dx >= 0)
トリックを行う必要があります。
_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)
です。
@ 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))
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:]))
複雑さO(n)
のreduce
を使用した機能的ソリューションは次のとおりです。
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's answer およびその高速に対してパフォーマンスをテストしました。
ケース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
さまざまな条件下でこの質問のすべての答えを時間を計ったところ、次のことがわかりました。
試してみるコードは次のとおりです。
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
)、最速から最速まで:
リストがほとんど単調に増加している場合(list_method == 2
)、最速から最速まで:
(スターマップまたはZipが実行に最も速く依存していたかどうかにかかわらず、パターンを特定できませんでした。通常、スターマップはより高速に見えました)
リストが完全にランダムな場合(list_method == 3
)、最速から最速まで:
L = [1,2,3]
L == sorted(L)
L == sorted(L, reverse=True)
これは、pip install pandas
を介してインストールできる Pandas を使用して可能です。
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