Python - Cách kiểm tra tính đơn điệu của danh sách


81

Điều gì sẽ là một cách hiệu quả và khó hiểu để kiểm tra tính đơn điệu của danh sách?
tức là nó có các giá trị tăng hoặc giảm đơn điệu?

Ví dụ:

[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

5
Tốt hơn là sử dụng các cụm từ "tăng nghiêm ngặt" hoặc "không giảm" để loại bỏ mọi sự mơ hồ (và theo cách tương tự, tốt hơn là nên tránh "tích cực" và thay vào đó sử dụng "không tiêu cực" hoặc "hoàn toàn tích cực")
6502

13
@ 6502 thuật ngữ đơn điệu được định nghĩa là một tập hợp các giá trị có thứ tự không tăng hoặc không giảm, vì vậy không có sự mơ hồ trong câu hỏi.
Autoplectic

nếu bạn đang tìm cách trích xuất phần dữ liệu với tính đơn điệu nhất định , vui lòng xem tại: github.com/Weilory/python-regression/blob/master/regression/…
Weilory

Câu trả lời:


160

Tốt hơn là nên tránh các thuật ngữ mơ hồ như "tăng" hoặc "giảm" vì không rõ liệu sự bình đẳng có được chấp nhận hay không. Bạn nên luôn sử dụng ví dụ: "không tăng" (rõ ràng là bình đẳng được chấp nhận) hoặc "giảm dần" (rõ ràng là bình đẳng KHÔNG được chấp nhận).

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)

14
Đây là mã Python thành ngữ, rõ ràng và độ phức tạp của nó là O (n) trong đó các câu trả lời sắp xếp đều là O (n log n). Một câu trả lời lý tưởng sẽ chỉ lặp lại danh sách một lần để nó hoạt động trên bất kỳ trình lặp nào, nhưng điều này thường đủ tốt và cho đến nay nó là câu trả lời tốt nhất trong số các trình lặp. (Tôi muốn đưa ra một giải pháp đơn vượt qua, nhưng OP sớm chấp nhận một câu trả lời kiềm chế bất cứ thôi thúc tôi có thể phải làm như vậy ...)
Glenn Maynard

2
chỉ vì tò mò đã thử nghiệm việc triển khai của bạn so với việc sử dụng sắp xếp. Của bạn rõ ràng là chậm hơn rất nhiều [Tôi đã sử dụng L = range (10000000)]. Có vẻ như độ phức tạp của tất cả là O (n), và tôi không thể tìm thấy việc triển khai zip.
Dấu hoa thị

4
Sắp xếp là chuyên biệt nếu danh sách đã được sắp xếp. Bạn đã thử tốc độ với một danh sách được xáo trộn ngẫu nhiên chưa? Cũng lưu ý rằng với sắp xếp, bạn không thể phân biệt giữa tăng và không giảm. Cũng xem xét rằng với Python 2.x sử dụng itertools.izipthay vì zipbạn có thể nhận được một lối ra sớm (trong python 3 zipđã hoạt động như một iterator)
6502

3
@ 6502: chỉ cần một chức năng: toán tử nhập; def monotone (L, op): trả về tất cả (op (x, y) cho x, y trong zip (L, L [1:])) và sau đó chỉ cần nạp những gì bạn muốn: operator.le hoặc .ge hoặc bất cứ thứ gì
akira

5
zip và toán tử lát cắt đều trả về toàn bộ danh sách, loại bỏ khả năng tắt của all (); điều này có thể được cải thiện đáng kể bằng cách sử dụng itertools.izip và itertools.islice, vì theo cách này, hoặc theo đúng_thường_nên hoặc_decreasing sẽ bị lỗi rất sớm.
Hugh Bothwell 13/02/11

36

Nếu bạn có nhiều danh sách các con số, tốt nhất nên sử dụng numpy và nếu bạn là:

import numpy as np

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

nên làm thủ thuật.


