Testing Class Material

This commit is contained in:
Lisa Schwetlick 2024-08-26 13:54:13 +02:00
commit 05b1f6cdd5
85 changed files with 102796 additions and 0 deletions

70
.gitignore vendored Normal file
View file

@ -0,0 +1,70 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
.pytest_cache
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
#macos stuff
.DS_Store
# PyCharm
.idea/
# Jupyter
.ipynb_checkpoints/
# general
_archive/

6
LICENSE.txt Normal file
View file

@ -0,0 +1,6 @@
The material in this repository is released under the
CC Attribution-Share Alike 4.0 International
license.
Full license text available at
https://creativecommons.org/licenses/by-sa/4.0/

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# testing_debugging_profiling
Material for the class "Testing, debugging, profiling -- Python tools for building software"

BIN
continuous_integration.pdf Normal file

Binary file not shown.

BIN
continuous_integration.pptx Normal file

Binary file not shown.

BIN
debugging.pdf Normal file

Binary file not shown.

BIN
debugging.pptx Normal file

Binary file not shown.

Binary file not shown.

BIN
extra_slides/mocking.pptx Normal file

Binary file not shown.

View file

@ -0,0 +1,30 @@
>>> from smtplib import SMTP
>>> mock_smtp = Mock(spec=SMTP)
>>> isinstance(mock_smtp, SMTP)
True
>>> mock_smtp.<TAB>
mock_smtp.assert_any_call mock_smtp.attach_mock mock_smtp.call_args
mock_smtp.assert_called_once_with mock_smtp.auth mock_smtp.call_args_list
mock_smtp.assert_called_with mock_smtp.auth_cram_md5 mock_smtp.call_count >
mock_smtp.assert_has_calls mock_smtp.auth_login mock_smtp.called
mock_smtp.assert_not_called mock_smtp.auth_plain mock_smtp.close
>>> mock_smtp.bogus
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-17-4856e93b6e10> in <module>()
----> 1 mock_smtp.bogus
/Users/pberkes/miniconda3/envs/gnode/lib/python3.5/unittest/mock.py in __getattr__(self, name)
576 elif self._mock_methods is not None:
577 if name not in self._mock_methods or name in _all_magics:
--> 578 raise AttributeError("Mock object has no attribute %r" % name)
579 elif _is_magic(name):
580 raise AttributeError(name)
AttributeError: Mock object has no attribute 'bogus'

View file

@ -0,0 +1,64 @@
##### Mock basic
m = Mock()
m.x = 3
m.x
m.f(1,2,3)
m.whatever(3, key=2)
m
m.f
m.g
##### special attributes and assert methods
mock=Mock()
mock.f(2,3)
mock.f('a')
mock.f.called
mock.add.called
mock.f.called
mock.f.call_args
mock.f.call_count
mock.f.call_args_list
mock.f.assert_called_with('a')
mock.f.assert_called_once_with('a')
mock.f.assert_called_with(2, 3)
mock.f.assert_any_call(2, 3)
mock.f.assert_has_calls(['a', (2,3)])
#### return_value and side_effect
mock.g.return_value = 7
mock.g(32)
mock.g('r')
# useful to simulate file errors or server errors
mock.g.side_effect = Exception('Noooo')
mock.g(2)
mock.g.side_effect = lambda x: x.append(2)
a=[1]
mock.g(a)
a
mock.g.side_effect = [1, 4, 5]
mock.g()
mock.g()
mock.g()
mock.g()
#####
mock = Mock()
mock.f(3,4)
mock.g('a')
mock.f.a()
mock.method_calls
result = m.h(32)
result(1)
m.mock_calls
##### spec
from chaco.api import Plot
m2 = Mock(spec=Plot)
isinstance(m2, Plot)
m2.add
m2.add(12,'asdfasd')
m2.aaa

View file

@ -0,0 +1,41 @@
report_template = """
Report
======
The experiment was a {judgment}!
Let's do this again, with a bigger budget.
"""
def send_report(result, smtp):
if result > 0.5:
judgment = 'big success'
else:
judgment = 'total failure'
report = report_template.format(judgment=judgment)
smtp.send_message(
report,
from_addr='pony@magicpony.com',
to_addrs=['ferenc@magicpony.com'],
)
from unittest.mock import Mock
def test_send_report_success():
smtp = Mock()
send_report(0.6, smtp)
assert smtp.send_message.call_count == 1
pos_args, kw_args = smtp.send_message.call_args
message = pos_args[0]
assert 'success' in message
smtp.reset_mock()
send_report(0.4, smtp)
assert smtp.send_message.call_count == 1
args, kwargs = smtp.send_message.call_args
message = args[0]
assert 'failure' in message

