Пітонічний спосіб перевірити, сортується список чи ні


145

Чи є пітонічний спосіб перевірити, чи список вже відсортований у ASCабоDESC

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

щось подібне isttimestamps.isSorted()повертається Trueабо False.

Я хочу ввести список часових позначок для деяких повідомлень і перевірити, чи транзакції відображалися в правильному порядку.

Відповіді:


212

Насправді ми не даємо відповіді, яку шукає anijhaw. Ось один вкладиш:

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

Для Python 3:

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

2
це мило. Ви можете обернути його у функцію, щоб ви могли передавати keyфункцію у користування. key=lambda x, y: x < yробить хороший дефолт.
aaronasterling

3
Комбо з двох рішень: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.leмає бути швидше лямбда
Мар'ян

Це не працює для мене (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)) надрукувати як результат:True
prodev_paris

1
Схоже, Python 3.x більше не має xrange, просто використовуйте range. Я отримую, NameError: name 'xrange' is not definedколи запускаю цей код. Я переключив його на просто використання rangeзамість, xrangeі це працює чудово. Дивіться: stackoverflow.com/questions/15014310/…
Cale Sweeney

78

Я б просто скористався

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

якщо це не дуже великий список, в цьому випадку ви можете створити власну функцію.

якщо ви просто збираєтеся сортувати його, якщо він не відсортований, то забудьте чек і відсортуйте його.

lst.sort()

і не думайте про це занадто.

якщо ви хочете користувацьку функцію, ви можете зробити щось подібне

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

Це буде O (n), якщо список уже відсортований, хоча (і O (n) в forциклі в цьому випадку!) Так, якщо ви не очікуєте, що він не буде відсортований (і досить випадковий) більшу частину часу, знову, просто сортуйте список.


10
Якщо це ви збираєтеся робити, ви можете також просто сказати: lst.sort () без умовної перевірки ;-)
SapphireSun

5
у тому, що в O (n) є явно швидший спосіб використання простого циклу.
anijhaw

1
@SapphireSun. Ось що я сказав;)
aaronasterling

@anijhaw, Дивіться оновлення, яке я зробив, поки ви залишали коментар. перевірка - O (n), а сортування - O (nlgn). чи краще взяти на себе витрати O (n), щоб просто розвернутись і додати O (nlgn) або просто взяти витрати на сортування відсортованого списку, який (я вважаю) O (n) для тимчасових скорочень.
aaronasterling

@ Aaron: Перевірте Правка до початкового питання,
anijhaw

44

Ця форма ітератора на 10-15% швидша, ніж використання цілочислової індексації:

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

Я не бачу суттєвої різниці на моїй машині gist.github.com/735259 Модифікований # 7 варіант відповіді Фаррингтона @ Натан є 2x швидше stackoverflow.com/questions/3755136 / ...
JFS

Це буде працювати лише для контейнерів, що індексуються, як-от список, і в цьому випадку два нові списки створюються з нарізкою. Для загальних ітераторів я вважаю за краще рішення Олександра .
Bas Swinckels

1
Елегантна відповідь, ви можете скористатися izipі isliceз itertools, щоб зробити це швидше.
Elmex80s

@jfs: варіант "№7 від Натана Фаррінгтона" невірний. він просто не робить того, що повинен робити, і тому це швидше. дивіться мій коментар там.
olivecoder

1
Ви можете спростити своє рішення для zip (l, l [1:]), оскільки zip зупиняється, коли найменший аргумент вичерпано
Gelineau

20

Прекрасним способом реалізації цього є використання imapфункції з 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))

Ця реалізація швидка і працює на будь-яких ітерабелях.


4
Приємно, але баггі! Спробуйте is_sorted(iter([1,2,3,2,5,8]))або еквівалентний генератор. Вам потрібно використовувати незалежний ітератор tail, спробуйте itertools.tee.
Кос

Пам’ятайте, що iter(x) is xдля ітераторів
Кос

1
Ах, це неприємний сюрприз! Я зараз це виправив. Дякую!
Олександр Васалотті

3
Зауважте, що в Python 3 itertools.imapбуло перейменовано на [__builtins__.]map.
Нік Т

10

Я провів показник і sorted(lst, reverse=True) == lstбув найшвидшим для довгих списків, і all(l[i] >= l[i+1] for i in xrange(len(l)-1))був найшвидшим для коротких списків . Ці орієнтири виконувались на MacBook Pro 2010 13 "(Core2 Duo 2.66GHz, 4GB 1067MHz DDR3 RAM, Mac OS X 10.6.5).

