added amortized O(1) moving extrema

This commit is contained in:
Ahmet Inan 2020-10-12 19:00:20 +02:00
commit 52d2a5b0f4
3 changed files with 136 additions and 0 deletions

View file

@ -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.

72
movext.hh Normal file
View file

@ -0,0 +1,72 @@
/*
Moving Extrema
Copyright 2020 Ahmet Inan <inan@aicodix.de>
*/
#pragma once
#include "deque.hh"
namespace DSP {
template <typename TYPE, int NUM>
class MovMin
{
Deque<TYPE, NUM> 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 <typename TYPE, int NUM>
class MovMax
{
Deque<TYPE, NUM> 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();
}
};
}

57
tests/movext_test.cc Normal file
View file

@ -0,0 +1,57 @@
/*
Test for Moving Extrema
Copyright 2020 Ahmet Inan <inan@aicodix.de>
*/
#include <iostream>
#include <cassert>
#include <random>
#include <limits>
#include "swa.hh"
#include "movext.hh"
template <int SIZE>
void test()
{
struct Min { int operator () (int a, int b) { return std::min(a, b); } };
DSP::SWA<int, Min, SIZE> ref_min(std::numeric_limits<int>::max());
DSP::MovMin<int, SIZE> dut_min;
struct Max { int operator () (int a, int b) { return std::max(a, b); } };
DSP::SWA<int, Max, SIZE> ref_max(std::numeric_limits<int>::min());
DSP::MovMax<int, SIZE> dut_max;
std::random_device ran;
std::default_random_engine gen(ran());
std::uniform_int_distribution<int> dis(std::numeric_limits<int>::min(), std::numeric_limits<int>::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;
}