View file

@ -0,0 +1,13 @@
def connect(address):
import time
time.sleep(5)
return '1'
def get_angle(address):
return 0.0
def set_angle(address, angle):
if angle < 0 or angle > 1.40:
raise IOError('Telescope jammed -- please call technical support')
return True

View file

@ -0,0 +1,30 @@
import telescope_driver
class TelescopeModel(object):
# Minimum safe elevation angle (see handbook).
MIN_ANGLE = 0.0
# Maximum safe elevation angle (see handbook).
MAX_ANGLE = 80.0
def __init__(self, address):
self.address = address
# Connect to telescope
self.connection = telescope_driver.connect(address)
# Get initial state of telescope.
self.current_angle = telescope_driver.get_angle(self.connection)
def set_elevation_angle(self, angle):
""" Set the elevation angle of the telescope (in rad).
If the angle is outside the range allowed by the manufacturer,
raise a ValueError.
"""
if angle < self.MIN_ANGLE or angle > self.MAX_ANGLE:
raise ValueError('Unsafe elevation angle: {}'.format(angle))
telescope_driver.set_angle(self.connection, angle)
self.current_angle = angle

View file

@ -0,0 +1,12 @@
import numpy as np
from py.test import raises
from telescope_model import TelescopeModel
def test_unsafe_elevation_angle():
telescope = TelescopeModel(address='10.2.1.1')
elevation_angle = np.pi / 2.0
with raises(ValueError):
telescope.set_elevation_angle(elevation_angle)

View file

@ -0,0 +1,29 @@
from unittest import mock
import numpy as np
from py.test import raises
from telescope_model import TelescopeModel
def test_unsafe_elevation_angle():
with mock.patch('telescope_model.telescope_driver'):
telescope = TelescopeModel(address='10.2.1.1')
elevation_angle = np.pi / 2.0
with raises(ValueError):
telescope.set_elevation_angle(elevation_angle)
def test_model_initialization():
connection_id = 'bogus_connection'
initial_angle = 1.23
with mock.patch('telescope_model.telescope_driver') as driver:
driver.connect.return_value = connection_id
driver.get_angle.return_value = initial_angle
telescope = TelescopeModel(address='10.2.1.1')
assert telescope.connection == connection_id
assert driver.connect.call_count == 1
assert telescope.current_angle == initial_angle

BIN
extra_slides/packaging.pptx Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

View file

@ -0,0 +1,22 @@
import os.path
import matplotlib.pyplot as plt
from scipy.ndimage import imread
from pkg_resources import resource_filename
from noiser.noise import white_noise
from noiser.utils import copy_image
def main():
path = resource_filename('noiser', os.path.join('images', 'baboon_kandinsky.png'))
print(path)
img = imread(path)
noisy = copy_image(white_noise(img, 20))
plt.imshow(noisy)
plt.draw()
plt.show()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,7 @@
import numpy as np
def white_noise(image, std):
noise = np.random.normal(scale=std, size=image.shape).astype(image.dtype)
noisy = image + noise
return noisy

View file

@ -0,0 +1,8 @@
from setuptools import setup, find_packages
setup(
name='Noiser',
version='1.0',
packages=find_packages(),
)

View file

@ -0,0 +1,24 @@
import numpy as np
from numpy.testing import assert_allclose
from noiser.noise import white_noise
def test_white_noise():
n_images, height, width = 201, 101, 102
dtype = np.float32
# Create ``n_images`` identical image.
base_image = np.random.rand(1, height, width, 3).astype(dtype) - 0.5
images = np.repeat(base_image, n_images, axis=0)
std = 0.13
noisy = white_noise(images, std=std)
# dtype and shape are preserved.
assert noisy.dtype == dtype
assert noisy.shape == images.shape
# Mean and std of noisy image match expectations.
assert_allclose(images.mean(0), base_image[0], atol=1e-4)
assert np.isclose((noisy - images).std(), std, atol=1e-4)

View file