ОНОВЛЕННЯ: Я переглянув сценарій, щоб ви могли запустити його безпосередньо у власній системі. У попередній версії були помилки. Також я додав як відсортовані, так і несортовані входи.

  • Найкраще для коротких відсортованих списків: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Найкраще для довгих сортованих списків: sorted(l, reverse=True) == l
  • Найкраще для коротких несортованих списків: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Найкраще для довгих несортованих списків: all(l[i] >= l[i+1] for i in xrange(len(l)-1))

Тож у більшості випадків є явний переможець.

ОНОВЛЕННЯ: відповіді aaronsterling (№6 та №7) насправді найшвидші у всіх випадках. №7 є найшвидшим, оскільки у нього немає шару непрямості для пошуку ключа.

#!/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
Ви оцінюєте тест на найгірший випадок для форми вираження генератора і найкращий випадок для мого рішення. Можливо, ви також хочете протестувати проти несортованого списку. Тоді ви побачите, що, якщо ви не очікуєте, що список буде сортований більшу частину часу, вираження генератора краще.
aaronasterling

@aaronsterling, я оновив сценарій, щоб мати сортовані та несортовані входи.
Натан Фаррінгтон

Усі функції з enumerateневірними. enumerate(l[1:])слід замінити наenumerate(l[1:], 1)
jfs

1
замість того , щоб замінити enumerate(l[1:])на enumerate(l[1:], 1)вас могли б замінити l[i-1]на l[i].
jfs

Якщо ви додасте випадкове введення, наприклад, L5=range(100); random.shuffle(L5)номер 5 порівняно повільний. У цьому випадку модифікований №7 швидше загальний codepad.org/xmWPxHQY
jfs

9

Я б це зробив (крадучись з безлічі відповідей тут [Аарон Стерлінг, Вай Іп Тунг, сорти від Пола Макгуайра] і переважно Армін Ронахер ):

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

Одне приємне: вам не потрібно усвідомити другий ітерабельний для серії (на відміну від фрагмента списку).


2
оманливе ім’я key. keyслід використовувати для перетворення елементів у порівнянні значення.
InQβ

4

Я використовую цей одноклапник на основі numpy.diff ():

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

Я насправді не прив’язував його до будь-якого іншого методу, але я припускаю, що це швидше, ніж будь-який чистий метод Python, особливо для великих n, оскільки цикл у numpy.diff (певно) працює безпосередньо в C (n-1 віднімання, а потім n -1 порівняння).

Однак вам потрібно бути обережними, якщо x - це непідписаний int, що може спричинити беззвучне підливання цілого числа в numpy.diff (), що призведе до помилкового додатного. Ось змінена версія:

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

Це схоже на верхню відповідь, але мені це подобається краще, оскільки вона уникає явного індексування. Якщо припустити, що ваш список має ім'я lst, ви можете генерувати
(item, next_item)кортежі зі свого списку за допомогою zip:

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

У Python 3 zipвже повертає генератор, в Python 2 ви можете використовувати itertools.izipдля кращої ефективності пам'яті.

Мала демонстрація:

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

Останній виходить з ладу, коли (3, 2)оцінюється кортеж .

Бонус: перевірка кінцевих (!) Генераторів, які не можна індексувати:

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

Не забудьте використати itertools.izipтут, якщо ви використовуєте Python 2, інакше ви переможете мету не створювати списки з генераторів.


2
Ви навіть можете використовувати isliceдля оптимізації для нарізки. Також в модулі itertools. all(x <= y for x, y in izip(lst, islice(lst, 1))).
Elmex80s

3

SapphireSun цілком прав. Можна просто використовувати lst.sort(). Реалізація сортування Python (TimSort) перевіряє, чи список вже відсортований. Якщо так сортування () завершиться за лінійним часом. Звучить як пітонічний спосіб забезпечити упорядкування списку;)


20
Тільки лінійний час, якщо список насправді відсортований. Якщо ні, то не існує короткого замикання, щоб пропустити фактичне завдання сортування, тому плата може бути величезною, якщо список довгий.
PaulMcG

Це чудова відповідь, якщо ваше завдання - "переконайтесь, що список відсортований, і померти, якщо ні". Що досить поширене, як перевірка обґрунтованості даних, яку слід сортувати з якоїсь іншої причини. Тоді лише випадок помилок повільний.
Ед Авіс

3

Хоча я не думаю, що є гарантія того, що sortedвбудований називає свою функцію cmpi+1, i , але, схоже, це робиться для CPython.

Отже, ви можете зробити щось на кшталт:

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

