web-dev-qa-db-fra.com

Python - Comment vérifier la liste monotonique

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
53
Jonathan
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)
120
6502

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.

31
Autoplectic
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. 

24
Michael J. Barber

@ 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))
14
Jochen Ritzel
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:]))
4
akira

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
2
BigOther

J'ai chronométré toutes les réponses à cette question dans différentes conditions et constaté que:

  • Le tri a été le plus rapide de loin si la liste augmentait déjà de façon monotone
  • Le tri était le plus lent de loin si la liste était mélangée/aléatoire ou si le nombre d'éléments en désordre était supérieur à ~ 1. Plus la liste est hors service, bien sûr, correspond à un résultat plus lent.
  • La méthode de Michael J. Barbers était la plus rapide SI la liste était principalement en augmentation monotone ou totalement aléatoire.

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:

  1. triés
  2. starmap
  3. ordinaire
  4. Zip *: français

Si la liste était généralement de plus en plus monotone (list_method == 2), le plus lent au plus lent était:

  1. starmap
  2. Zip *: français
  3. ordinaire
  4. triés

(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:

  1. starmap
  2. Zip *: français
  3. ordinaire
  4. triés (extrêmement mauvais)
1
Matthew Moisen

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.

Croissant de manière monotone (≥):

pd.Series(mylist).is_monotonic_increasing

En croissance strictement monotone (>):

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

Diminution monotone (≤):

pd.Series(mylist).is_monotonic_decreasing

Décroissant strictement monotone (<):

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
0
A-B-B
L = [1,2,3]
L == sorted(L)

L == sorted(L, reverse=True)
0
Asterisk