さて、今回は前回より(ただ単に実装するだけなら)簡単な組み合わせの数の計算をするプログラムをTDDで実装してみようと思います。ちなみに今回はC++&Boost::Testです。
「ただ単に」と意味深に言ったのは、関数による実装だけでなくテンプレートクラスの特殊化を用いた実装も行おうと考えたためです。
できればMPLのメタ関数のようなstatic constなメンバ変数で結果の値を返したいと思ったのですが、組み合わせの数の途中計算でどうしても実数値が入ることがあります(入らないようにする方法はあるのかもしれませんが、私は知りません)。そのためstatic const な整数メンバ変数とすることができず、初期化を単純に行うことができません。
そこで、結果を保持するメンバ変数以外に計算を行うためのメンバ関数を定義しました。しかし、これがpublicにしなければならないため、なんだかメンバ変数を用意する意味が薄れてしまっています。
それ以上の解決法が思いつかなかったため、今回はそこまでで断念しています。よりよい方法があるという方は教えていただければ幸いです。(メタ関数にしなければいい、とか言うのは無しですよ。これは私がメタプログラミングやTDDを練習するために書いているコードですから。)
次のコードが関数内でforループを使って実装したコード。
#ifndef COMBINATION_H #define COMBINATION_H long combi(int n, int r); #endif
combination.h
#include "combination.h" long combi(int n, int r){ long p = 1; for(int k = 1; k <= r; k++){ p = p*(n-k+1)/k; } return p; }
combination.cpp
そしてこれが問題のテンプレートを用いたコード。
これだとv()も外部から参照できるのでどうにかしたいところ。
#ifndef TEMPLATECOMBINATION_H #define TEMPLATECOMBINATION_H template <int n, int r> struct combination { static double const v(); static double const value; }; template <int n, int r> const double combination<n, r>::value = combination<n, r>::v(); template <int n, int r> const double combination<n, r>::v() { return combination<n, r-1>::v() * (n-r+1) / r; } template <int n> struct combination <n, 0> { static double const v(); static double const value; }; template <int n> const double combination<n, 0>::value = combination<n, 0>::v(); template <int n> const double combination<n, 0>::v() { return 1.; } #endif
templateCombination.h
そして最後にテストコード。
#define BOOST_TEST_MODULE CONBINATION #include <boost/test/included/unit_test.hpp> #include "combination.h" #include "templateCombination.h" using namespace boost::unit_test; BOOST_AUTO_TEST_CASE( test_combi ) { BOOST_CHECK_EQUAL( combi(1, 0), 1 ); BOOST_CHECK_EQUAL( combi(1, 1), 1 ); BOOST_CHECK_EQUAL( combi(2, 1), 2 ); BOOST_CHECK_EQUAL( combi(4, 2), 6 ); BOOST_CHECK_EQUAL( combi(6, 6), 1 ); } BOOST_AUTO_TEST_CASE( test_combination ) { typedef combination<1, 0> combiOneZero; BOOST_CHECK_EQUAL( (combiOneZero::value), 1 ); typedef combination<1, 1> combiOneOne; BOOST_CHECK_EQUAL( (combiOneOne::value), 1 ); typedef combination<2, 1> combiTwoOne; BOOST_CHECK_EQUAL( (combiTwoOne::value), 2 ); typedef combination<4, 2> combiFourTwo; BOOST_CHECK_EQUAL( (combiFourTwo::value), 6 ); typedef combination<6, 6> combiSixSix; BOOST_CHECK_EQUAL( (combiSixSix::value), 1 ); }
combinationTest.cpp
まだまだ勉強しないといけないことは多いですね。TDDはできれば、例外補足などのテストもしたいところです。
追記:前回、テンプレートを使った際のv()メンバがpublicでないといけないという問題点があったのをfriendを使うことで公開範囲をずいぶんと限定することができることに気がついたので、そういう風に書きなおしてみます。
行うことはtemplateCombination.hのCombinationの宣言部分を修正すればいいだけです。
template <int n, int r> struct combination { private: friend combination<n, r+1>; static double const v(); public: static double const value; };
これでcombination<n, r+1>にのみcombination<n, r>::v()が公開されるので、ただ単にpublicにするよりはいいかと思います。