112 votes

Python - Comment vérifier la monotonicité d'une liste ?

Qu'est-ce qui serait un efficace et pythonique Comment vérifier la monotonicité d'une liste ?
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

7 votes

Il est préférable d'utiliser les termes "strictement croissant" ou "non décroissant" pour lever toute ambiguïté (et de la même manière, il est préférable d'éviter "positif" et d'utiliser plutôt "non négatif" ou "strictement positif").

15 votes

@6502 le terme monotonique est défini comme un ensemble non croissant ou non décroissant de valeurs ordonnées, il n'y avait donc aucune ambiguïté dans la question.

0 votes

Si vous cherchez l'extraction de la partie des données avec une certaine monotonicité s'il vous plaît, jetez un coup d'œil à : github.com/Weilory/python-regression/blob/master/regression/

198voto

6502 Points 42700

Il est préférable d'éviter les termes ambigus comme "croissant" ou "décroissant", car on ne sait pas si l'égalité est acceptable ou non. Vous devez toujours utiliser, par exemple, "non croissant" (l'égalité est clairement acceptée) ou "strictement décroissant" (l'égalité n'est clairement PAS acceptée).

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)

18 votes

Il s'agit d'un code Python clair et idiomatique, et sa complexité est O(n) là où les réponses de tri sont toutes O(n log n). Une réponse idéale serait d'itérer sur la liste une seule fois pour que cela fonctionne avec n'importe quel itérateur, mais c'est généralement suffisant et c'est de loin la meilleure réponse parmi celles proposées jusqu'à présent. (Je proposerais bien une solution à passage unique, mais l'OP acceptant prématurément une réponse freine toute envie de le faire...)

2 votes

Juste par curiosité, j'ai testé votre implémentation par rapport à l'utilisation de sorted. La vôtre est clairement beaucoup plus lente [ J'ai utilisé L = range(10000000) ]. Il semble que la complexité de tout cela soit O(n), et je n'ai pas pu trouver d'implémentation de zip.

4 votes

Sort est spécialisé si la liste est déjà triée. Avez-vous essayé la vitesse avec une liste mélangée au hasard ? Notez également qu'avec Sort, vous ne pouvez pas faire la distinction entre un nombre strictement croissant et un nombre non décroissant. Considérez également qu'avec Python 2.x, l'utilisation de itertools.izip au lieu de zip vous pouvez obtenir une sortie anticipée (en python 3 zip fonctionne déjà comme un itérateur)

44voto

Autoplectic Points 4581

Si vous avez de grandes listes de nombres, il peut être préférable d'utiliser numpy, et si c'est le cas :

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.

0 votes

Notez que dx[0] est np.nan. Vous pourriez vouloir utiliser : dx = np.diff(x)[1 :] pour passer outre. Sinon, du moins pour moi, les appels à np.all() retournent toujours False.

0 votes

@Ryan, pourquoi dx[0] être NaN ? Quel est votre tableau d'entrée ?

1 votes

N/m, je pensais que np.diff() a fait du premier élément NaN pour que la forme de la sortie corresponde à l'entrée, mais c'est en fait un autre morceau de code qui a fait ça qui m'a mordu :)

26voto

Michael J. Barber Points 12837
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.

3 votes

La solution correcte(TM) IMO. Paradigme fonctionnel pour la victoire !

2 votes

Pourquoi utiliser itertools au lieu de simples générateurs ?

3 votes

Les paradigmes fonctionnels ne sont généralement pas "gagnants" en Python.

22voto

Jochen Ritzel Points 42916

@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))

4voto

akira Points 3632
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:]))

0 votes

Je pensais à une solution de ce genre - mais elle échoue si la liste est monotone croissante et si les deux premiers éléments sont égaux.

0 votes

@Hugh Bothwell : je vérifie maintenant le premier et le dernier pour obtenir la tendance : s'ils sont égaux alors tous les autres éléments devraient être égaux aussi, ce qui fonctionne alors pour operator.le et operator.ge.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X