Я работаю в STL, как только заголовок DSP библиотека .Сегодня я начал оптимизировать некоторые функции, и мне пришлось реализовать функцию, которая оценивает SNR сигнала.
Если предположить, что мы работаем с сигналами с нулевым средним, дисперсияравна мощности, поэтому SNR - это отношение дисперсии сигнала и шума.
На практике мы не знаем шумовой сигнал, и он постоянно изменяется во времени.Обычно мы можем приблизить шум как разницу между вашим чистым сигналом и записанным:
Что касается кода библиотеки, то здесь уже естьСуществующий код для оценки дисперсии, поэтому в качестве первого подхода я сделал что-то вроде этого:
const auto functor = [](const value_type left, const value_type right) {
return left - right;
};
// Allocation of memory, O(N)
std::vector<float> noise(N);
std::transform(std::cbegin(recorded), std::cend(recorded), std::cbegin(signal), std::begin(noise), functor);
const auto var_ref = statistics::variance(std::cbegin(recorded), std::cend(recorded));
const auto var_noise = statistics::variance(std::cbegin(noise), std::cend(noise));
const auto SNR = converter::pow2db(var_ref / var_noise);
Это список требований реализации:
- Я не могу изменитьвходные данные (только для чтения).
- Я не могу использовать boost или какую-либо внешнюю библиотеку, просто стандарт C ++
- Я должен избегать временных выделений:
O(1)
пробел. - Iне может изменить сигнатуру дисперсионного сигнала.
Код нарушает один из них, так как выделяет память для шумового сигнала, O(N)
пробел.
Мне удалось найти решение, но я не совсем уверен, что оно самое элегантное.По сути, я использовал идею функции zip
в python и создал итератор, который выполняет итерацию по двум массивам одновременно и выполняет операцию при каждом обращении к ней (отложенная инициализация).
Это кодитератора:
template <class Iter1, class Iter2, class zip_functor>
class zip_iterator {
public:
using first_type = typename std::iterator_traits<Iter1>::value_type;
using second_type = typename std::iterator_traits<Iter2>::value_type;
using iterator_category = std::input_iterator_tag;
constexpr zip_iterator(Iter1 first1, Iter2 first2, zip_functor&& f) :
first1_(first1),
first2_(first2),
functor_(f) {
}
constexpr auto operator !=(const zip_iterator& other) const {
return this->first1_ != other.first1_ and
this->first2_ != other.first2_;
}
constexpr auto operator ==(const zip_iterator& other) const {
return this->first1_ == other.first1_ and
this->first2_ == other.first2_;
}
constexpr zip_iterator& operator++() {
first1_ = std::next(first1_);
first2_ = std::next(first2_);
return *this;
}
constexpr zip_iterator& operator--() {
first1_ = std::prev(first1_);
first2_ = std::prev(first2_);
return *this;
}
constexpr auto operator*() {
return functor_(*first1_, *first2_);
}
private:
Iter1 first1_;
Iter2 first2_;
zip_functor functor_;
};
При новой реализации код становится:
// Alias of the custom iterator
using first_iter = typename std::array<float, N>::const_iterator;
using second_iter = typename std::array<float, N>::const_iterator;
using zip_iter = zip_iterator<first_iter, second_iter,
std::function<float(float, float)>>;
// This performs the estimation of the noise in each sample
const auto functor = [](const auto left, const auto right) {
return left - right;
};
// Estimates the variance of the noise in O(1) space
auto start = zip_iter(std::cbegin(recorded), std::cbegin(signal), functor);
auto end = zip_iter(std::cend(recorded), std::cend(signal), functor);
const auto var_noise = edsp::statistics::variance(start, end);
const auto var_ref = edsp::statistics::variance(std::cbegin(recorded), std::cend(recorded));
const auto SNR = converter::pow2db(var_ref / var_noise);
Этот код соответствует требованию и, кажется, его легко использовать для других распространенных приложений при обработке сигналов,но все же я сомневаюсь, если это самый элегантный и надежный способ сделать это.
Поэтому мой вопрос:
Есть ли более элегантный способ решения этой проблемы?Любой эталонный образец или идиома, которая обрабатывает эту ситуацию?