Maneira pitônica de verificar se uma lista está classificada ou não

145

Existe uma maneira pitônica de verificar se uma lista já está classificada ASCouDESC

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

algo assim isttimestamps.isSorted()retorna Trueou False.

Quero inserir uma lista de carimbos de data e hora para algumas mensagens e verificar se as transações apareceram na ordem correta.

anijhaw
fonte

Respostas:

212

Na verdade, não estamos dando a resposta que anijhaw está procurando. Aqui está o liner:

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

Para Python 3:

all(l[i] <= l[i+1] for i in range(len(l)-1))
Wai Yip Tung
fonte
2
isso é bom. Você pode agrupá-lo em uma função para poder passar uma keyfunção a ser usada. key=lambda x, y: x < yfaz um bom padrão.
aaronasterling 20/09/10
3
Uma combinação de algumas das soluções: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.ledeve ser mais rápido do que o lambda
Marian
Isso não funciona para mim (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)) imprima como resultado:True
prodev_paris
1
Parece que o Python 3.x não tem xrangemais, basta usar range. Recebo NameError: name 'xrange' is not definedquando executo esse código. Mudei para usar apenas em rangevez de xrangee isso funciona bem. Veja: stackoverflow.com/questions/15014310/…
Cale Sweeney
78

Eu usaria apenas

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

a menos que seja uma lista muito grande; nesse caso, convém criar uma função personalizada.

se você quiser classificá-lo se não estiver classificado, esqueça a verificação e classifique-a.

lst.sort()

e não pense muito sobre isso.

se você deseja uma função personalizada, pode fazer algo como

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

Isso será O (n) se a lista já estiver classificada (e O (n) em um forloop!), Portanto, a menos que você espere que ela não seja classificada (e bastante aleatória) na maioria das vezes, novamente, basta classificar a lista.

aaronasterling
fonte
10
Se é isso que você vai fazer, assim como você pode apenas dizer: lst.sort () sem a verificação condicional ;-)
SapphireSun
5
isso é nlogn, existe uma maneira claramente mais rápida em O (n) usando um loop for simples.
anijhaw
1
@SapphireSun. Isso é o que eu disse;)
aaronasterling
@anijhaw, veja a atualização que fiz enquanto você deixava o comentário. a verificação é O (n) e a classificação é O (nlgn). é melhor incorrer em um custo de O (n) para virar e adicionar O (nlgn) ou apenas assumir o custo de classificar uma lista classificada que seja (acredito) O (n) para timsort.
aaronasterling 20/09/10
@ Aaron: Verifique o Editar à pergunta original,
anijhaw
44

Este formulário do iterador é 10-15% mais rápido que o uso da indexação inteira:

# 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:]))
PaulMcG
fonte
Não vejo diferença significativa na minha máquina gist.github.com/735259 A variante # 7 modificada da resposta de @Nathan Farrington é 2x mais rápida stackoverflow.com/questions/3755136/…
jfs
Isso funcionará apenas para contêineres 'indexáveis' como uma lista; nesse caso, duas novas listas serão criadas com o fatiamento. Para iteradores gerais, prefiro a solução de Alexandre .
Bas Swinckels
1
Resposta elegante, você pode usar izipe islicede ferramentas para torná-lo mais rápido.
precisa saber é o seguinte
@jfs: a "variante nº 7 de Nathan Farrington's" está errada. simplesmente não faz o que deveria fazer e é por isso que é mais rápido. veja meu comentário lá.
precisa
1
Você pode simplificar sua solução para zip (l, l [1:]), porque o zip pára quando o argumento mais curto é esgotado
Gelineau
20

Uma maneira bonita de implementar isso é usar a imapfunção 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))

Essa implementação é rápida e funciona em quaisquer iteráveis.

Alexandre Vassalotti
fonte
4
Bom, mas com buggy! Experimente is_sorted(iter([1,2,3,2,5,8]))ou um gerador equivalente. Você precisa usar um iterador independente para tail, tente itertools.tee.
Kos
Lembre-se que iter(x) is xpara iterators
Kos
1
Ah, isso é uma surpresa desagradável! Eu consertei agora. Obrigado!
Alexandre Vassalotti
3
Observe que no Python 3 itertools.imapfoi renomeado para [__builtins__.]map.
Nick T
10

Fiz um benchmark e sorted(lst, reverse=True) == lstfoi o mais rápido para listas longas e all(l[i] >= l[i+1] for i in xrange(len(l)-1))o mais rápido para listas curtas . Esses benchmarks foram executados em um MacBook Pro 2010 13 "(Core2 Duo 2.66GHz, 4GB 1067MHz DDR3 RAM, Mac OS X 10.6.5).

