web-dev-qa-db-fra.com

Moyen pythonique de vérifier si une liste est triée ou non

Existe-t-il un moyen pythonique de vérifier si une liste est déjà triée dans ASC ou DESC

listtimestamps = [1, 2, 3, 5, 6, 7]

quelque chose comme isttimestamps.isSorted() qui retourne True ou False.

Je veux entrer une liste d'horodatages pour certains messages et vérifier si les transactions sont apparues dans le bon ordre.

119
anijhaw

En fait, nous ne donnons pas la réponse recherchée par anijhaw. Voici la doublure:

all(l[i] <= l[i+1] for i in xrange(len(l)-1))

Pour Python 3:

all(l[i] <= l[i+1] for i in range(len(l)-1))
179
Wai Yip Tung

Je voudrais juste utiliser

if sorted(lst) == lst:
    # code here

à moins que ce ne soit une très grande liste, auquel cas vous pouvez créer une fonction personnalisée.

si vous voulez simplement le trier s'il n'est pas trié, oubliez la vérification et triez-le.

lst.sort()

et n'y pense pas trop.

si vous voulez une fonction personnalisée, vous pouvez faire quelque chose comme

def is_sorted(lst, key=lambda x: x):
    for i, el in enumerate(lst[1:]):
        if key(el) < key(lst[i]): # i is the index of the previous element
            return False
    return True

Ce sera O(n) si la liste est déjà triée bien que (et O(n)) dans une boucle for à ce moment-là!! ) donc, à moins que vous ne vous attendiez à ce que ce ne soit pas trié (et assez aléatoire) la plupart du temps, je trierais à nouveau la liste.

64
aaronasterling

Cette forme d'itérateur est 10-15% plus rapide que l'indexation d'entiers:

# python2 only
if str is bytes:
    from itertools import izip as Zip

def is_sorted(l):
    return all(a <= b for a, b in Zip(l, l[1:]))
36
PaulMcG

Une belle façon d'implémenter ceci est d'utiliser la fonction imap de itertools:

from itertools import imap, tee
import operator

def is_sorted(iterable, compare=operator.le):
  a, b = tee(iterable)
  next(b, None)
  return all(imap(compare, a, b))

Cette implémentation est rapide et fonctionne sur tous les iterables.

18

J'ai couru un repère et sorted(lst, reverse=True) == lst était le plus rapide pour les longues listes et all(l[i] >= l[i+1] for i in xrange(len(l)-1)) était le plus rapide pour les petites listes. Ces tests ont été exécutés sur un MacBook Pro 2010 13 "(Core2 Duo à 2,66 GHz, 4 Go de RAM DDR3 à 1067 MHz, Mac OS X 10.6.5).

PDATE: J'ai révisé le script afin que vous puissiez l'exécuter directement sur votre propre système. La version précédente avait des bugs. De plus, j'ai ajouté des entrées triées et non triées.

  • Idéal pour les listes triées courtes: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Idéal pour les longues listes triées: sorted(l, reverse=True) == l
  • Idéal pour les listes courtes non triées: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Idéal pour les longues listes non triées: all(l[i] >= l[i+1] for i in xrange(len(l)-1))

Donc, dans la plupart des cas, il y a un gagnant clair.

PDATE: Les réponses de aaronsterling (n ° 6 et n ° 7) sont en réalité les plus rapides dans tous les cas. # 7 est le plus rapide car il ne dispose pas d'une couche d'indirection pour rechercher la clé.

#!/usr/bin/env python

import itertools
import time

def benchmark(f, *args):
    t1 = time.time()
    for i in xrange(1000000):
        f(*args)
    t2 = time.time()
    return t2-t1

L1 = range(4, 0, -1)
L2 = range(100, 0, -1)
L3 = range(0, 4)
L4 = range(0, 100)

# 1.
def isNonIncreasing(l, key=lambda x,y: x >= y): 
    return all(key(l[i],l[i+1]) for i in xrange(len(l)-1))
print benchmark(isNonIncreasing, L1) # 2.47253704071
print benchmark(isNonIncreasing, L2) # 34.5398209095
print benchmark(isNonIncreasing, L3) # 2.1916718483
print benchmark(isNonIncreasing, L4) # 2.19576501846

# 2.
def isNonIncreasing(l):
    return all(l[i] >= l[i+1] for i in xrange(len(l)-1))
