简体   繁体   中英

Determine if the input list is strictly increasing

I am trying to figure out if an input list is a strictly increasing list. Moreover, If removing only one element from the list results in a strictly increasing list, we still consider the list true. Here is my code. It seems to have an index error, but I do not understand why.

 def almostIncreasingSequence(sequence):
    n=len(sequence)
    count=0
    if n<=2:
        return True

    for i in range (n-1):
        #test if the i-th element is bigger or equal to the elements after it. If it is, remove that element, and add one to count 
        for j in range (i+1,n):
            if sequence[i]>=sequence[j]:
                sequence.pop(i)
                count+=1
    #if there is more than one element that has to be taken out, it's false           
    if count>1:
        return False

    return True
def almost_increasing_sequence(sequence):
    if len(sequence) < 3:
        return True

    a, b, *sequence = sequence
    skipped = 0
    for c in sequence:
        if a < b < c:  # XXX
            a, b = b, c
            continue
        elif b < c:    # !XX
            a, b = b, c
        elif a < c:    # X!X
            a, b = a, c
        skipped += 1
        if skipped == 2:
            return False
    return a < b


if __name__ == '__main__':
    assert almost_increasing_sequence([]) is True
    assert almost_increasing_sequence([1]) is True
    assert almost_increasing_sequence([1, 2]) is True
    assert almost_increasing_sequence([1, 2, 3]) is True
    assert almost_increasing_sequence([3, 1, 2]) is True
    assert almost_increasing_sequence([1, 2, 3, 0, 4, 5, 6]) is True
    assert almost_increasing_sequence([1, 2, 3, 0]) is True
    assert almost_increasing_sequence([1, 2, 0, 3]) is True
    assert almost_increasing_sequence([10, 1, 2, 3, 4, 5]) is True
    assert almost_increasing_sequence([1, 2, 10, 3, 4]) is True
    assert almost_increasing_sequence([1, 2, 3, 12, 4, 5]) is True

    assert almost_increasing_sequence([3, 2, 1]) is False
    assert almost_increasing_sequence([1, 2, 0, -1]) is False
    assert almost_increasing_sequence([5, 6, 1, 2]) is False
    assert almost_increasing_sequence([1, 2, 3, 0, -1]) is False
    assert almost_increasing_sequence([10, 11, 12, 2, 3, 4, 5]) is False

Alright, so it turns out this problem is not that easy.

If you want an efficient solution, I think your best bet may be an algorithm similar to the longest increasing subsequence problem .

But here, we don't care about the actual longest increasing subsequence - we just need it's length. Also, we can short-circuit when maintaining our ordered list if we have had to perform n insertions already (where n is our restriction on the number of "out of order" elements).

This also generalizes very well to the n element "almost increasing" case, and in the worst case performs n-1 binary searches on lists of size Mn-1 to M , where M is the size of the list.

import bisect

def almost_increasing(li, n=1):
    if len(li) < 2:
        return True
    ordered_li = [li[0]]
    violator_count = 0
    for ele in li[1:]:
        if ele < ordered_li[0]:
            violator_count += 1
            ordered_li[0] = ele
        elif ele > ordered_li[-1]:
            ordered_li.append(ele)
        else:
            violator_count += 1
            insertion_pos = bisect.bisect_right(ordered_li, ele)
            ordered_li[insertion_pos] = ele
        if violator_count > n: return False
    return True

The idea behind this algorithm is as follows:

  • We move through the list, and maintain an ordered subsequence of our list all the while.
  • When we reach a new element

    • if that element cannot be appended onto our ordered subsequence, it is a "violator" of the increasing property. We subsequently insert it into the ordered subsequence in the correct position, using bisect for binary search.

    • otherwise, we just append it to our ordered subsequence and continue on.

  • At the end of each iteration, if we have too many violators already we can short-circuit out. Otherwise, after the loop is done we are guaranteed to have an increasing subsequence that has length within n of the length of our original list.


Demo

>>> almost_increasing([5, 1, 2, 3, 4])
True
>>> almost_increasing([1, 2, 5, 2, 15, 0, 176])
False
>>> almost_increasing([1, 2, 5, 2, 15, 0, 176], 2)
True

If you've written for i in range(len(some_list)) in Python, you've probably done the wrong thing. This is indeed why this is failing. n is the length of the sequence as it stands before any processing, but that length can change as you pop items from the list.

Better instead is to compare each mark which indices need to be removed and do them all at once, or better yet -- don't remove them at all!! It's a side-effect that's not well-explained.

You can build this by building a list of all sequences that might be strictly increasing using itertools.combinations , comparing each pair with itertools 's pairwise recipe, then short-circuiting as long as at least one is.

import itertools

def pairwise(iterable):
    (a, b) = itertools.tee(iterable)
    next(b, None)  # advance b
    return zip(a, b)

def almostIncreasingSequence(sequence):
    if not sequence:
        return True
        # in case of empty list
    combos = itertools.combinations(sequence, len(sequence)-1)
    # combos is each ordered combination that's missing one element
    # it is processed as an iterator, so will do no extra work if we can
    # exit early.

    def strictly_increasing(cs):
        return all(a < b for (a, b) in pairwise(cs))

    return any(strictly_increasing(c) for c in combos)

The only thing you need to do is walk the list, counting the number of times sequence[i] > sequence[i+1] . If it happens at most once, your list is almost monotonically increasing.

def almostIncreasingSequence(sequence):
    count = 0
    for i in range(0, len(sequence) - 1):
        if sequence[i] > sequence[i+1]:
            count += 1
    return count < 2

You can also avoid counting, since the number of exceptions is small. Just return False as soon as you find the second exception, as tracked by the value of a Boolean variable initialized to True .

def almostIncreasingSequence(sequence):
    increasing = True
    for i in range(0, len(sequence) - 1):
        if sequence[i] > sequence[i+1]:
            if increasing:
                increasing = False
            else:
                return False
    return True

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM