Modo Pythonic per verificare se un elenco è ordinato o meno


145

Esiste un modo pitonico per verificare se un elenco è già ordinato in ASCoDESC

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

qualcosa del genere isttimestamps.isSorted()ritorna Trueo False.

Voglio inserire un elenco di timestamp per alcuni messaggi e verificare se le transazioni sono apparse nell'ordine corretto.

Risposte:


212

In realtà non stiamo dando la risposta che Anijhaw sta cercando. Ecco la copertina:

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

Per Python 3:

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

2
bello. Potresti volerlo avvolgere in una funzione in modo da poter passare una keyfunzione da usare. key=lambda x, y: x < yrende un buon default.
aaronasterling,

3
Una combinazione di un paio di soluzioni:def isSorted(x, key = lambda x: x): return all([key(x[i]) <= key(x[i + 1]) for i in xrange(len(x) - 1)])
eacousineau,

2
@aaronasterling: operator.ledovrebbe essere più veloce della lambda
Marian,

Questo non funziona per me (python --version = 2.6.4) l = [1, 2, 3, 4, 1, 6, 7, 8, 7] all(l[i] <= l[i+1] for i in xrange(len(l)-1)) stampa come risultato:True
prodev_paris

1
Python 3.x non ha xrangepiù, basta usare range. Ottengo NameError: name 'xrange' is not definedquando eseguo quel codice. L'ho cambiato per usare solo rangeinvece di xrangee funziona bene. Vedi: stackoverflow.com/questions/15014310/…
Cale Sweeney,

78

Vorrei solo usare

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

a meno che non sia un elenco molto grande, nel qual caso potresti voler creare una funzione personalizzata.

se hai intenzione di ordinarlo se non è ordinato, allora dimentica il segno di spunta e ordinalo.

lst.sort()

e non ci pensare troppo.

se vuoi una funzione personalizzata, puoi fare qualcosa del genere

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

Questo sarà O (n) se la lista è già ordinata (e O (n) in un forciclo in quel!) Quindi, a meno che non ti aspetti che non sia ordinata (e abbastanza casuale) la maggior parte delle volte, vorrei, di nuovo, basta ordinare l'elenco.


10
Se è quello che stai per fare, potresti anche dire: lst.sort () senza il controllo condizionale ;-)
SapphireSun

5
questo è giusto, c'è un modo chiaramente più veloce in O (n) usando un semplice ciclo per.
Anijhaw,

1
@SapphireSun. Questo è quello che ho detto;)
aaronasterling

@anijhaw, guarda l'aggiornamento che ho fatto mentre stavi lasciando il commento. il controllo è O (n) e l'ordinamento è O (nlgn). è meglio sostenere un costo O (n) semplicemente voltarsi e aggiungere O (nlgn) o semplicemente prendere il costo di ordinare un elenco ordinato che è (credo) O (n) per timsort.
aaronasterling,

@ Aaron: controlla la modifica alla domanda originale,
anijhaw

44

Questo modulo iteratore è del 10-15% più veloce rispetto all'utilizzo dell'indicizzazione di numeri interi:

# 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:]))

Non vedo differenza significativa sulla mia macchina gist.github.com/735259 Il modificata # 7 variante dalla risposta di @ Nathan Farrington è 2 volte più veloce stackoverflow.com/questions/3755136/...
JFS

Funzionerà solo per contenitori "indicizzabili" come un elenco, nel qual caso vengono creati due nuovi elenchi con lo slicing. Per gli iteratori generali, preferisco la soluzione di Alexandre .
Bas Swinckels,

1
Risposta elegante, puoi usare izipe isliceda itertools per renderlo più veloce.
Elmex80

@jfs: la "variante # 7 di Nathan Farrington" è sbagliata. semplicemente non fa quello che dovrebbe fare ed è per questo che è più veloce. vedi il mio commento lì.
olivecoder

1
Puoi semplificare la tua soluzione per comprimere (l, l [1:]), perché zip si interrompe quando l'argomento più breve è esaurito
Gelineau,