print benchmark(isNonIncreasing, L1) # 1.86919999123
print benchmark(isNonIncreasing, L2) # 21.8603689671
print benchmark(isNonIncreasing, L3) # 1.95684289932
print benchmark(isNonIncreasing, L4) # 1.95272517204

# 3.
def isNonIncreasing(l, key=lambda x,y: x >= y): 
    return all(key(a,b) for (a,b) in itertools.izip(l[:-1],l[1:]))
print benchmark(isNonIncreasing, L1) # 2.65468883514
print benchmark(isNonIncreasing, L2) # 29.7504849434
print benchmark(isNonIncreasing, L3) # 2.78062295914
print benchmark(isNonIncreasing, L4) # 3.73436689377

# 4.
def isNonIncreasing(l):
    return all(a >= b for (a,b) in itertools.izip(l[:-1],l[1:]))
print benchmark(isNonIncreasing, L1) # 2.06947803497
print benchmark(isNonIncreasing, L2) # 15.6351969242
print benchmark(isNonIncreasing, L3) # 2.45671010017
print benchmark(isNonIncreasing, L4) # 3.48461818695

# 5.
def isNonIncreasing(l):
    return sorted(l, reverse=True) == l
print benchmark(isNonIncreasing, L1) # 2.01579380035
print benchmark(isNonIncreasing, L2) # 5.44593787193
print benchmark(isNonIncreasing, L3) # 2.01813793182
print benchmark(isNonIncreasing, L4) # 4.97615599632

# 6.
def isNonIncreasing(l, key=lambda x, y: x >= y): 
    for i, el in enumerate(l[1:]):
        if key(el, l[i-1]):
            return False
    return True
print benchmark(isNonIncreasing, L1) # 1.06842684746
print benchmark(isNonIncreasing, L2) # 1.67291283607
print benchmark(isNonIncreasing, L3) # 1.39491200447
print benchmark(isNonIncreasing, L4) # 1.80557894707

# 7.
def isNonIncreasing(l):
    for i, el in enumerate(l[1:]):
        if el >= l[i-1]:
            return False
    return True
print benchmark(isNonIncreasing, L1) # 0.883186101913
print benchmark(isNonIncreasing, L2) # 1.42852401733
print benchmark(isNonIncreasing, L3) # 1.09229516983
print benchmark(isNonIncreasing, L4) # 1.59502696991
10
Nathan Farrington

Je ferais ceci (en volant beaucoup de réponses ici [Aaron Sterling, Wai Yip Tung, en quelque sorte de Paul McGuire] et surtout Armin Ronacher ):

from itertools import tee, izip

def pairwise(iterable):
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

def is_sorted(iterable, key=lambda a, b: a <= b):
    return all(key(a, b) for a, b in pairwise(iterable))

Une belle chose: vous n'avez pas à réaliser le deuxième itérable de la série (contrairement à une tranche de liste).

9
hughdbrown

J'utilise ce one-liner basé sur numpy.diff ():

def issorted(x):
    """Check if x is sorted"""
    return (numpy.diff(x) >= 0).all() # is diff between all consecutive entries >= 0?

Je ne l'ai pas vraiment comparé à une autre méthode, mais je suppose qu'elle est plus rapide que toute méthode pure Python, en particulier pour les grands n, car la boucle dans numpy.diff (probablement) s'exécute directement dans C (n-1 soustractions suivies de n-1 comparaisons).

Cependant, vous devez faire attention si x est un entier non signé, ce qui pourrait provoquer un dépassement inférieur entier en silence dans numpy.diff (), entraînant ainsi un faux positif. Voici une version modifiée:

def issorted(x):
    """Check if x is sorted"""
    try:
        if x.dtype.kind == 'u':
            # x is unsigned int array, risk of int underflow in np.diff
            x = numpy.int64(x)
    except AttributeError:
        pass # no dtype, not an array
    return (numpy.diff(x) >= 0).all()
3
Martin Spacek

Pas très pythonique du tout, mais il nous faut au moins une réponse reduce(), n'est-ce pas?

def is_sorted(iterable):
    prev_or_inf = lambda prev, i: i if prev <= i else float('inf')
    return reduce(prev_or_inf, iterable, float('-inf')) < float('inf')