@ -0,0 +1,14 @@
import numpy as np
from numpy.testing import assert_array_equal
from noiser.utils import copy_image
def test_copy_image():
height, width = 101, 102
dtype = np.float32
image = np.random.rand(height, width, 3).astype(dtype)
copy = copy_image(image)
assert_array_equal(copy, image)

View file

@ -0,0 +1,15 @@
import numpy as np
cimport numpy as np
def copy_image(np.ndarray img):
cdef int h = img.shape[0]
cdef int w = img.shape[1]
cdef int c = img.shape[2]
cdef np.ndarray copy = np.empty([h, w, c], dtype=img.dtype)
for i in range(h):
for j in range(w):
for k in range(c):
copy[i, j, k] = img[i, j, k]
return copy

View file

@ -0,0 +1,29 @@
from Cython.Build import cythonize
import numpy
from setuptools import setup, find_packages
from setuptools.extension import Extension
extensions = [
Extension(
'noiser.utils',
["noiser/utils.pyx"],
include_dirs=[numpy.get_include()],
)
]
setup(
name='Noiser',
version='1.0',
packages=find_packages(),
ext_modules=cythonize(extensions),
entry_points={
'console_scripts': [
'baboon=noiser.main:main',
]
},
package_data={
'noiser': ['images/*.png'],
}
)

View file

@ -0,0 +1,31 @@
import numpy as np
import theano
from theano import tensor as T
SLOPE = 3.1
INTERCEPT = -1.2
def residual_stats_theano(x, y):
expected = SLOPE * x + INTERCEPT
residuals = y - expected
return residuals.mean(), residuals.std()
x_var = T.vector()
y_var = T.vector()
residual_stats = theano.function(
inputs=[x_var, y_var],
outputs=residual_stats_theano(x_var, y_var),
allow_input_downcast=True,
)
if __name__ == '__main__':
x = np.linspace(-10, 10, 1000)
y = SLOPE * x + INTERCEPT
y += np.random.normal(loc=0.1, scale=0.5, size=x.shape)
mn, std = residual_stats(x, y)
print('Residual mean=', mn, ', std=', std)

View file

@ -0,0 +1,35 @@
import numpy as np
import theano
from theano import tensor as T
theano.config.profile_memory = True
theano.config.profile = True
SLOPE = 3.1
INTERCEPT = -1.2
def residual_stats_theano(x, y):
expected = SLOPE * x + INTERCEPT
residuals = y - expected
return residuals.mean(), residuals.std()
x_var = T.vector()
y_var = T.vector()
residual_stats = theano.function(
inputs=[x_var, y_var],
outputs=residual_stats_theano(x_var, y_var),
allow_input_downcast=True,
profile=True,
)
if __name__ == '__main__':
x = np.linspace(-10, 10, 1000)
y = SLOPE * x + INTERCEPT
y += np.random.normal(loc=0.1, scale=0.5, size=x.shape)
mn, std = residual_stats(x, y)
print('Residual mean=', mn, ', std=', std)

View file

@ -0,0 +1 @@
This is the directory used as a datastore by `file_datastore.py`.

View file

@ -0,0 +1,113 @@
import os
class FileDatastore:
"""Datastore based on a regular file system.
Parameters
----------
base_path: str
Filesystem path at which the data store is based.
"""
def __init__(self, base_path):
if not os.path.exists(base_path):
raise FileNotFoundError(f'Base path {base_path} does not exist')
if not os.path.isdir(base_path):
raise NotADirectoryError(f'Base path {base_path} exists but is not a directory')
self.base_path = base_path
def open(self, path, mode):
""" Open a file-like object
Parameters
----------
path: str
Path relative to the root of the data store.
mode: str
Specifies the mode in which the file is opened.
Returns
-------
IO[Any]
An open file-like object.
"""
path = os.path.join(self.base_path, path)
return open(path, mode)
def read(self, path):
""" Read a sequence of bytes from the data store.
Parameters
----------
path: str
Path relative to the root of the data store.
Returns
-------
bytes
The sequence of bytes read from `path`.
"""
with self.open(path, 'rb') as f:
data = f.read()
return data
def write(self, path, data) -> None:
""" Write a sequence of bytes to the data store.
`path` contains the path relative to the root of the data store, including the name
of the file to be created. If a file already exists at `path`, it is overwritten.
Intermediate directories that do not exist will be created.
Parameters
----------
path: str
Path relative to the root of the data store.
data: bytes
Sequence of bytes to write at `path`.
"""
path = os.path.join(self.base_path, path)
self.makedirs(os.path.dirname(path))
with self.open(path, 'wb') as f:
f.write(data)
def exists(self, path):
""" Returns True if the file exists.
Parameters
----------
path: str
Path relative to the root of the data store.
Returns
-------
bool
True if the file exists, false otherwise
"""
complete_path = os.path.join(self.base_path, path)
return os.path.exists(complete_path)
def makedirs(self, path):
""" Creates the specified directory if needed.
If the directories already exist, the method does not do anything.
Parameters
----------
path: str
Path relative to the root of the data store.
"""
complete_path = os.path.join(self.base_path, path)
os.makedirs(complete_path, exist_ok=True)
if __name__ == '__main__':
data = b'A test! 012'
datastore = FileDatastore(base_path='./datastore')
datastore.write('a/mydata.bin', data)
# This should pass!
# The code works correctly if `base_path` is an absolute path :-(
assert os.path.exists('./datastore/a/mydata.bin')

