Python - 如何检查列表单调性

时间:2011-02-13 08:45:49

标签: python list performance

检查列表单调性的高效和pythonic 方法是什么?左边是单调增加还是减少值?

示例:

[0, 1, 2, 3, 3, 4]   # This is a monotonically increasing list
[4.3, 4.2, 4.2, -2]  # This is a monotonically decreasing list
[2, 3, 1]            # This is neither

14 个答案:

答案 0 :(得分:127)

def strictly_increasing(L):
    return all(x<y for x, y in zip(L, L[1:]))

def strictly_decreasing(L):
    return all(x>y for x, y in zip(L, L[1:]))

def non_increasing(L):
    return all(x>=y for x, y in zip(L, L[1:]))

def non_decreasing(L):
    return all(x<=y for x, y in zip(L, L[1:]))

def monotonic(L):
    return non_increasing(L) or non_decreasing(L)

答案 1 :(得分:32)

如果你有大量的数字列表,最好使用numpy,如果你是:

import numpy as np

def monotonic(x):
    dx = np.diff(x)
    return np.all(dx <= 0) or np.all(dx >= 0)

应该这样做。

答案 2 :(得分:25)

import itertools
import operator

def monotone_increasing(lst):
    pairs = zip(lst, lst[1:])
    return all(itertools.starmap(operator.le, pairs))

def monotone_decreasing(lst):
    pairs = zip(lst, lst[1:])
    return all(itertools.starmap(operator.ge, pairs))

def monotone(lst):
    return monotone_increasing(lst) or monotone_decreasing(lst)

这种方法在列表的长度上是O(N)

答案 3 :(得分:15)

@ 6502有完美的列表代码,我只想添加适用于所有序列的通用版本:

def pairwise(seq):
    items = iter(seq)
    last = next(items)
    for item in items:
        yield last, item
        last = item

def strictly_increasing(L):
    return all(x<y for x, y in pairwise(L))

def strictly_decreasing(L):
    return all(x>y for x, y in pairwise(L))

def non_increasing(L):
    return all(x>=y for x, y in pairwise(L))

def non_decreasing(L):
    return all(x<=y for x, y in pairwise(L))

答案 4 :(得分:4)

import operator, itertools

def is_monotone(lst):
    op = operator.le            # pick 'op' based upon trend between
    if not op(lst[0], lst[-1]): # first and last element in the 'lst'
        op = operator.ge
    return all(op(x,y) for x, y in itertools.izip(lst, lst[1:]))

答案 5 :(得分:2)

以下是使用reduce复杂度O(n)的功能性解决方案:

is_increasing = lambda L: reduce(lambda a,b: b if a < b else 9999 , L)!=9999

is_decreasing = lambda L: reduce(lambda a,b: b if a > b else -9999 , L)!=-9999

9999替换为您的值的上限,将-9999替换为下限。例如,如果您要测试数字列表,则可以使用10-1

我针对@6502's answer及其速度更快地测试了其效果。

案例真实:[1,2,3,4,5,6,7,8,9]

# my solution .. 
$ python -m timeit "inc = lambda L: reduce(lambda a,b: b if a < b else 9999 , L)!=9999; inc([1,2,3,4,5,6,7,8,9])"
1000000 loops, best of 3: 1.9 usec per loop

# while the other solution:
$ python -m timeit "inc = lambda L: all(x<y for x, y in zip(L, L[1:]));inc([1,2,3,4,5,6,7,8,9])"
100000 loops, best of 3: 2.77 usec per loop

第二个元素的案例错误:[4,2,3,4,5,6,7,8,7]

# my solution .. 
$ python -m timeit "inc = lambda L: reduce(lambda a,b: b if a < b else 9999 , L)!=9999; inc([4,2,3,4,5,6,7,8,7])"
1000000 loops, best of 3: 1.87 usec per loop

# while the other solution:
$ python -m timeit "inc = lambda L: all(x<y for x, y in zip(L, L[1:]));inc([4,2,3,4,5,6,7,8,7])"
100000 loops, best of 3: 2.15 usec per loop

答案 6 :(得分:1)

L = [1,2,3]
L == sorted(L)

L == sorted(L, reverse=True)

答案 7 :(得分:1)

我在不同的条件下计算了这个问题的所有答案,并发现:

  • 如果列表已经单调增加,则排序是最快的
  • 如果列表被洗牌/随机或者乱序的元素数量大于〜1,则排序是最慢的。当然列表越顺序,结果就越慢。
  • Michael J. Barbers方法是最快的,因为该列表大多是单调增加,或完全随机。