Lưu ý rằng dx [0] là np.nan. Bạn có thể muốn sử dụng: dx = np.diff (x) [1:] để bỏ qua nó. Nếu không, ít nhất đối với tôi, các lệnh gọi np.all () luôn trả về Sai.
Ryan

@Ryan, tại sao lại dx[0]như NaNvậy? Mảng đầu vào của bạn là gì?
DilithiumMatrix

1
Không, tôi đã nghĩ rằng np.diff()đã tạo phần tử đầu tiên NaNđể hình dạng của đầu ra khớp với đầu vào, nhưng đó thực sự là một đoạn mã khác đã làm điều đó khiến tôi khó chịu. :)
Ryan

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

Cách tiếp cận này nằm O(N)trong độ dài của danh sách.


3
Giải pháp (TM) đúng IMO. Mô hình chức năng để giành chiến thắng!
mike3996 13/02/11

2
tại sao sử dụng itertools thay vì máy phát điện đơn giản?
6502

3
Các mô hình chức năng thường không phải là "chiến thắng" trong Python.
Glenn Maynard

@ 6502 Chủ yếu là thói quen. Mặt khác map, ở đây chính xác là cần trừu tượng, vậy tại sao phải tạo lại nó bằng biểu thức trình tạo?
Michael J. Barber

3
Tính toán các cặp là O(N)tốt. Bạn có thể thực hiện pairs = itertools.izip(lst, itertools.islice(lst, 1, None)).
Tomasz Elendt

18

@ 6502 có mã hoàn hảo cho danh sách, tôi chỉ muốn thêm một phiên bản chung hoạt động cho tất cả các chuỗi:

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

6

Các Pandas gói làm cho thuận tiện này.

import pandas as pd

Các lệnh sau hoạt động với danh sách các số nguyên hoặc số thực.

Tăng đơn điệu (≥):

pd.Series(mylist).is_monotonic_increasing

Tăng đơn điệu nghiêm ngặt (>):

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

Thay thế bằng phương pháp riêng tư không có giấy tờ:

pd.Index(mylist)._is_strictly_monotonic_increasing

Giảm đơn điệu (≤):

pd.Series(mylist).is_monotonic_decreasing

Giảm đơn điệu nghiêm ngặt (<):

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

Thay thế bằng phương pháp riêng tư không có giấy tờ:

pd.Index(mylist)._is_strictly_monotonic_decreasing

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

Tôi đã nghĩ về một giải pháp như thế này - nhưng nó không thành công nếu danh sách tăng đơn điệu và hai phần tử đầu tiên bằng nhau.
Hugh Bothwell 13/02/11

@Hugh Bothwell: i kiểm tra bây giờ là người đầu tiên và cuối cùng để có được xu hướng này: nếu chúng bằng nhau thì tất cả các yếu tố khác cần được bình đẳng cũng sau đó làm việc cho cả hai operator.le và operator.ge
akira

3

Đây là một giải pháp chức năng sử dụng reduceđộ phức tạp 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

Thay thế 9999bằng giới hạn trên cùng của các giá trị của bạn và -9999bằng giới hạn dưới cùng. Ví dụ: nếu bạn đang kiểm tra danh sách các chữ số, bạn có thể sử dụng 10-1.


Tôi đã kiểm tra hiệu suất của nó so với câu trả lời của @ 6502 và tốc độ của nó nhanh hơn.