20

Un bel modo per implementare questo è usare la imapfunzione da 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))

Questa implementazione è veloce e funziona su tutti gli iterabili.


4
Bello, ma buggy! Prova is_sorted(iter([1,2,3,2,5,8]))o un generatore equivalente. È necessario utilizzare un iteratore indipendente per tail, provare itertools.tee.
Kos,

Ricorda che iter(x) is xper gli iteratori
Kos,

1
Ah, è una sorpresa spiacevole! L'ho risolto ora. Grazie!
Alexandre Vassalotti il

3
Nota che in Python 3 itertools.imapè stato rinominato [__builtins__.]map.
Nick T,

10

Ho eseguito un benchmark ed è sorted(lst, reverse=True) == lststato il più veloce per le liste lunghe ed è all(l[i] >= l[i+1] for i in xrange(len(l)-1))stato il più veloce per le liste brevi . Questi benchmark sono stati eseguiti su un MacBook Pro 2010 13 "(Core2 Duo 2,66 GHz, RAM DDR3 da 10 GB a 1067 MHz, Mac OS X 10.6.5).

AGGIORNAMENTO: ho rivisto lo script in modo che tu possa eseguirlo direttamente sul tuo sistema. La versione precedente aveva dei bug. Inoltre, ho aggiunto input sia ordinati che non ordinati.

  • Ideale per brevi elenchi ordinati: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Ideale per elenchi lunghi ordinati: sorted(l, reverse=True) == l
  • Ideale per elenchi brevi non ordinati: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Ideale per elenchi lunghi non ordinati: all(l[i] >= l[i+1] for i in xrange(len(l)-1))

Quindi nella maggior parte dei casi c'è un chiaro vincitore.

AGGIORNAMENTO: le risposte di aaronsterling (# 6 e # 7) sono in realtà le più veloci in tutti i casi. # 7 è il più veloce perché non ha un livello di riferimento indiretto per cercare la chiave.

#!/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

1
Il tuo punto di riferimento sta testando il caso peggiore per le forme di espressione del generatore e il caso migliore per la mia soluzione. Potresti voler testare anche su un elenco non ordinato. Quindi vedrai che, a meno che non ti aspetti che l'elenco sia ordinato per la maggior parte del tempo, l'espressione del generatore è migliore.
aaronasterling,

@aaronsterling, ho aggiornato lo script per avere input sia ordinati che non ordinati.
Nathan Farrington,

Tutte le funzioni con enumeratesono errate. enumerate(l[1:])dovrebbe essere sostituito daenumerate(l[1:], 1)
jfs il

1
invece di sostituirlo enumerate(l[1:])con enumerate(l[1:], 1)potresti sostituirlo l[i-1]con l[i].
jfs,

Se aggiungi input casuali, ad es., L5=range(100); random.shuffle(L5)# 5 è relativamente lento. In questo caso il # 7 modificato è più veloce nel complesso codepad.org/xmWPxHQY
jfs

9

Lo farei (rubando da molte risposte qui [Aaron Sterling, Wai Yip Tung, una specie di Paul McGuire] e principalmente 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))

Una cosa carina: non devi realizzare il secondo iterabile per la serie (a differenza di una fetta di elenco).


2
nome fuorviante key. keydovrebbe essere usato per trasformare oggetti in valori comparabili.
InQβ,

4

Uso questo one-liner basato su numpy.diff ():

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

Non l'ho davvero cronometrato con nessun altro metodo, ma presumo sia più veloce di qualsiasi metodo Python puro, specialmente per n grande, poiché il ciclo in numpy.diff (probabilmente) viene eseguito direttamente in C (sottrazioni n-1 seguite da n -1 confronti).

Tuttavia, è necessario fare attenzione se x è un int senza segno, che potrebbe causare un underflow intero silenzioso in numpy.diff (), risultando in un falso positivo. Ecco una versione modificata:

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

4

È simile alla risposta principale, ma mi piace di più perché evita l'indicizzazione esplicita. Supponendo che il tuo elenco abbia il nome lst, puoi generare
(item, next_item)tuple dal tuo elenco con zip:

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