以下是试用它的代码:

import timeit

setup = '''
import random
from itertools import izip, starmap, islice
import operator

def is_increasing_normal(lst):
    for i in range(0, len(lst) - 1):
        if lst[i] >= lst[i + 1]:
            return False
    return True

def is_increasing_zip(lst):
    return all(x < y for x, y in izip(lst, islice(lst, 1, None)))

def is_increasing_sorted(lst):
    return lst == sorted(lst)

def is_increasing_starmap(lst):
    pairs = izip(lst, islice(lst, 1, None))
    return all(starmap(operator.le, pairs))

if {list_method} in (1, 2):
    lst = list(range({n}))
if {list_method} == 2:
    for _ in range(int({n} * 0.0001)):
        lst.insert(random.randrange(0, len(lst)), -random.randrange(1,100))
if {list_method} == 3:
    lst = [int(1000*random.random()) for i in xrange({n})]
'''

n = 100000
iterations = 10000
list_method = 1

timeit.timeit('is_increasing_normal(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

timeit.timeit('is_increasing_zip(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

timeit.timeit('is_increasing_sorted(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

timeit.timeit('is_increasing_starmap(lst)', setup=setup.format(n=n, list_method=list_method), number=iterations)

如果列表已经单调增加(list_method == 1),最慢到最慢的是:

  1. 排序
  2. 星图
  3. 正常
  4. 拉链
  5. 如果列表大部分单调增加(list_method == 2),最慢到最慢的是:

    1. 星图
    2. 拉链
    3. 正常
    4. 排序
    5. (starmap或zip是否最快取决于执行情况,我无法识别模式。星图通常更快)

      如果列表是完全随机的(list_method == 3),最慢到最慢的是:

      1. 星图
      2. 拉链
      3. 正常
      4. 排序(非常糟糕)

答案 8 :(得分:1)

@ 6502为此提供了精美的python代码。这是一种迭代器更简单且没有潜在昂贵的临时片的替代解决方案:

def strictly_increasing(L):
    return all(L[i] < L[i+1] for i in range(len(L)-1))

def strictly_decreasing(L):
    return all(L[i] > L[i+1] for i in range(len(L)-1))

def non_increasing(L):
    return all(L[i] >= L[i+1] for i in range(len(L)-1))

def non_decreasing(L):
    return all(L[i] <= L[i+1] for i in range(len(L)-1))

def monotonic(L):
    return non_increasing(L) or non_decreasing(L)

答案 9 :(得分:0)

使用Pandas可以实现,您可以通过pip install pandas安装。

import pandas as pd

以下命令可处理整数或浮点数的列表。

Monotonically increasing(≥):

pd.Series(mylist).is_monotonic_increasing

严格单调递增(>):

myseries = pd.Series(mylist)
myseries.is_unique and myseries.is_monotonic_increasing

使用未公开的私有方法的替代方法:

pd.Index(mylist)._is_strictly_monotonic_increasing

Monotonically decreasing(≤):

pd.Series(mylist).is_monotonic_decreasing

严格单调递减(<):

myseries = pd.Series(mylist)
myseries.is_unique and myseries.is_monotonic_decreasing

使用未公开的私有方法的替代方法:

pd.Index(mylist)._is_strictly_monotonic_decreasing

答案 10 :(得分:0)

这是一个接受物化非物化序列的变体。它会自动确定其是否为monotonic,如果是,则确定其方向(即increasingdecreasing)和strict的方向。提供内联注释以帮助读者。最后提供的测试用例也是如此。

    def isMonotonic(seq):
    """
    seq.............: - A Python sequence, materialized or not.
    Returns.........:
       (True,0,True):   - Mono Const, Strict: Seq empty or 1-item.
       (True,0,False):  - Mono Const, Not-Strict: All 2+ Seq items same.
       (True,+1,True):  - Mono Incr, Strict.
       (True,+1,False): - Mono Incr, Not-Strict.
       (True,-1,True):  - Mono Decr, Strict.
       (True,-1,False): - Mono Decr, Not-Strict.
       (False,None,None) - Not Monotonic.
    """    
    items = iter(seq) # Ensure iterator (i.e. that next(...) works).
    prev_value = next(items, None) # Fetch 1st item, or None if empty.
    if prev_value == None: return (True,0,True) # seq was empty.

    # ============================================================
    # The next for/loop scans until it finds first value-change.
    # ============================================================
    # Ex: [3,3,3,78,...] --or- [-5,-5,-5,-102,...]
    # ============================================================
    # -- If that 'change-value' represents an Increase or Decrease,
    #    then we know to look for Monotonically Increasing or
    #    Decreasing, respectively.
    # -- If no value-change is found end-to-end (e.g. [3,3,3,...3]),
    #    then it's Monotonically Constant, Non-Strict.
    # -- Finally, if the sequence was exhausted above, which means
    #    it had exactly one-element, then it Monotonically Constant,
    #    Strict.
    # ============================================================
    isSequenceExhausted = True
    curr_value = prev_value
    for item in items:
        isSequenceExhausted = False # Tiny inefficiency.
        if item == prev_value: continue
        curr_value = item
        break
    else:
        return (True,0,True) if isSequenceExhausted else (True,0,False)
    # ============================================================

    # ============================================================
    # If we tricked down to here, then none of the above
    # checked-cases applied (i.e. didn't short-circuit and
    # 'return'); so we continue with the final step of
    # iterating through the remaining sequence items to
    # determine Monotonicity, direction and strictness.
    # ============================================================
    strict = True
    if curr_value > prev_value: # Scan for Increasing Monotonicity.
        for item in items:
            if item < curr_value: return (False,None,None)
            if item == curr_value: strict = False # Tiny inefficiency.
            curr_value = item
        return (True,+1,strict)
    else:                       # Scan for Decreasing Monotonicity.
        for item in items: 
            if item > curr_value: return (False,None,None)
            if item == curr_value: strict = False # Tiny inefficiency.
            curr_value = item
        return (True,-1,strict)
    # ============================================================


# Test cases ...
assert isMonotonic([1,2,3,4])     == (True,+1,True)
assert isMonotonic([4,3,2,1])     == (True,-1,True)
assert isMonotonic([-1,-2,-3,-4]) == (True,-1,True)
assert isMonotonic([])            == (True,0,True)
assert isMonotonic([20])          == (True,0,True)
assert isMonotonic([-20])         == (True,0,True)
assert isMonotonic([1,1])         == (True,0,False)
assert isMonotonic([1,-1])        == (True,-1,True)
assert isMonotonic([1,-1,-1])     == (True,-1,False)
assert isMonotonic([1,3,3])       == (True,+1,False)
assert isMonotonic([1,2,1])       == (False,None,None)
assert isMonotonic([0,0,0,0])     == (True,0,False)

我想这可能更多Pythonic,但这很棘手,因为它避免了创建中间集合(例如listgenexps等);以及采用fall/trickle-throughshort-circuit方法来过滤各种情况:边缘序列(如空序列或单项序列;或具有所有相同项目的序列);确定增加或减少的单调性,严格性等。希望对您有所帮助。

答案 11 :(得分:0)

def IsMonotonic(data):
    ''' Returns true if data is monotonic.'''
    data = np.array(data)
    # Greater-Equal
    if (data[-1] > data[0]):
        return np.all(data[1:] >= data[:-1])
    # Less-Equal
    else:
        return np.all(data[1:] <= data[:-1])

我的提议(用 numpy)作为这里几个想法的总结。用途

  • 投射到 np.array 以创建每个列表比较的布尔值,
  • np.all 用于检查所有结果是否都为 True
  • 检查第一个和最后一个元素之间的差异以选择比较运算符,
  • 使用直接比较 >=, <= 而不是计算 np.diff

答案 12 :(得分:0)

这里有两种方法可以仅使用 range 或列表推导式来确定列表是单调递增还是单调递减。使用 range 稍微更有效,因为它可以短路,而列表推导式必须迭代整个列表。享受。

a = [1,2,3,4,5]
b = [0,1,6,1,0]
c = [9,8,7,6,5]

def monotonic_increase(x):
    if len(x) <= 1: return False

    for i in range(1, len(x)):
        if x[i-1] >= x[i]:
            return False
    return True

def monotonic_decrease(x):
    if len(x) <= 1: return False

    for i in range(1, len(x)):
        if x[i-1] <= x[i]:
            return False

    return True

monotonic_increase = lambda x: len(x) > 1 and all(x[i-1] < x[i] for i in range(1, len(x)))
monotonic_decrease = lambda x: len(x) > 1 and all(x[i-1] > x[i] for i in range(1, len(x)))

print(monotonic_increase(a))
print(monotonic_decrease(c))
print(monotonic_decrease([]))
print(monotonic_increase(c))
print(monotonic_decrease(a))
print(monotonic_increase(b))
print(monotonic_decrease(b))

答案 13 :(得分:-2)

>>> l = [0,1,2,3,3,4]
>>> l == sorted(l) or l == sorted(l, reverse=True)