In [1]:
import numpy as np
import pandas as pd

In [2]:
import doctest

In [3]:
def detect_heads(v, height) -> 'list of tuple (i,j,k)':
    """ pi < pj > pk, such that |pj - pi| > hight and |pj - pk| > hight
        i  <  j <  k, i and k the first index that makes a head
        
        >>> detect_heads([1,2,3,2,1], 1)
        [(1, 2, 3)]
        >>> detect_heads([1,2,3,2,1], 2)
        [(0, 2, 4)]
        >>> detect_heads([1,2,3,2,1], 3)
        []
        >>> detect_heads([5,4,3,2,1], 1)
        []
        >>> detect_heads([1,2,3,4,5], 1)
        []
        >>> detect_heads([1,2,3,4,3], 1)
        [(2, 3, 4)]
        >>> detect_heads([1,2,3,2,3,2,3], 1)
        [(1, 2, 3), (3, 4, 5)]
        >>> detect_heads([1,2,1,2,3,4,1], 3)
        [(0, 5, 6)]
    """
    assert height > 0
    if len(v) < 2:
        return []
    i, j, k = 0, None, None
    result = []
    for idx, p in enumerate(v):
        if j:
            if p <= v[j] - height:
                result.append((i, j, idx))
                i, j, k = idx, None, None
            elif p > v[j]:
                j = idx
                while p >= v[i+1] + height:
                    i = i + 1
            continue
        elif p < v[i]:
            i = idx
        elif p >= v[i] + height:
            j = idx
            while p >= v[i+1] + height:
                i = i + 1
                
    return result

doctest.run_docstring_examples(detect_heads, globals(), verbose=False)

In [4]:
def detect_vallies(v, height) -> 'list of tuple (i,j,k)':
    """ pi > pj < pk, such that |pj - pi| > hight and |pj - pk| > hight
        i  <  j <  k, i and k the first index that makes a valley
        
        >>> detect_vallies([3,2,1,2,3], 1)
        [(1, 2, 3)]
        >>> detect_vallies([3,2,1,2,3], 2)
        [(0, 2, 4)]
        >>> detect_vallies([3,2,1,2,3], 3)
        []
        >>> detect_vallies([5,4,3,2,1], 1)
        []
        >>> detect_vallies([1,2,3,4,5], 1)
        []
        >>> detect_vallies([4,3,2,1,2], 1)
        [(2, 3, 4)]
        >>> detect_vallies([3,2,1,2,1,2,1], 1)
        [(1, 2, 3), (3, 4, 5)]
        >>> detect_vallies([4,3,4,3,2,1,4], 3)
        [(0, 5, 6)]
    """
    return detect_heads([-i for i in v], height)

doctest.run_docstring_examples(detect_vallies, globals(), verbose=False)

In [59]:
def find_head_heights(v: list, peaks_idx: list) -> 'peak_heights':
    """
    Returns the height of each peaks_idx[i]
    Height is defined as v[i] - max(min_left, min_right)

    There's no checks on `peaks_idx`!
    
    >>> find_head_heights([1,2,3,2,1,2,3,2,1,2,3,2,1,2,3], [2,6,10])
    [2, 2, 2]
    >>> find_head_heights([1,9,3,6,2,7,5], [1,3,5])
    [6, 3, 2]
    >>> find_head_heights([1,9,1,8], [1])
    [8]
    >>> find_head_heights([1], [])
    []
    >>> find_head_heights([1,9,3,6,2,7,5], [1,3,6])
    Traceback (most recent call last):
    ValueError: Height <= 0 detected at i=2, probably the input `peaks_idx` is wrong
    """
    peak_heights =[float('nan') for i in peaks_idx]
    n = len(peaks_idx)
    for i in range(n):
        min_left = min(v[peaks_idx[i-1] if i > 0 else 0:peaks_idx[i]])
        min_right = min(v[peaks_idx[i]:peaks_idx[i+1] if i < n-1 else len(v)])
        peak_heights[i] = v[peaks_idx[i]] - max(min_right, min_left)
        if peak_heights[i] <= 0:
            raise ValueError(f'Height <= 0 detected at i={i}, probably the input `peaks_idx` is wrong')
    return peak_heights

doctest.run_docstring_examples(find_head_heights, globals(), verbose=False)

In [88]:
def detect_chain_of_heads(v, min_heights, max_heights=None, height=None) -> "list of head's center":
    """ Returns that last sequence that matches min_heights & max_heights 
    
    >>> detect_chain_of_heads([1,4,1,4,1,4], [3], height=1000)
    [3]
    >>> detect_chain_of_heads([1,4,1,4,1,4], [3,3], height=1000)
    [1, 3]
    >>> detect_chain_of_heads([1,9,1,8,1,7,1], [8, 7, 6], [9, 8, 7])
    [1, 3, 5]
    >>> detect_chain_of_heads([1,9,1,8,1,7,2], [8, 7, 6], [9, 8, 7])
    []
    """
    if max_heights is None:
        if height is None:
            raise ValueError('max_heights and height can\'t both be None')
        max_heights = [h + height for h in min_heights]
    assert len(min_heights) == len(max_heights)
    assert min(min_heights) > 0
    if not min_heights:
        return list()
    
    heads = [i[1] for i in detect_heads(v, min(min_heights))]
    head_heights = find_head_heights(v, heads)
    if len(heads) < len(min_heights):
        return []

    for i in range(len(heads)-1, len(min_heights) - 2, -1):
        match = True
        for k in range(len(min_heights)):
            z = len(min_heights) - 1 - k
            if not (min_heights[z] <= head_heights[i - k] <= max_heights[z]):
                match = False
                break
        if match:
            break
    return heads[i - len(min_heights) + 1:i + 1]
    
doctest.run_docstring_examples(detect_chain_of_heads, globals(), verbose=False)

Test Everything


In [ ]:
doctest.testmod(verbose=0)

In [89]:
for  _ in range(3):
    print('hi')


hi
hi
hi

In [92]:
k = 3
while k-=1 > 0:
    print('k')


  File "<ipython-input-92-346f79d19bac>", line 2
    while k-=1 > 0:
            ^
SyntaxError: invalid syntax

In [ ]: