お題
私は日常的な作業には Scheme を利用することがほとんどなのだけれど、 C++ は好きな言語のひとつで Qiita 内でも C++ タグが付いている記事は結構見ている。 そんなときに、お題としてちょうど良いと思う記事があった。
C++ > 複数回条件成立かどうかの検知 > 対象は50項目
方針
STL やテンプレートなどを使うつもりがないということなので、私はあえて逆に私が知る限りの C++11 の機能を活用して実装してみることにする。 私が C++ をそれほど使い熟せているとも思ってはいないけれど、 C++11 の機能を積極的に使うことで効果的な抽象化が出来るということを示したい。
一方で、 C++ の設計者である Bjarne Stroustrup は著書「C++ の設計と進化」の中で C++ は段階的な学習が可能だと述べていることも紹介しておく。 C++ は使えるようになるまで学習のためだけに時間をあてるのではなく、使いながら学習していくことが出来るように考慮されている。 ほどほどに知っていればほどほどなりに実用できるということだし、少しばかり便利な C (Better C) として使うことも否定していない。 C++ の機能には無くてもよさそうな、しかしあると間違いなく便利な機能がたくさんあるのは確かなので、そういった機能の積み重ねでどれくらい差があるのか感じてもらえればと思う。
実装
#ifndef CONTINUOUS_HEADER
#define CONTINUOUS_HEADER
#include <functional>
#include <deque>
#include <iostream>
template<class T>
class continuous {
private:
const int count_limit;
int establish_count;
std::deque<T> data;
std::function<bool(T)> checker;
public:
using value_type = T;
using const_iterator = typename std::deque<T>::const_iterator;
continuous(int count_limit, std::function<bool(T)> predicate);
void push(T datum);
bool is_established(void) const;
void clear(void);
const_iterator begin(void) const;
const_iterator end(void) const;
};
template<class T>
continuous<T>::continuous(int count_limit, std::function<bool(T)> predicate)
: count_limit(count_limit),
checker(predicate),
establish_count(0) {
}
template<class T>
void continuous<T>::push(const T datum) {
data.push_back(datum);
if(data.size() > count_limit) data.pop_front();
if(checker(datum)) {
establish_count++;
} else {
establish_count = 0;
}
}
template<class T>
void continuous<T>::clear(void) {
establish_count = 0;
data.clear();
}
template<class T>
bool continuous<T>::is_established(void) const {
return establish_count >= count_limit;
}
template<class T>
typename continuous<T>::const_iterator continuous<T>::begin(void) const {
return data.cbegin();
}
template<class T>
typename continuous<T>::const_iterator continuous<T>::end(void) const {
return data.cend();
}
template<class T>
std::ostream& operator<<(std::ostream& os, const continuous<T>& c) {
os << "{";
if(c.begin() != c.end()) {
os << *c.begin();
for(auto iter = c.begin()+1, e = c.end(); iter != e; ++iter) {
os << ", " << *iter;
}
}
os << "}";
return os;
}
#endif
元記事の方針との具体的な差
- 命名規則を標準ライブラリの雰囲気に合わせた
- 成立条件はコンストラクタで指定し、以後は変更することは出来ない
- テンプレートを活用することによって、型ごとではなくひとつの定義で済ませている
- 成立条件の指定は関数オブジェクト (または関数ポインタ) で行ない、この場合に専用の指定子を導入しなかった
利用例兼テストケース
#include "continuous.h"
#include <iostream>
#include <cassert>
void test_inrange(void) {
continuous<double>
continuous_inrange(3, [](double x){return x>2.7182 && x<3.1415;});
for(auto x: {4.0, 3.0, 4.0, 3.0}) continuous_inrange.push(x);
assert(continuous_inrange.is_established() == false);
std::cout << "non-establish data = " << continuous_inrange << std::endl;
for(auto x: {3.0, 3.0, 3.0}) continuous_inrange.push(x);
assert(continuous_inrange.is_established() == true);
std::cout << "establish data = " << continuous_inrange << std::endl;
}
void test_outrange(void) {
continuous<double>
continuous_outrange(3, [](double x){return !(x>2.7182 && x<3.1415);});
for(auto x: {2.0, 4.0, 2.0, 3.0}) continuous_outrange.push(x);
assert(continuous_outrange.is_established() == false);
std::cout << "non-establish data = " << continuous_outrange << std::endl;
for(auto x: {2.0, 4.0, 2.0}) continuous_outrange.push(x);
assert(continuous_outrange.is_established() == true);
std::cout << "establish data = " << continuous_outrange << std::endl;
}
void test_even(void) {
continuous<int>
continuous_even(3, [](int x){return !(x%2);});
for(auto x: {2, 3, 2, 2}) continuous_even.push(x);
assert(continuous_even.is_established() == false);
std::cout << "non-establish data = " << continuous_even << std::endl;
for(auto x: {2, 3, 5, 4, 2, 8}) continuous_even.push(x);
assert(continuous_even.is_established() == true);
std::cout << "establish data = " << continuous_even << std::endl;
}
int main(void) {
test_inrange();
test_outrange();
test_even();
std::cout << "Pass all" << std::endl;
return 0;
}