ATUALIZAÇÃO: Revisei o script para que você possa executá-lo diretamente em seu próprio sistema. A versão anterior tinha bugs. Além disso, adicionei entradas classificadas e não classificadas.

  • Melhor para pequenas listas ordenadas: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Melhor para listas longas ordenadas: sorted(l, reverse=True) == l
  • Melhor para pequenas listas não ordenadas: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Melhor para longas listas não ordenadas: all(l[i] >= l[i+1] for i in xrange(len(l)-1))

Portanto, na maioria dos casos, há um vencedor claro.

ATUALIZAÇÃO: as respostas de aaronsterling (# 6 e # 7) são realmente as mais rápidas em todos os casos. O número 7 é o mais rápido porque não possui uma camada de indireção para procurar a chave.

#!/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
Nathan Farrington
fonte
1
Sua referência é testar o pior caso para as formas de expressão do gerador e o melhor caso para a minha solução. Você também pode testar uma lista não classificada. Então você verá que, a menos que você espere que a lista seja classificada na maioria das vezes, a expressão do gerador é melhor.
aaronasterling
@aaronsterling, atualizei o script para ter entradas classificadas e não classificadas.
Nathan Farrington
Todas as funções com enumerateestão incorretas. enumerate(l[1:])deve ser substituído porenumerate(l[1:], 1)
jfs
1
em vez de substituir enumerate(l[1:])por enumerate(l[1:], 1)você pode substituir l[i-1]por l[i].
JFS
Se você adicionar entradas aleatórias, por exemplo, o L5=range(100); random.shuffle(L5)número 5 é comparativamente lento. Nesse caso, o nº 7 modificado é mais rápido no geral codepad.org/xmWPxHQY
jfs
9

Eu faria isso (roubando muitas respostas aqui [Aaron Sterling, Wai Yip Tung, meio que de 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))

Uma coisa boa: você não precisa entender o segundo iterável da série (ao contrário de uma fatia da lista).

hughdbrown
fonte
2
nome enganador key. keydeve ser usado para transformar itens em valores comparáveis.
InQβ 17/09/18
4

Eu uso esse one-liner baseado em numpy.diff ():

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

Eu realmente não cronometrei isso contra nenhum outro método, mas presumo que seja mais rápido que qualquer método Python puro, especialmente para n grandes, pois o loop em numpy.diff (provavelmente) é executado diretamente em C (subtrações n-1 seguidas por n -1 comparações).

No entanto, você precisa ter cuidado se x for um int não assinado, o que pode causar um estouro silencioso de números inteiros em numpy.diff (), resultando em um falso positivo. Aqui está uma versão modificada:

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()
Martin Spacek
fonte
4

Isso é semelhante à resposta principal, mas eu gosto mais porque evita a indexação explícita. Supondo que sua lista tenha o nome lst, você pode gerar
(item, next_item)tuplas da sua lista com zip:

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

No Python 3, zipjá retorna um gerador, no Python 2 você pode usar itertools.izippara melhorar a eficiência da memória.

Pequena demonstração:

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

O último falha quando a tupla (3, 2)é avaliada.

Bônus: verificando geradores finitos (!) Que não podem ser indexados:

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

Certifique-se de usar itertools.izipaqui se você estiver usando o Python 2, caso contrário você perderia o propósito de não precisar criar listas dos geradores.

timgeb
fonte
2
Você pode até usar islicepara otimizar o fatiamento. Também no módulo itertools. all(x <= y for x, y in izip(lst, islice(lst, 1))).
precisa saber é o seguinte
3

SapphireSun está certo. Você pode apenas usar lst.sort(). A implementação de classificação do Python (TimSort) verifica se a lista já está classificada. Nesse caso, sort () será concluído em tempo linear. Soa como uma maneira Pythonic de garantir que uma lista seja classificada;)

Wai Yip Tung
fonte
20
Somente tempo linear se a lista for, de fato, classificada. Caso contrário, não há curto-circuito para pular a tarefa de classificação real; portanto, pode ser uma penalidade enorme a pagar se a lista for longa.
PaulMcG 20/09/10
Essa é uma ótima resposta se sua tarefa for "verifique se a lista está classificada e, se não estiver," morrerá ". O que é bastante comum como uma verificação de integridade de dados que deve ser classificada por algum outro motivo. Somente o caso de erro é lento.
Ed Avis
3

Embora eu ache que não há garantia de que o sortedbuilt-in chame sua função cmp comi+1, i , ele parece fazê-lo no CPython.

Então você pode fazer algo como:

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 assim (sem as declarações if -> EAFP deram errado? ;-)):

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
Anthon
fonte
3