La variable accumulateur stocke simplement la dernière valeur vérifiée, et si une valeur est inférieure à la valeur précédente, l'accumulateur est défini sur l'infini (et sera donc toujours l'infini à la fin, car la "valeur précédente" sera toujours supérieure à l'actuel).

3
orlade

Bien que je ne pense pas qu'il y ait une garantie pour que le sorted intégré appelle sa fonction cmp avec i+1, i, il semble que ce soit le cas pour CPython.

Pour que vous puissiez faire quelque chose comme:

def my_cmp(x, y):
   cmpval = cmp(x, y)
   if cmpval < 0:
      raise ValueError
   return cmpval

def is_sorted(lst):
   try:
      sorted(lst, cmp=my_cmp)
      return True
   except ValueError:
      return False

print is_sorted([1,2,3,5,6,7])
print is_sorted([1,2,5,3,6,7])

Ou de cette façon (sans si déclarations -> EAFP qui a mal tourné? ;-)):

def my_cmp(x, y):
   assert(x >= y)
   return -1

def is_sorted(lst):
   try:
      sorted(lst, cmp=my_cmp)
      return True
   except AssertionError:
      return False
3
Anthon

Ceci est similaire à la première réponse, mais je l’aime mieux parce qu’il évite l’indexation explicite. En supposant que votre liste porte le nom lst, vous pouvez générer
(item, next_item) tuples de votre liste avec Zip:

all(x <= y for x,y in Zip(lst, lst[1:]))

Dans Python 3, Zip renvoie déjà un générateur, dans Python 2, vous pouvez utiliser itertools.izip pour une meilleure efficacité de la mémoire.

Petite démo:

>>> lst = [1, 2, 3, 4]
>>> Zip(lst, lst[1:])
[(1, 2), (2, 3), (3, 4)]
>>> all(x <= y for x,y in Zip(lst, lst[1:]))
True
>>> 
>>> lst = [1, 2, 3, 2]
>>> Zip(lst, lst[1:])
[(1, 2), (2, 3), (3, 2)]
>>> all(x <= y for x,y in Zip(lst, lst[1:]))
False

Le dernier échoue lorsque le tuple (3, 2) est évalué.

Bonus: vérification des générateurs finis (!) Non indexables:

>>> def gen1():
...     yield 1
...     yield 2
...     yield 3
...     yield 4
...     
>>> def gen2():
...     yield 1
...     yield 2
...     yield 4
...     yield 3
... 
>>> g1_1 = gen1()
>>> g1_2 = gen1()
>>> next(g1_2)
1
>>> all(x <= y for x,y in Zip(g1_1, g1_2))
True
>>>
>>> g2_1 = gen2()
>>> g2_2 = gen2()
>>> next(g2_2)
1
>>> all(x <= y for x,y in Zip(g2_1, g2_2))
False

Assurez-vous d'utiliser itertools.izip ici si vous utilisez Python 2, sinon vous iriez à l'encontre de l'objectif de ne pas avoir à créer des listes à partir des générateurs.

3
timgeb

SapphireSun a tout à fait raison. Vous pouvez simplement utiliser lst.sort(). L'implémentation de tri de Python (TimSort) vérifie si la liste est déjà triée. Si tel est le cas, sort () sera terminé en temps linéaire. Cela ressemble à une façon pythonique de s'assurer qu'une liste est triée;)

3
Wai Yip Tung

Comme l'a noté @aaronsterling, la solution suivante est la plus courte et semble la plus rapide lorsque le tableau est trié et pas trop petite: def is_sorted (lst): return (tri (lst) == lst)

Si la plupart du temps, le tableau n'est pas trié, il serait souhaitable d'utiliser une solution qui n'analyse pas tout le tableau et renvoie False dès qu'un préfixe non trié est découvert. Voici la solution la plus rapide que j'ai pu trouver, elle n'est pas particulièrement élégante:

def is_sorted(lst):
    it = iter(lst)
    try:
        prev = it.next()
    except StopIteration:
        return True
    for x in it:
        if prev > x:
            return False
        prev = x
    return True

En utilisant le point de repère de Nathan Farrington, cela permet une meilleure durée d'exécution que d'utiliser trié (lst) dans tous les cas, sauf lorsqu'il est exécuté sur la grande liste triée.

Voici les résultats de référence sur mon ordinateur.

trié (lst lst solution) ==

  • L1: 1.23838591576
  • L2: 4.19063091278
  • L3: 1.17996287346
  • L4: 4.68399500847

