2

So I have a function that performs just fine on small lists. It's function is to check if removing one element from the sequence will make the sequence a strictly increasing sequence:

def almostIncreasingSequence(sequence):
    length = len(sequence)
    for i in range(1, length):
        newSequence = sequence[:i-1] + sequence[i:]
        if checkIfSorted(newSequence, length):
            return True
    return checkIfSorted(sequence[:length-1], length)

def checkIfSorted(sequence, length):
    for i in range(1, length - 1):
        if sequence[i-1] >= sequence[i]:
            return False
    return True

But I need it to work on lists up to 100,000 elements long. What sort of optimizations could I make for this to work faster? Right now, it's abysmally slow on lists of 100,000, working through a few thousand elements a second.

HoldenGs
  • 87
  • 2
  • 8
  • Want to be sure about something before offer a solutio. You need to check if reomving only one element will convert it into strictly increasing sequence? Right? – gautamaggarwal Mar 21 '18 at 13:01
  • Yes, or if it's already a strictly increasing sequence on its own @gautamaggarwal – HoldenGs Mar 21 '18 at 20:00
  • I saw so all the answers in it and I guess there was a pretty solution to this problem. You just need to loop over whole list checking if the next element is greater than current one and if next is lesser than that element needs to be removed. But even after that element being found you will need to check if any other element does same. It will run in O(n). No? – gautamaggarwal Mar 29 '18 at 11:43

4 Answers4

1

I wrote another answer on this site answering almost the same question as you, but mine was for checking if removing at most one element from the sequence makes it strictly increasing. That may be what you mean--there seems to be no practical difference. It seems you want my second solution, copied here.

def first_bad_pair(sequence, k):
    """Return the first index of a pair of elements in sequence[]
    for indices k-1, k+1, k+2, k+3, ... where the earlier element is
    not less than the later element. If no such pair exists, return -1."""
    if 0 < k < len(sequence) - 1:
        if sequence[k-1] >= sequence[k+1]:
            return k-1
    for i in range(k+1, len(sequence)-1):
        if sequence[i] >= sequence[i+1]:
            return i
    return -1

def almostIncreasingSequence(sequence):
    """Return whether it is possible to obtain a strictly increasing
    sequence by removing no more than one element from the array."""
    j = first_bad_pair(sequence, -1)
    if j == -1:
        return True  # List is increasing
    if first_bad_pair(sequence, j) == -1:
        return True  # Deleting earlier element makes increasing
    if first_bad_pair(sequence, j+1) == -1:
        return True  # Deleting later element makes increasing
    return False  # Deleting either does not make increasing

Your code is slow because it, like my first solution, makes new sequences by joining slices. This copies almost the entire sequence, and doing that many times slows the code. The code above avoids that by complicating the routine that checks a sequence to see if it is strictly increasing. Check my other linked answer for more details.

Rory Daulton
  • 21,934
  • 6
  • 42
  • 50
1

My solution:

def is_almost_increasing(x):
    lastx = x[0]  # value to use in the next comparison
    found_one = False
    for i in range(1, len(x)):
        if x[i] <= lastx:
            if found_one:
                return False
            found_one = True
            if i > 1 and x[i] <= x[i-2]:  # i > 1 in case the first comparison failed
                break
        lastx = x[i]
    return True


print('\nThese should be True.')
print(is_almost_increasing([1]))
print(is_almost_increasing([1, 2]))
print(is_almost_increasing([1, 2, 3]))
print(is_almost_increasing([1, 3, 2]))
print(is_almost_increasing([10, 1, 2, 3, 4, 5]))
print(is_almost_increasing([0, -2, 5, 6]))
print(is_almost_increasing([1, 1]))
print(is_almost_increasing([1, 2, 3, 4, 3, 6]))
print(is_almost_increasing([1, 2, 3, 4, 99, 5, 6]))
print(is_almost_increasing([1, 2, 2, 3]))

print('\nThese should be False.')
print(is_almost_increasing([1, 3, 2, 1]))
print(is_almost_increasing([3, 2, 1]))
print(is_almost_increasing([1, 1, 1]))

This is pretty similar to, but slightly shorter than, Rory Daulton's. I borrowed his test code from the link he provided, so thanks to him for that. The point is that you don't want to build a lot of secondary lists, which is inefficient. To get a really big improvement in efficiency you almost always need to find a better algorithm.

The two complications here are (1) what to do when the first element fails the test? (2) when you find an element out of sequence, do you drop that element or the one before it? The comments address that.

Paul Cornelius
  • 9,245
  • 1
  • 15
  • 24
1

Okay I know this post has already an answer. But I will just like to offer my code for same problem. Suppose you have two lists lis1 and lis2 as follows:-

test_lis1 = [1, 2, 3, 4, 5, 2, 6]
test_lis2 = [1, 2, 3, 1, 4, 5, 1]
test_lis3 = [7, 1, 2, 3, 4, 5, 6]

Now, I guess your problem is that you want to know if this list can be converted to a strictly increasing subsequence by removing ONLY one element. If we need to remove two, Then answer is no.

Here is the function:-

def remove_one_only(lis):
    len_lis = len(lis)
    i = 0
    j = 1
    rem_count = 0
    flag = False
    while j <= len_lis - 1:
        if lis[i] > lis[j]:
            rem_count += 1
            if rem_count == 2:
                break
            if i > 0:
                j += 1
                continue
        i = j
        j += 1
    if rem_count == 1:
        flag = True
    return flag

This function will tell you answer in yes or no if removing only one element can convert list into strictly increasing list and it runs in O(n). This is the fastest you can get.

gautamaggarwal
  • 341
  • 2
  • 11
0

This would be the most pythonic solution I've found, but it's not the fastest.

def almostIncreasingSequence(sequence):
  return any([all([i<j for i,j in zip(sequence[:k]+sequence[k+1:],(sequence[:k]+sequence[k+1:])[1:])]) for k in range(len(sequence))])

Looks nice, though. :)

naicul38
  • 1
  • 2