3

I want to find a peak in a list.

  1. I want to find if a number is bigger than his neighbors.
  2. if it is the first object in the list I want to check only if he is bigger than the one after him.
  3. if it is the last object in the list I want to check the one before him.
def peaks(lst):
    num = 0
    leni = len(lst)
    print(leni)
    for i in range(1,leni - 1):
        if  lst[i] > lst[i-1] and lst[i] > lst[i+1]:
                    num = num + 1
    for i in range(leni):
        print(i)
        if i == 0:
            if lst[i] > lst[i+1]:
                num = num + 1
        elif i == leni+1:
            if lst[i] > lst[i-1]:
                num = num + 1
    return num

This code doesn't work when it should check the last object. When I try [1,2,3] I get 0 instead of 1.

matheburg
  • 2,097
  • 1
  • 19
  • 46
Nadavsh
  • 29
  • 5

8 Answers8

4

You could do some trickery to count peaks by making the boundary special cases not so special any more:

def peaks(lst): 
    lst = [float("-inf")] + lst + [float("-inf")]
    return sum(a < b > c for a, b, c in zip(lst, lst[1:], lst[2:]))
user2390182
  • 72,016
  • 6
  • 67
  • 89
  • 1
    I know it's not really important, but when I saw your answer I was wondering if `lst = [float("-inf")] + lst + [float("-inf")]` is the 'best' way to do this or if `lst = [float("-inf"), *lst, float("-inf")]` might be better. I did a quick speed test, and it seems `lst = [float("-inf"), *lst, float("-inf")]` is about twice as fast. – mapf Dec 17 '20 at 13:20
  • 2
    True, that may well be, but it won't change asymptotic time complexity. Also, the given code will work in all Python versions, so I will leave it. If you want to go fully berserk on the syntactic sugar front, do: `lst = [i:=float("-inf"), *lst, i]` ;-) – user2390182 Dec 17 '20 at 13:27
  • Ok, you lost me at asymptotic time complexity :D that is some pretty interesting syntax you got there though, never seen that before! Have to remember that and see if I can find a use case... – mapf Dec 17 '20 at 13:32
  • 2
    https://www.python.org/dev/peps/pep-0572/ :-) you'll find plenty use cases – user2390182 Dec 17 '20 at 13:36
  • Oh my god. That makes `while` loops so much nicer. I'm still working in Python 3.7 though. Maybe it's time to upgrade.. but I'll also shut up about the ot now. – mapf Dec 17 '20 at 13:42
1

Hello and welcome to Stackoverflow!

Note that range(leni) is a sequence of numbers from 0 to leni - 1 inclusive. So your condition i == leni+1 is never satisfied. You may replace it to i == leni - 1.

Note also that you don't need a second loop. You may just replace it with

if lst[0] > lst[1]:
    num = num + 1
if lst[-1] > lst[-2]:
    num= num + 1

Here lst[-1] is the same as lst[leni - 1] and lst[-2] is the same as lst[leni - 2].

Ilya V. Schurov
  • 7,687
  • 2
  • 40
  • 78
0

Should do the trick

def peaks(lst):
  lst_len = len(lst)
  hits = 0
      
  for i in range(1,lst_len-1):
    num_to_test = lst[i]
    before = lst[i-1]
    after = lst[i+1]
    if num_to_test > before and num_to_test > after:
      hits+=1
    # This is for the case lst contains only 1 member and you want to count it as 1
    if lst_len == 1:
      hits+=1
    # For checking the edges 
    if lst_len > 1:
      if lst[0] > lst[1]:
        hits+=1
      if lst[lst_len-1] > lst[lst_len-2]:
        hits+=1

return hits
CloudBalancing
  • 1,461
  • 2
  • 11
  • 22
0

It seems like you're just starting with python. Your code is difficult to follow. There are many possible solutions for this problem but I'd advise to write simple code. Then worry about performance.

Readability first!

from typing import List


def peaks(lst: List[int]):
    found = 0

    for i, current_value in enumerate(lst):
        is_first_object = i == 0
        is_last_object = i == len(lst) - 1

        if is_first_object:
            previous_val = None
        else:
            previous_val = lst[i-1]

        if is_last_object:
            next_val = None
        else:
            next_val = lst[i+1]

        if is_first_object and not is_last_object:
            found += 1 if current_value > next_val else 0
        elif is_last_object and not is_first_object:
            found += 1 if current_value > previous_val else 0
        elif not is_last_object and not is_last_object:
            found += 1 if previous_val < current_value > next_val else 0

    return found


print(peaks([1, 2, 3]))
Tom Wojcik
  • 5,471
  • 4
  • 32
  • 44
  • Opinion-alert: I find your proposal massively less readable than schwobaseggl's answer. I appreciate that you try to annotate the types, but you forgot the return value `-> int`. – matheburg Dec 17 '20 at 13:28
  • I didn't _try_ to annotate, I added type annotation in places I find useful for this example. – Tom Wojcik Dec 17 '20 at 13:37
  • And yeah, schwobaseggl's solution is cool. But OP's code is very hard to follow. – Tom Wojcik Dec 17 '20 at 13:41
0

Alternative way.

Check the extremes then check the core, three by three:

def count_peaks(ls):
    res = [ls[0] > ls[1], ls[-1] > ls[-2]]
    for n in range(len(ls)-2):
        a, b, c = ls[n:n+3]
        res.insert(-1, b > max([a, c]))
    return res

Insert before the last element, to reflect the peaks position in the result.

So, in case of [1,2,1,2,4,5]:

