From 52d2a5b0f47ad98a57ba18d5177e16bcc9a20bc0 Mon Sep 17 00:00:00 2001 From: Ahmet Inan Date: Mon, 12 Oct 2020 19:00:20 +0200 Subject: [PATCH] added amortized O(1) moving extrema --- README.md | 7 +++++ movext.hh | 72 ++++++++++++++++++++++++++++++++++++++++++++ tests/movext_test.cc | 57 +++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 movext.hh create mode 100644 tests/movext_test.cc diff --git a/README.md b/README.md index 56ae9aa..789b9e0 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,13 @@ The [simple moving average](https://en.wikipedia.org/wiki/Moving_average#Simple_ The sliding window accelerator uses a tree and only updates nodes that depend on the new input value for the pairwise reduction. +### [movext.hh](movext.hh) + +[Amortized](https://en.wikipedia.org/wiki/Amortized_analysis) [O(1)](https://en.wikipedia.org/wiki/Computational_complexity_theory) moving [extrema](https://en.wikipedia.org/wiki/Maxima_and_minima) implementations of: + +* Moving Minimum +* Moving Maximum + ### [bip_buffer.hh](bip_buffer.hh) The [Bip buffer](https://en.wikipedia.org/wiki/Circular_buffer#Fixed-length-element_and_contiguous-block_circular_buffer) provides contiguous block access to the last N value stored in a circular buffer. diff --git a/movext.hh b/movext.hh new file mode 100644 index 0000000..4fffbd6 --- /dev/null +++ b/movext.hh @@ -0,0 +1,72 @@ +/* +Moving Extrema + +Copyright 2020 Ahmet Inan +*/ + +#pragma once + +#include "deque.hh" + +namespace DSP { + +template +class MovMin +{ + Deque window, dispenser, refill; +public: + TYPE operator () (TYPE input) + { + if (window.full()) { + if (window.front() == dispenser.front()) + dispenser.pop_front(); + window.pop_front(); + } + window.push_back(input); + + while (!refill.empty() && input < refill.front()) + refill.pop_front(); + refill.push_front(input); + + if (dispenser.empty()) { + while (!refill.empty()) { + dispenser.push_front(refill.front()); + refill.pop_front(); + } + return dispenser.front(); + } + return dispenser.front() < refill.back() ? dispenser.front() : refill.back(); + } +}; + +template +class MovMax +{ + Deque window, dispenser, refill; +public: + TYPE operator () (TYPE input) + { + if (window.full()) { + if (window.front() == dispenser.front()) + dispenser.pop_front(); + window.pop_front(); + } + window.push_back(input); + + while (!refill.empty() && input > refill.front()) + refill.pop_front(); + refill.push_front(input); + + if (dispenser.empty()) { + while (!refill.empty()) { + dispenser.push_front(refill.front()); + refill.pop_front(); + } + return dispenser.front(); + } + return dispenser.front() > refill.back() ? dispenser.front() : refill.back(); + } +}; + +} + diff --git a/tests/movext_test.cc b/tests/movext_test.cc new file mode 100644 index 0000000..0f35baa --- /dev/null +++ b/tests/movext_test.cc @@ -0,0 +1,57 @@ +/* +Test for Moving Extrema + +Copyright 2020 Ahmet Inan +*/ + +#include +#include +#include +#include +#include "swa.hh" +#include "movext.hh" + +template +void test() +{ + struct Min { int operator () (int a, int b) { return std::min(a, b); } }; + DSP::SWA ref_min(std::numeric_limits::max()); + DSP::MovMin dut_min; + struct Max { int operator () (int a, int b) { return std::max(a, b); } }; + DSP::SWA ref_max(std::numeric_limits::min()); + DSP::MovMax dut_max; + std::random_device ran; + std::default_random_engine gen(ran()); + std::uniform_int_distribution dis(std::numeric_limits::min(), std::numeric_limits::max()); + for (int loop = 0; loop < 1000000; ++loop) { + int val = dis(gen); + assert(ref_min(val) == dut_min(val)); + assert(ref_max(val) == dut_max(val)); + } +} + +int main() +{ + test<1>(); + test<2>(); + test<3>(); + test<4>(); + test<5>(); + test<6>(); + test<7>(); + test<8>(); + test<9>(); + test<10>(); + test<12>(); + test<13>(); + test<14>(); + test<15>(); + test<16>(); + test<42>(); + test<123>(); + test<4567>(); + + std::cerr << "Moving extrema test passed!" << std::endl; + return 0; +} +