Deuxième solution:

  • L1: 0.81095790863
  • L2: 0.802397012711
  • L3: 1.06135106087
  • L4: 8.82761001587
2
Amit Moscovich

Juste pour ajouter une autre manière (même si cela nécessite un module supplémentaire): iteration_utilities.all_monotone :

>>> from iteration_utilities import all_monotone
>>> listtimestamps = [1, 2, 3, 5, 6, 7]
>>> all_monotone(listtimestamps)
True

>>> all_monotone([1,2,1])
False

Pour vérifier l'ordre de DESC:

>>> all_monotone(listtimestamps, decreasing=True)
False

>>> all_monotone([3,2,1], decreasing=True)
True

Il existe également un paramètre strict si vous devez vérifier les séquences monotones strictement (si les éléments successifs ne doivent pas être égaux).

Ce n'est pas un problème dans votre cas, mais si vos séquences contiennent des valeurs nan, certaines méthodes échoueront, par exemple avec les éléments triés:

def is_sorted_using_sorted(iterable):
    return sorted(iterable) == iterable

>>> is_sorted_using_sorted([3, float('nan'), 1])  # definetly False, right?
True

>>> all_monotone([3, float('nan'), 1])
False

Notez que iteration_utilities.all_monotone est plus rapide que les autres solutions mentionnées ici, en particulier pour les entrées non triées (voir repère ).

2
MSeifert

Si vous voulez le moyen le plus rapide pour les tableaux numpy, utilisez numba , qui, si vous utilisez conda, devrait déjà être installé

Le code sera rapide car il sera compilé par numba

import numba
@numba.jit
def issorted(vec, ascending=True):
    if len(vec) < 2:
        return True
    if ascending:
        for i in range(1, len(vec)):
            if vec[i-1] > vec[i]:
                return False
        return True
    else:
        for i in range(1, len(vec)):
            if vec[i-1] < vec[i]:
                return False
        return True

et alors:

>>> issorted(array([4,9,100]))
>>> True
2
tal

Python 3.6.8

from more_itertools import pairwise

class AssertionHelper:
    @classmethod
    def is_ascending(cls, data: iter) -> bool:
        for a, b in pairwise(data):
            if a > b:
                return False
        return True

    @classmethod
    def is_descending(cls, data: iter) -> bool:
        for a, b in pairwise(data):
            if a < b:
                return False
        return True

    @classmethod
    def is_sorted(cls, data: iter) -> bool:
        return cls.is_ascending(data) or cls.is_descending(data)
>>> AssertionHelper.is_descending((1, 2, 3, 4))
False
>>> AssertionHelper.is_ascending((1, 2, 3, 4))
True
>>> AssertionHelper.is_sorted((1, 2, 3, 4))
True

1
Chweng Mega

Paresseux

from itertools import tee

def is_sorted(l):
    l1, l2 = tee(l)
    next(l2, None)
    return all(a <= b for a, b in Zip(l1, l2))
1
Sergey11g

Manière la plus simple:

def isSorted(arr):
  i = 1
  while i < len(arr):
    if(result[i] < result[i - 1]):
      return False
    i += 1
  return True
0
Aluis92
from functools import reduce

# myiterable can be of any iterable type (including list)
isSorted = reduce(lambda r, e: (r[0] and (r[1] or r[2] <= e), False, e), myiterable, (True, True, None))[0]

La valeur de réduction dérivée est un nuplet en 3 parties de (triéSoFarFlag, firstTimeFlag, lastElementValue). Cela commence par (True, True, None), qui est également utilisé comme résultat pour une liste vide (considérée comme triée car il n'y a pas éléments de commande). Lors du traitement de chaque élément, il calcule de nouvelles valeurs pour le Tuple (en utilisant les valeurs précédentes de Tuple avec la valeur suivante elementValue):

[0] (sortedSoFarFlag) evaluates true if: prev_0 is true and (prev_1 is true or prev_2 <= elementValue)
[1] (firstTimeFlag): False
[2] (lastElementValue): elementValue

Le résultat final de la réduction est un tuple de:

[0]: True/False depending on whether the entire list was in sorted order
[1]: True/False depending on whether the list was empty
[2]: the last element value

La première valeur est celle qui nous intéresse, nous utilisons donc [0] pour saisir cela du résultat réduit.

0
Mr Weasel