检查列表是不是已排序的 Pythonic 方法
Posted
技术标签:
【中文标题】检查列表是不是已排序的 Pythonic 方法【英文标题】:Pythonic way to check if a list is sorted or not检查列表是否已排序的 Pythonic 方法 【发布时间】:2011-04-14 21:13:21 【问题描述】:有没有一种pythonic方法来检查列表是否已经在ASC
或DESC
中排序
listtimestamps = [1, 2, 3, 5, 6, 7]
类似isttimestamps.isSorted()
的东西会返回True
或False
。
我想输入一些消息的时间戳列表,并检查交易是否以正确的顺序出现。
【问题讨论】:
【参考方案1】:我会用
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) (并且在 for
循环中 O(n) !)所以,除非你期望它大部分都没有排序(并且相当随机)到时候,我会再次对列表进行排序。
【讨论】:
如果这就是你要做的,你还不如说:lst.sort() 没有条件检查;-) @SapphireSun。我就是这么说的;) @anijhaw,请查看我在您发表评论时所做的更新。检查是 O(n),排序是 O(nlgn)。是否会产生 O(n) 成本来转身并添加 O(nlgn) 或只花费对排序列表进行排序的成本(我相信)对于 timsort 来说是 O(n)。 @Aaron:检查对原始问题的编辑,【参考方案2】:SapphireSun 说的很对。您可以使用lst.sort()
。 Python 的排序实现 (TimSort) 检查列表是否已经排序。如果是这样, sort() 将在线性时间内完成。听起来像是一种确保列表排序的 Pythonic 方式;)
【讨论】:
如果列表实际上是排序的,则只有线性时间。如果不是,则没有跳过实际排序任务的短路,因此如果列表很长,可能需要支付巨额罚款。 如果您的任务是“确保列表已排序,否则就死掉”,这是一个很好的答案。这对于出于某种其他原因应该排序的数据进行完整性检查是很常见的。那么只有错误情况是慢的。【参考方案3】:这是一个单行:
all(l[i] <= l[i+1] for i in range(len(l) - 1))
如果使用 Python 2,请使用 xrange
而不是 range
。
对于reverse=True
,使用>=
而不是<=
。
【讨论】:
这很好。您可能希望将其包装在一个函数中,以便您可以传递一个key
函数来使用。 key=lambda x, y: x < y
是一个很好的默认值。
几种解决方案的组合:def isSorted(x, key = lambda x: x): return all([key(x[i]) <= key(x[i + 1]) for i in xrange(len(x) - 1)])
@aaronasterling: operator.le
应该比 lambda 更快
这对我不起作用(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
看起来 Python 3.x 不再有xrange
,只需要使用range
。当我运行该代码时,我得到NameError: name 'xrange' is not defined
。我将其切换为仅使用 range
而不是 xrange
并且效果很好。见:***.com/questions/15014310/…【参考方案4】:
这种迭代器形式比使用整数索引快 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@Nathan Farrington 的答案中修改后的 #7 变体比 ***.com/questions/3755136/… 快 2 倍@ 这仅适用于像列表这样的“可索引”容器,在这种情况下,会使用切片创建两个新列表。对于通用迭代器,我更喜欢Alexandre's solution。 优雅的答案,您可以使用 itertools 中的izip
和 islice
使其更快。
@jfs:“Nathan Farrington 的#7 变体”是错误的。它只是没有做它应该做的事情,这就是它更快的原因。在那里查看我的评论。
您可以将解决方案简化为 zip(l, l[1:]),因为当最短参数用完时 zip 会停止【参考方案5】:
我运行了一个基准测试,。这些基准测试在 MacBook Pro 2010 13"(Core2 Duo 2.66GHz、4GB 1067MHz DDR3 RAM、Mac OS X 10.6.5)上运行。sorted(lst, reverse=True) == lst
是长列表最快的,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))
最适合长排序列表: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))
所以在大多数情况下,赢家是显而易见的。
更新:aaronasterling's answers(#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
【讨论】:
您的基准测试是测试生成器表达式表单的最坏情况和我的解决方案的最佳情况。您可能还想针对未排序的列表进行测试。然后你会看到,除非你期望列表大部分时间都是排序的,否则生成器表达式更好。 @aaronsterling,我已经更新了脚本以包含已排序和未排序的输入。 所有带有enumerate
的函数都不正确。 enumerate(l[1:])
应替换为 enumerate(l[1:], 1)
您可以将l[i-1]
替换为l[i]
,而不是将enumerate(l[1:])
替换为enumerate(l[1:], 1)
。
如果添加随机输入,例如L5=range(100); random.shuffle(L5)
,那么#5 相对较慢。在这种情况下,修改后的 #7 总体上更快codepad.org/xmWPxHQY【参考方案6】:
正如@aaronsterling 所指出的,以下解决方案是最短的,并且在数组排序且不太小时似乎最快: def is_sorted(lst): 返回(排序(lst)== lst)
如果大多数情况下数组未排序,则最好使用不扫描整个数组并在发现未排序前缀后立即返回 False 的解决方案。以下是我能找到的最快的解决方案,它不是特别优雅:
def is_sorted(lst):
it = iter(lst)
try:
prev = next(it)
except StopIteration:
return True
for x in it:
if prev > x: # For reverse, use <
return False
prev = x
return True
使用 Nathan Farrington 的基准,这在所有情况下都比使用 sorted(lst) 实现了更好的运行时间,除非在大型排序列表上运行。
这是我电脑上的基准测试结果。
sorted(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【讨论】:
尽管这个答案已经是最快的,但我通过 cythonizing 得到了 40% 的进一步加速。【参考方案7】:虽然我认为不能保证sorted
内置函数使用i+1, i
调用它的 cmp 函数,但它似乎确实适用于 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])
或者这样(没有 if 语句 -> 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
【讨论】:
【参考方案8】:一点也不像 Pythonic,但我们至少需要一个 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')
累加器变量只是存储最后检查的值,如果任何值小于前一个值,累加器将设置为无穷大(因此最后仍然是无穷大,因为“前一个值”总是比当前更大)。
【讨论】:
【参考方案9】:我会这样做(从这里的很多答案中窃取 [Aaron Sterling, Wai Yip Tung, sorta from Paul McGuire] 并且主要是 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))
一件好事:您不必实现该系列的第二个可迭代对象(与列表切片不同)。
【讨论】:
误导性名称key
。 key
应该用于将项目转换为可比较的值。【参考方案10】:
我使用这个基于 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 是无符号整数,则需要小心,这可能会导致 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()
【讨论】:
这对于任何大序列来说实际上都非常慢,大概是因为它计算了整个序列的差异。它不会懒惰地工作。在我的测试中,使用惰性求值的各种纯 Python 方法的速度提高了 10 倍以上。【参考方案11】:实现这一点的一个绝妙方法是使用来自itertools
的imap
函数:
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))
此实现速度很快,适用于任何可迭代对象。
【讨论】:
不错,但有问题!试试is_sorted(iter([1,2,3,2,5,8]))
或等效的生成器。 tail
需要使用独立的迭代器,试试itertools.tee
。
记住 iter(x) is x
用于迭代器
啊,真是令人不快的惊喜!我现在已经修好了。谢谢!
请注意,在 Python 3 中,itertools.imap
已重命名为 [__builtins__.]map
。
另外,在 Python 3.10 中,我们有 itertools.pairwise
函数,可以用来代替 itertools.tee
。【参考方案12】:
绝对适用于 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')
【讨论】:
【参考方案13】:懒惰
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))
【讨论】:
太棒了!这是我的改进,使其成为单线 - 而不是 iter() 和 next() 使用切片,结果相同:all(a <= b for a, b in zip(l, l[1:]))
@LiborJelinek 不错,但是当l
是生成器并且不支持切片时,我的版本可以工作。【参考方案14】:
这使用递归:
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
的长序列。
【讨论】:
请注意,对于长列表,这将引发RuntimeError: maximum recursion depth exceeded
。试试any_list = range(1000)
。【参考方案15】:
这类似于最佳答案,但我更喜欢它,因为它避免了显式索引。假设您的列表名称为 lst
,您可以使用 zip
从列表中生成(item, next_item)
元组:
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
如果您使用的是 Python 2,请务必在此处使用 itertools.izip
,否则您将无法实现不必从生成器创建列表的目的。
【讨论】:
您甚至可以使用islice
来优化切片。也在 itertools 模块中。 all(x <= y for x, y in izip(lst, islice(lst, 1)))
.【参考方案16】:
如果您想要最快的方式来处理 numpy 数组,请使用 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
【讨论】:
【参考方案17】:只是添加另一种方式(即使它需要额外的模块):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
值,那么某些方法将失败,例如 sorted:
def is_sorted_using_sorted(iterable):
return sorted(iterable) == iterable
>>> is_sorted_using_sorted([3, float('nan'), 1]) # definitely False, right?
True
>>> all_monotone([3, float('nan'), 1])
False
请注意,与此处提到的其他解决方案相比,iteration_utilities.all_monotone
的执行速度更快,尤其是对于未排序的输入(请参阅benchmark)。
【讨论】:
这个速度很快,但为什么比this answer慢很多? @Asclepius 您指的是哪些测量值?我还没有在这里看到包含这两个答案的基准,并且这个周末没有时间自己做一个。【参考方案18】:这个怎么样?简单明了。
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
【讨论】:
【参考方案19】:最简单的方法:
def isSorted(arr):
i = 1
while i < len(arr):
if(result[i] < result[i - 1]):
return False
i += 1
return True
【讨论】:
【参考方案20】: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
【讨论】:
【参考方案21】: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]
派生的缩减值是(sortedSoFarFlag、firstTimeFlag、lastElementValue)的三部分元组。它最初以 (True
, True
, None
) 开头,它也用作空列表的结果(被视为已排序,因为没有乱序元素)。在处理每个元素时,它会为元组计算新值(使用先前的元组值和下一个 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
归约的最终结果是一个元组:
[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]
从reduce 结果中获取它。
【讨论】:
请注意,此解决方案适用于任何可相互比较的可迭代包含元素类型。这包括布尔列表(检查 False 值出现在 True 值之前)、数字列表、字符串列表(字母顺序)、集合列表(子集出现在超集之前)等。【参考方案22】:由于我没有在上面看到此选项,因此我将其添加到所有答案中。
让列表以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:])
【讨论】:
【参考方案23】:使用赋值表达式的解决方案(在 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
【讨论】:
虽然prev :=
看起来增加了清晰度,但似乎没有必要。仅cur <= (cur := nxt)
似乎就足够了。对于reverse=True
,我使用>=
而不是<=
。【参考方案24】:
从Python 3.10
开始,新的pairwise
函数提供了一种遍历连续元素对的方法,从而确定所有这些对是否满足相同的排序谓词:
from itertools import pairwise
all(x <= y for x, y in pairwise([1, 2, 3, 5, 6, 7]))
# True
pairwise
的中间结果:
pairwise([1, 2, 3, 5, 6, 7])
# [(1, 2), (2, 3), (3, 5), (5, 6), (6, 7)]
【讨论】:
【参考方案25】:第三方打包方式more_itertools.is_sorted
暂未提及:
import more_itertools
ls = [1, 4, 2]
print(more_itertools.is_sorted(ls))
ls2 = ["ab", "c", "def"]
print(more_itertools.is_sorted(ls2, key=len))
【讨论】:
【参考方案26】:这种使用 Pandas 的方法非常缓慢,但它以完整性着称。我不建议使用,除非输入数据已经是 Pandas Index
或 Series
。
from typing import Sequence
import pandas as pd
def is_sorted(seq: Sequence, reverse: bool = False) -> bool:
index = pd.Index(seq)
if reverse:
return index.is_monotonic_decreasing
return index.is_monotonic_increasing
【讨论】:
以上是关于检查列表是不是已排序的 Pythonic 方法的主要内容,如果未能解决你的问题,请参考以下文章
python 检查列表中所有项目是否相等的Pythonic方法
有没有一种pythonic方法来检查列表元素,并在列表末尾停止(没有错误)?
有没有一种 Pythonic 方法来检查操作系统是不是是 64 位 Ubuntu?