View file

@ -0,0 +1,65 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "30f3f04f",
"metadata": {},
"outputs": [],
"source": [
"from debugger_example import sum_over_difference"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3632cacb",
"metadata": {},
"outputs": [],
"source": [
"result = sum_over_difference(7, 5)\n",
"print(result)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "02577028",
"metadata": {},
"outputs": [],
"source": [
"result = sum_over_difference(12, 12)\n",
"print(result)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58fc58c0",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View file

@ -0,0 +1,23 @@
def add(arg1, arg2):
return arg1 + arg2
def sub(arg1, arg2):
return arg1 - arg2
def div(arg1, arg2):
return arg1 / arg2
def sum_over_difference(arg1, arg2):
"""Compute sum of arguments over difference of arguments."""
arg_sum = add(arg1, arg2)
arg_diff = sub(arg1, arg2)
sum_over_diff = div(arg_sum, arg_diff)
return sum_over_diff
if __name__ == '__main__':
result = sum_over_difference(7, 5)
print(result)

View file

@ -0,0 +1,33 @@
""" Compute the factorial of a set of numbers stored in a file. """
def factorial(n):
if n == 0:
return 1
else:
return factorial(n-1) * n
def read_data(filename):
numbers = []
with open(filename, 'r') as f:
for line in f:
number = int(line)
numbers.append(number)
return numbers
def compute_factorials_for_list(numbers):
factorials = []
for number in numbers:
result = factorial(number)
factorials.append(result)
return factorials
def main():
numbers = read_data('numbers.txt')
factorials = compute_factorials_for_list(numbers)
if __name__ == '__main__':
main()

100000
hands_on/factorial/numbers.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
""" Tests for the factorial function. """
from factorial import factorial
def test_factorial():
factorial_cases = [(1, 1),
(0, 1),
(5, 2*3*4*5),
(30, 265252859812191058636308480000000)]
for n, fact_n in factorial_cases:
assert factorial(n) == fact_n

8
hands_on/first/first.py Normal file
View file

@ -0,0 +1,8 @@
def times_3(x):
""" Multiply x by 3.
Parameters
----------
x : The item to multiply by 3.
"""
return x * 3

View file

@ -0,0 +1,19 @@
from first import times_3
def test_times_3_integer():
value = 7
expected = 21
result = times_3(value)
assert result == expected
def test_times_3_string():
value = 'wow'
expected = 'wowwowwow'
result = times_3(value)
assert result == expected

View file

@ -0,0 +1,8 @@
def times_3(x):
""" Multiply x by 3.
Parameters
----------
x : The item to multiply by 3.
"""
return x * 3

View file

@ -0,0 +1,10 @@
def find_maxima(x):
"""Find local maxima of x.
Input arguments:
x -- 1D list of real numbers
Output:
idx -- list of indices of the local maxima in x
"""
return []

View file

@ -0,0 +1,10 @@
def find_maxima(x):
"""Find local maxima of x.
Input arguments:
x -- 1D list of real numbers
Output:
idx -- list of indices of the local maxima in x
"""
return []

View file

@ -0,0 +1,30 @@
from local_maxima import find_maxima
def test_find_maxima():
values = [1, 3, -2, 0, 2, 1]
expected = [1, 4]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_edges():
values = [4, 2, 1, 3, 1, 5]
expected = [0, 3, 5]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_empty():
values = []
expected = []
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau():
raise Exception('not yet implemented')
def test_find_maxima_not_a_plateau():
raise Exception('not yet implemented')

View file

@ -0,0 +1,49 @@
def find_maxima(x):
"""Find local maxima of x.
Input arguments:
x -- 1D list of real numbers
Output:
idx -- list of indices of the local maxima in x
"""
maxima = []
len_x = len(x)
if len_x == 0:
return maxima
maxima = check_first_element(x, maxima)
maxima = check_last_element(x, maxima)
# Check numbers in between
i = 1
while i < len_x - 1:
if x[i] > x[i - 1]:
# We have found a potential maximum or start of a plateau
plateau_start = i
while i < len_x - 1 and x[i] == x[i + 1]:
i += 1
plateau_end = i
if x[plateau_end] > x[plateau_end + 1]:
maxima.append(plateau_start)
i += 1
return maxima
def check_first_element(x, maxima):
if x[0] > x[1]:
maxima.append(0)
return maxima
def check_last_element(x, maxima):
if x[-1] > x[-2]:
maxima.append(len(x) - 1)
return maxima
if __name__ == "__main__":
result = find_maxima([1, 2, 2, 1])
print(result)

View file

@ -0,0 +1,107 @@
import numpy as np
import pytest
from local_maxima import find_maxima
def test_find_maxima():
values = [1, 3, -2, 0, 2, 1]
expected = [1, 4]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_edges():
values = [4, 2, 1, 0, 1, 5]
expected = [0, 5]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_empty():
values = []
expected = []
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau():
values = [1, 2, 2, 1]
expected = [1]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_not_a_plateau():
values = [1, 2, 2, 3, 1]
expected = [3]
maxima = find_maxima(values)
assert maxima == expected
# the tests below here fail, can you get them to pass?
def test_find_maxima_correct_order():
# TASK: get this test to pass
values = [2, 1, 5, 1, 9]
expected = [0, 2, 4]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_one_value():
# TASK: get this test to pass
values = [1]
expected = [0]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_long_plateau():
# TASK: Change the implementation for when there is a plateau
# for uneven plateau length, return the middle index, e.g. [1, 2, *2*, 2, 1] --> [2]
# for even plateau length, return the index left of the middle e.g. [1, 2, *2*, 2, 2, 1] --> [2]
values = [1, 2, 2, 2, 2, 2, 1, 8, 0]
expected = [3, 7]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau_at_end():
# TASK: make sure plateaus at the end are handled properly (see test above)
values = [1, 2, 2]
expected = [1]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau_at_start():
# TASK: make sure plateaus at the start are handled properly (see test above)
values = [1, 1, 0, 0]
expected = [0]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_all_same_values():
# TASK: implement a check for lists where there is no local maximum
values = [1, 1]
expected = [0]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_letters():
# stings can be evaluated with > and <, who knew!
# Find an easy solution so that both "t"s are recognised as local maxima
values = ["T", "e", "s", "t", "s", "!"]
expected = [0, 3]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_new_inputs_to_make_current_function_fail():
# should you actually be done with all tests, then you can think of other cases where the current function fails
# and write tests for them and fix them
assert False

View file

@ -0,0 +1 @@
This exercise is in a separate repo!

View file

@ -0,0 +1,22 @@
import numpy as np
def test_equality():
x = np.array([1, 1])
y = np.array([2, 2])
z = np.array([3, 3])
assert x + y == z
def test_equality_with_nan():
x = np.array([1, np.nan])
y = np.array([2, np.nan])
z = np.array([3, np.nan])
assert x + y == z
def test_allclose_with_nan():
x = np.array([1.1, np.nan])
y = np.array([2.2, np.nan])
z = np.array([3.3, np.nan])
assert x + y == z

View file

@ -0,0 +1,8 @@
def times_3(x):
""" Multiply x by 3.
Parameters
----------
x : The item to multiply by 3.
"""
return x * 3

View file

@ -0,0 +1,28 @@
from first import times_3
def test_times_3_integer():
value = 7
expected = 21
result = times_3(value)
assert result == expected
def test_times_3_string():
value = 'wow'
expected = 'wowwowwow'
result = times_3(value)
assert result == expected
def test_times_3_list():
value = [1]
expected = [1, 1, 1]
result = times_3(value)
assert result == expected

View file

@ -0,0 +1,44 @@
def find_maxima(x):
"""Find local maxima of x.
Example:
>>> x = [1, 3, -2, 0, 2, 1]
>>> find_maxima(x)
[1, 4]
If in a local maximum several elements have the same value,
return the left-most index.
Example:
>>> x = [1, 2, 2, 1]
>>> find_maxima(x)
[1]
Input arguments:
x -- 1D list of real numbers
Output:
idx -- list of indices of the local maxima in x
"""
idx = []
up = False
down = False
for i in range(len(x)):
if i == 0 or x[i-1] < x[i]:
up = True
up_idx = i
elif x[i-1] > x[i]:
up = False
# if x[i-1] == x[i], no change
if i+1 == len(x) or x[i+1] < x[i]:
down = True
elif x[i+1] > x[i]:
down = False
if up and down:
idx.append(up_idx)
return idx

View file

@ -0,0 +1,36 @@
from local_maxima import find_maxima
def test_find_maxima():
values = [1, 3, -2, 0, 2, 1]
expected = [1, 4]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_edges():
values = [4, 2, 1, 3, 1, 5]
expected = [0, 3, 5]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_empty():
values = []
expected = []
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau():
values = [1, 2, 2, 1]
expected = [1]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_not_a_plateau():
values = [1, 2, 2, 3, 1]
expected = [3]
maxima = find_maxima(values)
assert maxima == expected

View file

@ -0,0 +1,67 @@
import math
import numpy as np
def find_maxima(x):
"""Find local maxima of x.
Input arguments:
x -- 1D list of real numbers
Output:
idx -- list of indices of the local maxima in x
"""
maxima = []
len_x = len(x)
if len_x == 0:
return maxima
elif len_x == 1:
return [0]
# additional checks
if np.all([isinstance(item, str) for item in x]):
x = [item.lower() for item in x]
if np.all([item == x[0] for item in x]):
return [0]
maxima = check_first_element(x, maxima)
# Check numbers in between
i = 1
while i < len_x - 1:
if x[i] >= x[i - 1]:
# We have found a potential maximum or start of a plateau
# breakpoint()
if i == 1 and x[i] == x[i - 1]:
plateau_start = i - 1
else:
plateau_start = i
while i < len_x - 1 and x[i] == x[i + 1]:
i += 1
plateau_end = i
if plateau_end == len_x - 1:
maxima.append((plateau_end + plateau_start) // 2)
elif x[plateau_end] > x[plateau_end + 1]:
maxima.append((plateau_end + plateau_start) // 2)
i += 1
maxima = check_last_element(x, maxima)
return maxima
def check_first_element(x, maxima):
if x[0] > x[1]:
maxima.append(0)
return maxima
def check_last_element(x, maxima):
if x[-1] > x[-2]:
maxima.append(len(x) - 1)
return maxima
if __name__ == "__main__":
# result = find_maxima([1, 3, -2, 0, 2, 1])
result = find_maxima([1, 2, 2, 1])
print(result)

View file

@ -0,0 +1,97 @@
import numpy as np
import pytest
from local_maxima_solution import find_maxima
def test_find_maxima():
values = [1, 3, -2, 0, 2, 1]
expected = [1, 4]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_edges():
values = [4, 2, 1, 0, 1, 5]
expected = [0, 5]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_empty():
values = []
expected = []
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau():
values = [1, 2, 2, 1]
expected = [1]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_not_a_plateau():
values = [1, 2, 2, 3, 1]
expected = [3]
maxima = find_maxima(values)
assert maxima == expected
# the tests below here fail, can you get them to pass?
def test_find_maxima_correct_order():
values = [2, 1, 5, 1, 9]
expected = [0, 2, 4]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_one_value():
values = [1]
expected = [0]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_long_plateau():
values = [1, 2, 2, 2, 2, 2, 1, 8, 0]
expected = [3, 7]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau_at_end():
values = [1, 2, 2]
expected = [1]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_plateau_at_start():
values = [1, 1, 0, 0]
expected = [0]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_all_same_values():
values = [1, 1]
expected = [0]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_letters():
values = ["T", "e", "s", "t", "s", "!"]
expected = [0, 3]
maxima = find_maxima(values)
assert maxima == expected
def test_find_maxima_new_inputs_to_make_current_function_fail():
# should you actually be done with all tests, then you can think of other cases where the current function fails
# and write tests for them and fix them
assert True