In Python 3, ziprestituisce già un generatore, in Python 2 è possibile utilizzareitertools.izip per una migliore efficienza della memoria.

Piccola demo:

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

L'ultimo fallisce quando la tupla (3, 2) viene valutata .

Bonus: controllo di generatori finiti (!) Che non possono essere indicizzati:

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

Assicurati di usare itertools.izipqui se stai usando Python 2, altrimenti annulleresti lo scopo di non dover creare liste dai generatori.


2
È anche possibile utilizzare isliceper ottimizzare per affettare. Anche nel modulo itertools. all(x <= y for x, y in izip(lst, islice(lst, 1))).
Elmex80

3

SapphireSun ha ragione. Puoi semplicemente usare lst.sort(). L'implementazione dell'ordinamento di Python (TimSort) controlla se l'elenco è già ordinato. In tal caso, sort () verrà completato in tempo lineare. Sembra un modo Pythonic per assicurarsi che un elenco sia ordinato;)


20
Tempo lineare solo se l'elenco è, in effetti, ordinato. In caso contrario, non vi è alcun cortocircuito per saltare l'effettiva attività di smistamento, quindi potrebbe essere una penalità enorme da pagare se l'elenco è lungo.
PaulMcG,

Questa è un'ottima risposta se il tuo compito è "assicurati che l'elenco sia ordinato e muori in caso contrario". Che è piuttosto comune come controllo di integrità dei dati che dovrebbero essere ordinati per qualche altro motivo. Quindi solo il caso di errore è lento.
Ed Avis,

3

Anche se non credo che ci sia una garanzia per cui il sortedbuilt-in chiama la sua funzione cmp i+1, i, sembra farlo per CPython.

Quindi potresti fare qualcosa del tipo:

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

O in questo modo (senza se le dichiarazioni -> EAFP sono andate male? ;-)):

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

Non molto Pythonic, ma abbiamo bisogno di almeno una reduce()risposta, giusto?

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 variabile accumulatore memorizza semplicemente l'ultimo valore verificato e, se un valore è inferiore al valore precedente, l'accumulatore viene impostato su infinito (e quindi sarà comunque infinito alla fine, poiché il "valore precedente" sarà sempre maggiore di quello attuale).


2

Come notato da @aaronsterling, la seguente soluzione è la più breve e sembra più veloce quando l'array è ordinato e non troppo piccolo: def is_sorted (lst): return (ordinato (lst) == lst)

Se la maggior parte delle volte l'array non viene ordinato, sarebbe preferibile utilizzare una soluzione che non analizzi l'intero array e restituisca False non appena viene scoperto un prefisso non ordinato. Di seguito è la soluzione più veloce che ho trovato, non è particolarmente elegante:

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

Utilizzando il benchmark di Nathan Farrington, si ottiene un runtime migliore rispetto all'utilizzo dell'ordinamento (lst) in tutti i casi, tranne quando si esegue su un grande elenco ordinato.

Ecco i risultati del benchmark sul mio computer.

ordinata (prima) == prima soluzione

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

Seconda soluzione:

  • L1: 0,81095790863
  • L2: 0.802397012711
  • L3: 1.06135106087
  • L4: 8.82761001587

2

Se vuoi il modo più veloce per gli array intorpiditi, usa numba , che se usi conda dovrebbe essere già installato

Il codice sarà veloce perché sarà compilato da 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

e poi:

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

2

Solo per aggiungere un altro modo (anche se richiede un modulo aggiuntivo) 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

Per verificare l'ordine DESC:

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

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

C'è anche un strictparametro se è necessario controllare le sequenze monotoniche rigorosamente (se gli elementi successivi non devono essere uguali).

Non è un problema nel tuo caso, ma se le tue sequenze contengono nanvalori, alcuni metodi falliranno, ad esempio con ordinati:

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

Si noti che offre iteration_utilities.all_monotoneprestazioni più veloci rispetto alle altre soluzioni menzionate qui, in particolare per input non ordinati (vedere benchmark ).


