HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Find the locally biggest or equal values including corners in an array

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
theequalarraylocallybiggestincludingfindvaluescorners

Problem

I am looking for a function, that will find in array of n integers highest or equal locally values including corners.

My code is probably wrong, and I somehow think solution is in some library like NumPy or SciPy, but I do not know how to find it:

def look_for_maximas(vals):
    '''
    actually look for hills, 1, 0, 1, 1, 0, 1, 1, 2, 1, 0, 0 should return indexes 0, 2, 3, 7
    :param vals:
    :return:
    '''
    if len(vals) == 0:
        return []
    res = []
    buff = [0]
    is_candidate = True
    i = 1
    while i  vals[i-1]:
            is_candidate = True
            buff = [i]
        elif vals[i] == vals[i-1] and is_candidate:
            buff.append(i)
        else:
            if is_candidate:
                res += buff
            is_candidate = False
            buff = []
        i += 1
    if is_candidate:
        res += buff

    return res


I have some tests to test it:

assert look_for_maximas([1, 0, 1, 1, 0, 1, 1, 2, 1, 0, 0]) == [0, 2, 3, 7]
assert look_for_maximas([0]) == [0]
assert look_for_maximas([0, 1, 0]) == [1]
assert look_for_maximas([]) == []
assert look_for_maximas([0, 0, 0, 0]) == [0, 1, 2, 3]
assert look_for_maximas([i for i in repeat(0, 1000)]) == range(0, 1000)
assert look_for_maximas(
    [100, 0, 0, 100, 10, 0, 10, 10, 0, 5, 3, 0, 0, 10, 10, 10, 10, 100, 0, 1]) == [0, 3, 6, 7, 9, 17, 19]


And it passes it, but it is probably not the best code and I am probably inventing wheel one more time.

Solution

First off you can change the while loop to a for loop.

for i in range(1, len(vals)):


This makes the program easier to understand. As then there is no i += 1.

It is also un-pythonic to do:

if len(vals) == 0:


Instead do:

if not vals:


As you want it to work with edge cases, what about generators? And other objects that are iterable, but not index-able.

I would change the if not vals: to a try except.

vals = iter(vals)
try:
    prev = next(vals)
except StopIteration:
    return []


And then you can change the for loop to use enumerate.

for i, curr in enumerate(vals, 1):
    if curr > prev:
        is_candidate = True
        buff = [i]
    elif curr == prev and is_candidate:
        buff.append(i)
    else:
        if is_candidate:
            res += buff
        is_candidate = False
        buff = []
    prev = curr


now it will work with anything that is iterable. And so it will work with generators.

Both yield and list.append are \$O(1)\$. However I like to think that list.append is \$O(n)\$. This is as lists rely on the amortized worst case to be \$O(1)\$.

From the Python time complexity page.


Internally, a list is represented as an array; the largest costs come from growing beyond the current allocation size (because everything must move)

So using range, assuming you're using Python3, and generators can be better.

def look_for_maximas(vals): 
    def res(vals):
        vals = iter(vals)
        # Exploiting for-loops and generators. It's kinda surprising this works.
        prev = next(vals)

        start = 0
        # Has to be in scope for the last yield.
        i = 1
        for curr in vals:
            if curr > prev:
                start = i
            elif curr < prev:
                if start is not None:
                    yield range(start, i)
                start = None
            prev = curr
            i += 1

        if start is not None:
            yield range(start, i)

    for range_ in res(vals):
        for i in range_:
            yield i


It is not as fast as a NumPy or SciPi solution, but can be a lot faster than using lists.

I tested this by doing assert list(look_for_maximas(...)) == .... And it worked for them all. However I don't have repeat, and removed that test.

Code Snippets

for i in range(1, len(vals)):
if len(vals) == 0:
if not vals:
vals = iter(vals)
try:
    prev = next(vals)
except StopIteration:
    return []
for i, curr in enumerate(vals, 1):
    if curr > prev:
        is_candidate = True
        buff = [i]
    elif curr == prev and is_candidate:
        buff.append(i)
    else:
        if is_candidate:
            res += buff
        is_candidate = False
        buff = []
    prev = curr

Context

StackExchange Code Review Q#98574, answer score: 4

Revisions (0)

No revisions yet.