Quel serait un moyen efficient et Pythonic de vérifier la monotonie des listes?
c'est-à-dire qu'il a des valeurs monotones croissantes ou décroissantes?
Exemples:
[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)
Si vous avez de grandes listes de nombres, il vaut peut-être mieux utiliser numpy, et si vous êtes:
import numpy as np
def monotonic(x):
dx = np.diff(x)
return np.all(dx <= 0) or np.all(dx >= 0)
devrait faire l'affaire.
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)
Cette approche est O(N)
dans la longueur de la liste.
@ 6502 a le code parfait pour les listes, je veux juste ajouter une version générale qui fonctionne pour toutes les séquences:
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:]))
Voici une solution fonctionnelle utilisant reduce
de complexité 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
Remplacez 9999
par la limite supérieure de vos valeurs et -9999
par la limite inférieure. Par exemple, si vous testez une liste de chiffres, vous pouvez utiliser 10
et -1
.
J'ai testé ses performances contre la réponse de @ 6502 et sa rapidité.
Case 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
Case False à partir du 2e élément: [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
J'ai chronométré toutes les réponses à cette question dans différentes conditions et constaté que:
Voici le code pour l'essayer:
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)
Si la liste était déjà en augmentation monotone (list_method == 1
), le plus lent au plus lent était:
Si la liste était généralement de plus en plus monotone (list_method == 2
), le plus lent au plus lent était:
(Que ce soit le starmap ou Zip qui soit le plus rapide dépendait de l'exécution et je ne pouvais pas identifier un motif. Starmap semblait être généralement plus rapide)
Si la liste était complètement aléatoire (list_method == 3
), le plus lent au plus lent était:
Ceci est possible en utilisant Pandas que vous pouvez installer via pip install pandas
.
import pandas as pd
Les commandes suivantes fonctionnent avec une liste d’entiers ou de flottants.
pd.Series(mylist).is_monotonic_increasing
myseries = pd.Series(mylist)
myseries.is_unique and myseries.is_monotonic_increasing
Alternative utilisant une méthode privée non documentée:
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
Alternative utilisant une méthode privée non documentée:
pd.Index(mylist)._is_strictly_monotonic_decreasing
L = [1,2,3]
L == sorted(L)
L == sorted(L, reverse=True)