2

Pigro

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
Assolutamente fantastico! Ecco il mio miglioramento per renderlo one-liner - invece di iter () e next () utilizzare lo slicing con lo stesso risultato:all(a <= b for a, b in zip(l, l[1:]))
Matt

1
@LiborJelinek bene, ma la mia versione funziona quando lè un generatore e non supporta lo slicing.
Sergey11g

2

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

0

Il modo più semplice:

def isSorted(arr):
  i = 1
  while i < len(arr):
    if(result[i] < result[i - 1]):
      return False
    i += 1
  return True

0
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]

Il valore di riduzione derivato è una tupla in 3 parti di ( SortSoFarFlag , firstTimeFlag , lastElementValue ). Inizialmente inizia con ( True, True, None), che viene usato anche come risultato un elenco vuoto (considerata stati scelti perché non ci sono elementi out-of-ordine). Man mano che elabora ciascun elemento, calcola i nuovi valori per la tupla (utilizzando i valori di tupla precedenti con l'elemento successivo Valore):

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

Il risultato finale della riduzione è una tupla di:

[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

Il primo valore è quello a cui siamo interessati, quindi usiamo [0]per afferrarlo dal risultato di riduzione.


Si noti che questa soluzione funziona per tutti i tipi di elementi contenenti iterabili che possono essere confrontati tra loro. Ciò include elenchi di valori booleani (verifica che i valori False si verifichino prima dei valori True), elenchi di numeri, elenchi di stringhe (ordine alfabetico), elenchi di insiemi (i sottoinsiemi si verificano prima dei superset) ecc.
Mr Weasel,

0

Poiché non vedo questa opzione sopra, la aggiungerò a tutte le risposte. Lasciate denotare l'elenco per l, quindi:

import numpy as np

# Trasform the list to a numpy array
x = np.array(l)

# check if ascendent sorted:
all(x[:-1] <= x[1:])

# check if descendent sorted:
all(x[:-1] >= x[1:])

0

Una soluzione che utilizza espressioni di assegnazione (aggiunta in Python 3.8):

def is_sorted(seq):
    seq_iter = iter(seq)
    cur = next(seq_iter, None)
    return all((prev := cur) <= (cur := nxt) for nxt in seq_iter)

z = list(range(10))
print(z)
print(is_sorted(z))

import random
random.shuffle(z)
print(z)
print(is_sorted(z))

z = []
print(z)
print(is_sorted(z))

dà:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
True
[1, 7, 5, 9, 4, 0, 8, 3, 2, 6]
False
[]
True

-1

Questo è in effetti il ​​modo più breve per farlo usando la ricorsione:

se è Ordinato stamperà True else stamperà False

 def is_Sorted(lst):
    if len(lst) == 1:
       return True
    return lst[0] <= lst[1] and is_Sorted(lst[1:])

 any_list = [1,2,3,4]
 print is_Sorted(any_list)

Si noti che questo aumenterà RuntimeError: maximum recursion depth exceededper liste lunghe. Prova any_list = range(1000).
data del

-1

Che ne dici di questo? Semplice e diretto

def is_list_sorted(al):

    llength =len(al)


    for i in range (llength):
        if (al[i-1] > al[i]):
            print(al[i])
            print(al[i+1])
            print('Not sorted')
            return -1

    else :
        print('sorted')
        return  true

-3

Funziona sicuramente in Python 3 e versioni successive per numeri interi o stringhe:

def tail(t):
    return t[:]

letters = ['a', 'b', 'c', 'd', 'e']
rest = tail(letters)
rest.sort()
if letters == rest:
    print ('Given list is SORTED.')
else:
    print ('List NOT Sorted.')

================================================== ===================

Un altro modo per scoprire se l'elenco dato è ordinato o meno

trees1 = list ([1, 4, 5, 3, 2])
trees2 = list (trees1)
trees2.sort()
if trees1 == trees2:
    print ('trees1 is SORTED')
else:
    print ('Not sorted')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.