関数を組み合わせて別の関数を作ると言う発想が無かったみたいです [日記]
DBから取り出したデータを表形式で表示するプログラムがありました。
この表は、指定した項目で並べ替えて表示する(ソート)機能があるのですが、並べ替えを行う度に DBからデータを取得し直すという動作をします。
作った人に「何故そんな無駄とも思えるDB問い合わせを繰り返すのか」と聞いてみたところ、『これが一番楽だ』と言う。
はて?
この並べ替え機能は、ソート対象となる項目が複数個選択できるようになっていました。所謂、第二ソートキーが指定できます。第二ソートキーどころか第三ソートキー、第四ソートキーと最大で項目数と同じだけソート項目が指定できました。
具体例で説明すると、
項目がA、B、C、Dの4つあったとして、項目Bが昇順になるように並べる。項目Bの値が同じなら項目Dで、BもDも同じなら項目Aを降順で。……。
という感じの指定が可能です。それも並べ替えを行う度に自由に設定可能です。
で、
この機能の実装を担当したプログラマが言うには「複数のキーでソートするプログラムは作れる。でも、ソートキーを複数個、任意に選ぶ仕様になっているから作れない」
出来ないはず無いんだけどねぇ…。
そこで編み出されたのが冒頭の並べ替えの度にDBからデータを取り直す方法。
- ソートキーとなる項目の項目名を優先順位の高い順にカンマ区切りで並べる。
- 表示するデータをDBから取得するSQLの order by 句に指定する。
- DBに問い合わせ、その結果を順番に表に出力する。
ソートはDBサーバにやらせてしまおうという発想です。
確かに、楽と言えば楽ですね。
最初に言った通りソートする度にDB問い合わせが発生するという無駄 について目を瞑れば。
(並べ替えをする度に、最新のデータに更新されると言う副作用的な利点はありますが)
しかし、どうして、苦肉の策っぽい感じで妙な方法を思い付いた人は「すごい画期的な方法を思い付いた」的な誇らしげな態度をとるんだろうな。(別人だけど過去の他の事例。ん?そう言えば同じ会社の人だ。その会社の芸風か?)
というわけで実際に作ってみました。VisualStudio 2012 の C++で。
template<typename T> class CombinatedLess { public: typedef std::function<bool(const T&, const T&)> Less; private: std::vector<Less> less_list_; public: CombinatedLess<T>& add(const Less& func) { less_list_.push_back(func); return *this; } bool operator()(const T& o1, const T& o2) const { for (const auto& f : less_list_) { if (f(o1, o2)) return true; if (f(o2, o1)) return false; } return false; } };
使い方は、こんな感じで
// 並べ替え対象のデータ struct Target { int a; int b; int c; int d; }; void sortTarget() { // 各項目ごとの比較関数 CombinatedLess<Target>::Less less_func_list[] = { [](const Target& o1, const Target& o2) { return o1.a < o2.a;}, [](const Target& o1, const Target& o2) { return o1.b < o2.b;}, [](const Target& o1, const Target& o2) { return o1.c < o2.c;}, [](const Target& o1, const Target& o2) { return o1.d < o2.d;}, }; // 並べ替えの優先順位で登録 auto pred = CombinatedLess<Target>() .add(less_func_list[1]) // 第1キー、B .add(less_func_list[3]) // 第2キー、D .add(std::not2(less_func_list[0])); // 第3キー、Aを逆順 // 並べ替えの対象となるサンプルデータ Target x[] = { {5,2,3,1}, {1,2,4,1}, {1,2,3,1}, }; // 並べ替え処理 std::sort(x, x + sizeof(x) / sizeof(x[0]), pred); }
比較関数を一旦配列に入れているのは、ユーザが選択したソートキーに対応する関数を取り出しやすくするためなんだけど(別に直接CombinatedLess
クラスのadd()メソッド
呼び出し時にラムダ式を書いても良いけど、項目が増えると、でっかいswitch文を書く羽目に)、ちょっと定義の部分が格好悪いかな?
比較対象が構造体だと限定すれば、こんな手もありですね。
#define DEF_LESS(T,F) [](const T & o1, const T & o2) { return o1.##F## < o2.##F##;} CombinatedLess<Target>::Less less_func_list[] = { DEF_LESS(Target, a), DEF_LESS(Target, b), DEF_LESS(Target, c), DEF_LESS(Target, d), };
しかし、実際に作ってみると、実に面白味のないコードだと実感します。
なんか、もっと、こう、C++らしいテンプレートを駆使しまくった変態的な(誉め言葉)コードにしたかったのですが、良く考えればテンプレートはコンパイル時に(静的に)どうにかする方法だから、この場合はちょっと違うか。
改めて、冒頭のプログラマに何が出来ないのか聞き取り調査をしてみたところ、『ソートキーの全ての組み合わせに対応した比較関数を作るのは(数が多過ぎて)無理』との回答。
関数を組み合わせて別の関数を作ると言う発想が無かったみたいです。
コメント 0