Não é muito pitonico, mas precisamos de pelo menos uma reduce()resposta, certo?

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

A variável acumulador simplesmente armazena esse último valor verificado e, se algum valor for menor que o valor anterior, o acumulador é definido como infinito (e, portanto, ainda será infinito no final, pois o 'valor anterior' sempre será maior que o atual).

orlade
fonte
2

Conforme observado por @aaronsterling, a solução a seguir é a mais curta e parece mais rápida quando a matriz é classificada e não muito pequena: def is_sorted (lst): return (classificado (lst) == lst)

Se na maioria das vezes a matriz não for classificada, seria desejável usar uma solução que não varra toda a matriz e retorne False assim que um prefixo não classificado for descoberto. A seguir, é a solução mais rápida que pude encontrar, não é particularmente 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

Usando o benchmark de Nathan Farrington, isso obtém um tempo de execução melhor do que o uso do ordenado (lst) em todos os casos, exceto quando executado na grande lista classificada.

Aqui estão os resultados de benchmark no meu computador.

ordenada (lst) == lst solução

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

Segunda solução:

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

Se você deseja o caminho mais rápido para matrizes numpy, use numba , que se você usar conda já deve estar instalado

O código será rápido porque será compilado pela 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 depois:

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

Apenas para adicionar outra maneira (mesmo que exija um módulo adicional) 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

Para verificar a ordem DESC:

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

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

Também existe um strictparâmetro se você precisar verificar estritamente (se elementos sucessivos não forem iguais) seqüências monotônicas.

Não é um problema no seu caso, mas se suas seqüências contiverem nanvalores, alguns métodos falharão, por exemplo, com a classificação:

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

Observe que o iteration_utilities.all_monotonedesempenho é mais rápido em comparação com as outras soluções mencionadas aqui, especialmente para entradas não classificadas (consulte a referência ).

MSeifert
fonte
2

Preguiçoso

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))
Sergey11g
fonte
1
Absolutamente incrível! Aqui está é a minha melhora para torná-lo de uma linha - em vez de iter () e ao lado) o uso de corte (com o mesmo resultado:all(a <= b for a, b in zip(l, l[1:]))
Matt
1
@LiborJelinek é bom, mas minha versão funciona quando lé um gerador e não suporta fatiamento.
usar o seguinte código
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
Chweng Mega
fonte
0

Maneira mais simples:

def isSorted(arr):
  i = 1
  while i < len(arr):
    if(result[i] < result[i - 1]):
      return False
    i += 1
  return True
Aluis92
fonte
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]

O valor de redução derivado é uma tupla de três partes de ( sortedSoFarFlag , firstTimeFlag , lastElementValue ). É inicialmente começa com ( True, True, None), que é também utilizado como o resultado de uma lista vazia (considerado como classificados porque não existem elementos de fora-de-fim). À medida que processa cada elemento, ele calcula novos valores para a tupla (usando valores anteriores da tupla com o próximo 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

O resultado final da redução é uma tupla 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

O primeiro valor é aquele em que estamos interessados, então usamos isso [0]para obter o resultado reduzido.

Sr. Weasel
fonte
Observe que esta solução funciona para qualquer tipo de elemento contendo iterável que possa ser comparado entre si. Isso inclui listas de valores booleanos (verifica se os valores falsos ocorrem antes dos valores verdadeiros), listas de números, listas de seqüências de caracteres (ordem alfabética), listas de conjuntos (subconjuntos ocorrem antes de superconjuntos) etc.
Sr. Weasel
0

Como não vejo essa opção acima, vou adicioná-la a todas as respostas. Vamos denotar a lista até l, então:

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:])
FBruzzesi
fonte
0

Uma solução usando expressões de atribuição (adicionadas no 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
PaulMcG
fonte
-1

Essa é, de fato, a maneira mais curta de fazer isso usando a recursão:

se estiver classificado, imprimirá True; caso contrário, imprimirá 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)
Baraa
fonte
Observe que isso aumentará RuntimeError: maximum recursion depth exceededpara listas longas. Tente any_list = range(1000).
timgeb
-1

Que tal este ? Simples e direto.

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
Zuckerberg
fonte
-3

Definitivamente funciona em Python 3 e acima para números inteiros ou seqüências de caracteres:

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.')

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

Outra maneira de descobrir se a lista fornecida está classificada ou não

trees1 = list ([1, 4, 5, 3, 2])
trees2 = list (trees1)
trees2.sort()
if trees1 == trees2:
    print ('trees1 is SORTED')
else:
    print ('Not sorted')
cooldeal
fonte