count_peaks([1,2,1,2,4,5])
#=>[False, True, False, False, False, True]

Just return sum(res) instead to get the desired result: 2

iGian
  • 11,023
  • 3
  • 21
  • 36
0

you can add proxy value of first element and second last element, at front and last and then check the condition from the first element to second last element

def peaks(array):
        if len(array)<2:
            return 0
        array2 = [array[1]] + array + [array[-2]]
        
        count = sum([1 for i in range(len(array2)-2) if  array2[i+2]<array2[i+1] >array2[i]])
        return count
sahasrara62
  • 10,069
  • 3
  • 29
  • 44
0

try this one

def peaks(lst):
    num = 0
    leni = len(lst)
    
    for i in range(leni):
        if i == 0:
            if lst[i] > lst[i+1]:
                num = num + 1
        elif i == leni-1:
            if lst[i] > lst[i-1]:
                num = num + 1
        else:
            if lst[i] > lst[i-1] and lst[i] > lst[i+1]:
                num = num + 1
    return num
SAIKAT
  • 93
  • 1
  • 6
0

Counting of direct-neighbor peaks can be a performance-relevant task and the list of implementations is getting longer. Good reasons to compare the runtime. In a first run, I decided to go for one test set only. Obviously, the different implementations may reveal their strength at different lengths of lists. For instance, the optimal implementation of border value handling seems to be a candidate that depends on the list length.

Output:

count_peaks_schwobaseggl: elapsed time 1.44 s
count_peaks_sahasrara62: elapsed time 1.50 s
count_peaks_saikat: elapsed time 2.27 s
count_peaks_tom_wojcik: elapsed time 4.11 s
count_peaks_igian: elapsed time 3.65 s
count_peaks_cloud_balancing: elapsed time 1.86 s

Implementation:

import random
import time
from typing import List


def measure_runtime_in_s(func, test_lists):
    start = time.time()
    results = []
    for test_list in test_lists:
        max_cnt = func(test_list)
        results.append(max_cnt)
    return time.time() - start, results


def run_experiment(funcs, nlists=1000, len_range=(20, 10000), num_range=(-100, 100)):
    assert len(funcs) > 0
    # generate test data
    test_lists = [[random.randint(*num_range) for _ in range(random.randint(*len_range))]
                  for _ in range(nlists)]

    # run it for all implementations and check results
    _, ref_results = measure_runtime_in_s(funcs[0], test_lists)
    for func in funcs:
        failed = False
        time_in_s, results = measure_runtime_in_s(func, test_lists)
        if not all(result == ref_result for result, ref_result in zip(results, ref_results)):
            failed = True
        print(
            f"{func.__name__}: elapsed time {time_in_s:.2f} s"
            + (" (FAILED TESTS!)" if failed else ""))


def count_peaks_schwobaseggl(lst):
    lst = [float("-inf")] + lst + [float("-inf")]
    return sum(a < b > c for a, b, c in zip(lst, lst[1:], lst[2:]))


def count_peaks_sahasrara62(array):
    if len(array) < 2:
        return 0
    array2 = [array[1]] + array + [array[-2]]

    count = sum([1 for i in range(len(array2) - 2) if array2[i + 2] < array2[i + 1] > array2[i]])
    return count


def count_peaks_saikat(lst):
    num = 0
    leni = len(lst)

    for i in range(leni):
        if i == 0:
            if lst[i] > lst[i + 1]:
                num = num + 1
        elif i == leni - 1:
            if lst[i] > lst[i - 1]:
                num = num + 1
        else:
            if lst[i] > lst[i - 1] and lst[i] > lst[i + 1]:
                num = num + 1
    return num


def count_peaks_igian(ls):
    res = [ls[0] > ls[1], ls[-1] > ls[-2]]
    for n in range(len(ls)-2):
        a, b, c = ls[n:n+3]
        res.insert(-1, b > max([a, c]))
    return sum(res)  # < modified


def count_peaks_tom_wojcik(lst: List[int]):
    found = 0

    for i, current_value in enumerate(lst):
        is_first_object = i == 0
        is_last_object = i == len(lst) - 1

        if is_first_object:
            previous_val = None
        else:
            previous_val = lst[i-1]

        if is_last_object:
            next_val = None
        else:
            next_val = lst[i+1]

        if is_first_object and not is_last_object:
            found += 1 if current_value > next_val else 0
        elif is_last_object and not is_first_object:
            found += 1 if current_value > previous_val else 0
        elif not is_last_object and not is_last_object:
            found += 1 if previous_val < current_value > next_val else 0

    return found


def count_peaks_cloud_balancing(lst):
    lst_len = len(lst)
    hits = 0

    for i in range(1, lst_len - 1):
        num_to_test = lst[i]
        before = lst[i - 1]
        after = lst[i + 1]
        if num_to_test > before and num_to_test > after:
            hits += 1
    # This is for the case lst contains only 1 member and you want to count it as 1
    if lst_len == 1:
        hits += 1
    # For checking the edges
    if lst_len > 1:
        if lst[0] > lst[1]:
            hits += 1
        if lst[lst_len - 1] > lst[lst_len - 2]:
            hits += 1

    return hits


if __name__ == "__main__":
    run_experiment([
        count_peaks_schwobaseggl,
        count_peaks_sahasrara62,
        count_peaks_saikat,
        count_peaks_tom_wojcik,
        count_peaks_igian,
        count_peaks_cloud_balancing,
    ])
matheburg
  • 2,097
  • 1
  • 19
  • 46