Або таким чином (без того, якщо заяви -> EAFP пішли не так? ;-)):

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

Зовсім не пітонічний, але нам потрібна хоча б одна reduce()відповідь, правда?

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

Змінна акумулятора просто зберігає останнє перевірене значення, і якщо якесь значення менше попереднього значення, акумулятор встановлюється на нескінченність (і, таким чином, все ще буде нескінченним в кінці, оскільки "попереднє значення" завжди буде більше, ніж поточний).


2

Як зазначає @aaronsterling, наступне рішення є найкоротшим і здається найшвидшим, коли масив сортується та не надто малий: def is_sorted (lst): return (сортується (lst) == lst)

Якщо більшість часу масив не сортується, було б бажано використовувати рішення, яке не сканує весь масив і повертає False, як тільки виявиться несортований префікс. Далі йде найшвидше рішення, яке я міг знайти, воно не особливо елегантне:

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

Використовуючи орієнтир Натана Фаррінгтона, це досягає кращого часу виконання, ніж використання відсортованого (lst) у всіх випадках, за винятком випадків, коли ви працюєте у великому відсортованому списку.

Ось результати порівняння на моєму комп’ютері.

сортований (lst) == lst рішення

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

Друге рішення:

  • L1: 0.81095790863
  • L2: 0.802397012711
  • L3: 1.06135106087
  • L4: 8.82761001587

2

Якщо ви хочете найшвидший спосіб для нумерованих масивів, використовуйте numba , яка, якщо ви використовуєте conda, повинна бути вже встановлена

Код буде швидким, тому що він буде складений 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

і потім:

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

2

Просто додати інший спосіб (навіть якщо для цього потрібен додатковий модуль) 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

Щоб перевірити наявність замовлення DESC:

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

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

Також є strict параметр, якщо вам потрібно перевіряти на суворі (якщо послідовні елементи не повинні бути рівними) монотонні послідовності.

У вашому випадку це не проблема, але якщо ваші послідовності містять nanзначення, то деякі методи не вдасться, наприклад, сортувати:

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

Зауважте, що вона iteration_utilities.all_monotoneпрацює швидше порівняно з іншими рішеннями, згаданими тут, особливо для несортованих входів (див. Орієнтир ).


2

Ледачий

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
Абсолютно дивовижно! Ось моє вдосконалення, щоб зробити його однолінійним - замість iter () та next () використовуйте нарізку з тим же результатом:all(a <= b for a, b in zip(l, l[1:]))
Matt

1
@LiborJelinek добре, але моя версія працює, коли lє генератором і не підтримує нарізку.
Сергій11г

2

Пітон 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

Найпростіший спосіб:

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]

Отримане значення зменшення - це 3-х частинний кортеж ( сортованийSoFarFlag , firstTimeFlag , lastElementValue ). Спочатку вона починається з ( True, True, None), який також використовуються в якості результату для порожнього списку (вважаються відсортовано , тому що немає зіпсованих елементів). Обробляючи кожен елемент, він обчислює нові значення кортежу (використовуючи попередні значення кортежу із наступним елементомValue):

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

Кінцевим результатом скорочення є набір:

[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

Перше значення - це те, що нас цікавить, тому ми використовуємо, [0]щоб отримати це з результату зменшення.


Зауважте, що це рішення працює для будь-яких ітерабельних, що містять типів елементів, які можна порівняти один з одним. Це включає в себе списки логічних (чеки Хибних значень відбуваються до істинних значень), списки номерів, списки рядків ( в алфавітному порядку), списки наборів (підмножини відбуваються до надбезліччю) і т.д.
г - н Weasel

0

Оскільки я не бачу цієї опції вище, я додам її до всіх відповідей. Нехай позначають список через l, а потім:

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

Рішення з використанням виразів призначення (додано в 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))

Дає:

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

-1

Це насправді найкоротший спосіб зробити це за допомогою рекурсії:

якщо це сортується, буде надруковано Істинно, інакше буде виведено Неправдиво

 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)

Зауважте, що це буде піднято RuntimeError: maximum recursion depth exceededдля довгих списків. Спробуйте any_list = range(1000).
timgeb

-1

Як щодо цього? Простий і прямолінійний.

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

Однозначно працює в Python 3 і вище для цілих чисел або рядків:

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

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

Інший спосіб виявити, чи поданий список відсортований чи ні

trees1 = list ([1, 4, 5, 3, 2])
trees2 = list (trees1)
trees2.sort()
if trees1 == trees2:
    print ('trees1 is SORTED')
else:
    print ('Not sorted')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.