Đúng: [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

Trường hợp Sai từ phần tử thứ 2[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

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

L == sorted(L, reverse=True)

Tôi sẽ bỏ qua sorted()nếu nó không thực sự sắp xếp bất cứ điều gì, chỉ cần kiểm tra. Đặt tên xấu - nghe giống như một vị ngữ khi nó không phải.
mike3996

13
Cái gì tiếp theo? Sử dụng sorted(L)[0]thay vì min?
6502

4
Điều này là kém về mặt thuật toán; nghiệm này là O (n log n), khi bài toán này có thể được thực hiện một cách đáng kể trong O (n).
Glenn Maynard

@ tất cả đều đồng ý với tất cả các bạn, cảm ơn những lời phê bình mang tính xây dựng.
Dấu hoa thị

1
Tôi đã thử nghiệm tất cả các giải pháp trong chủ đề này ở đây và nhận thấy rằng phương pháp đã sắp xếp thực sự là tốt nhất ... nếu danh sách thực sự tăng đơn điệu. Nếu danh sách có bất kỳ mục nào không theo thứ tự, nó sẽ trở thành danh sách chậm nhất.
Matthew Moisen

1

Tôi đã định thời gian cho tất cả các câu trả lời trong câu hỏi này trong các điều kiện khác nhau và nhận thấy rằng:

  • Sắp xếp là nhanh nhất bởi một cú đánh dài NẾU danh sách đã tăng lên một cách đơn điệu
  • Sắp xếp chậm nhất bởi một khoảng thời gian dài NẾU danh sách bị xáo trộn / ngẫu nhiên hoặc nếu số phần tử không theo thứ tự lớn hơn ~ 1. Tất nhiên danh sách càng không theo thứ tự thì kết quả càng chậm.
  • Phương pháp của Michael J. Barbers là nhanh nhất NẾU danh sách chủ yếu tăng đơn điệu, hoặc hoàn toàn ngẫu nhiên.

Đây là mã để dùng thử:

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)

Nếu danh sách đã tăng đơn điệu ( list_method == 1), thì nhanh nhất đến chậm nhất là:

  1. sắp xếp
  2. bản đồ sao
  3. bình thường
  4. zip

Nếu danh sách chủ yếu tăng đơn điệu ( list_method == 2), thì nhanh nhất đến chậm nhất là:

  1. bản đồ sao
  2. zip
  3. bình thường
  4. sắp xếp

(Bản đồ sao hoặc zip có nhanh nhất hay không phụ thuộc vào quá trình thực thi và tôi không thể xác định một mẫu. Bản đồ sao dường như thường nhanh hơn)

Nếu danh sách hoàn toàn ngẫu nhiên ( list_method == 3), thì nhanh nhất đến chậm nhất là:

  1. bản đồ sao
  2. zip
  3. bình thường
  4. đã sắp xếp (cực kỳ tệ)

Tôi không thử phương pháp @Assem Chelli như nó đòi hỏi kiến thức về mục tối đa trong danh sách
Matthew Moisen

Các so sánh về thời gian cũng sẽ phụ thuộc nhiều vào quy mô ncủa danh sách và có thể thay đổi đáng kể từ 100000
nealmcb

0

@ 6502 có mã python thanh lịch cho việc này. Đây là một giải pháp thay thế với các trình vòng lặp đơn giản hơn và không có các lát cắt tạm thời có khả năng đắt tiền:

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)


-1

Đây là một biến thể chấp nhận cả trình tự hiện thực hóakhông hiện thực hóa . Nó tự động xác định xem nó có hay không monotonic, và nếu có, hướng của nó (tức là increasinghoặc decreasing) và strictness. Nhận xét nội tuyến được cung cấp để giúp người đọc. Tương tự như vậy đối với các trường hợp thử nghiệm được cung cấp ở cuối.

    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)

Tôi cho rằng điều này có thể có nhiều Pythonic, nhưng đó là khó khăn bởi vì nó tránh được việc tạo ra bộ sưu tập trung (ví dụ list, genexps, vv); cũng như sử dụng phương pháp fall/trickle-throughshort-circuitcách tiếp cận để lọc qua các trường hợp khác nhau: Ví dụ: Trình tự cạnh (như trình tự trống hoặc một mục; hoặc chuỗi có tất cả các mục giống nhau); Xác định tính đơn điệu tăng hoặc giảm, tính chặt chẽ, v.v. Tôi hy vọng nó sẽ giúp.


Tại sao lại ủng hộ? Nó được giải thích rõ ràng và cung cấp cho người đọc một cách tiếp cận thay thế cho những người khác.
NYCeyes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.