C++でC#のクエリのようなものを作る。
最近、C#を勉強していてlinqのクエリ(?)がすごく便利だと感じたので、C++でそれっぽいのを作ろう。
進捗は
- select
- select_with_index
- select_unzip
- where
- where_with_index
- skip
- take
- zip
を作成。
使い方は下記の通り。
コードは
GitHub - nishiba/query: c++ code like c# query
int main() { std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7 }; std::vector<int> y = { 0, 1, 2, 3, 4, 5, 6, 7 }; for (auto& x : where(v, [](int x) -> bool {return (x % 2) == 0; })) { std::cout << x << std::endl; } std::cout << std::endl; for (auto& x : where_with_index(v, [](int x, std::size_t index) -> bool {return (index < 4); })) { std::cout << x << std::endl; } std::cout << std::endl; for (auto& x : skip(v, 1).where([](int x) {return x < 6; })) { std::cout << x << std::endl; } std::cout << std::endl; for (auto x : skip(v, 1).select([](int x) {return x < 6; })) { std::cout << x << std::endl; } std::cout << std::endl; for (auto& x : zip(v, y).take(2)) { std::cout << x.get<0>() << "," << x.get<1>() << std::endl; } std::cout << std::endl; for (auto&& x : select_unzip(zip(v, y), [](auto& x, auto& y) {return x + y; })) { std::cout << x << std::endl; } std::cout << std::endl; for (auto& x : skip(v, 1).take(3)) { std::cout << x << std::endl; } return 0; }
C#からExcelを開く
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Office.Interop; using Excel = Microsoft.Office.Interop.Excel; using System.IO; using System.Runtime.InteropServices; namespace excel_starter { class Program { static void Main(string[] args) { try { var app = new Excel.Application(); // Make the object visible. app.Visible = true; // Load My Addin //app.RegisterXLL(Directory.GetCurrentDirectory() + "\\addin.xll"); // Open My Workbook Excel.Workbooks wbks = app.Workbooks; wbks.Open(Directory.GetCurrentDirectory() + "\\addin1.xlam"); Excel.Workbook wbk = wbks.Open(Directory.GetCurrentDirectory() + "\\example.xlsx"); // Release COM-Objects Marshal.ReleaseComObject(wbk); Marshal.ReleaseComObject(wbks); Marshal.ReleaseComObject(app); } catch (Exception e) { Console.WriteLine(e.Message); } } } }
Factory&Builderパターン再考
インスタンスの生成方法について再考。
したいこと
- Dataの商品IDに応じて、違うルールでShelfを作りたい。
- 新しい商品を追加も容易にしたい。追加や変更するクラス、箇所を極力小さくしたい。
int main() { ProductId("c1").procedure<Screw1Builder, BoardBuilder, ShelfBuilder>(); ProductId("c2").procedure<Screw2Builder, BoardBuilder, ShelfBuilder>(); Shelf s1 = ShelfFactory::create(Data("c1")); Shelf s2 = ShelfFactory::create(Data("c2")); return 0; }
上記では、IDによって違うScrewを使うように実行時にfactoryに登録している。
procedureは可変長引数となっており、手順を増やすことも容易。
ShelfFactoryはstd::functionを保持しているので、Screw1Builderなどに依存していない。
Screw1Builder等も同じBaseを継承する必要もない。
class Screw {}; class Board {}; class Shelf {}; struct WoreHouse { Screw screw; Board bord; Shelf shelf; }; class ShelfFactory { public: class ProcedureCreator { friend ShelfFactory; public: ProcedureCreator& next(const std::function<void(ShelfFactory&)>& f) { _p.push_back(f); return *this; } private: ProcedureCreator(std::list<std::function<void(ShelfFactory&)>>& p) : _p(p) {} std::list<std::function<void(ShelfFactory&)>>& _p; }; static ProcedureCreator makeProcedure(const std::string& code) { instance()._procedures[code].clear(); return ProcedureCreator(instance()._procedures[code]); } static Shelf create(const Data& d) { ShelfFactory& f = instance(); f._data = d; for (auto& p : f._procedures[f._data.id]) p(f); return f._worehous.shelf; } Data _data; WoreHouse _worehous; std::map<std::string, std::list<std::function<void(ShelfFactory&)>>> _procedures; private: static ShelfFactory& instance() { static ShelfFactory _f; return _f; } ShelfFactory() {} ShelfFactory(const ShelfFactory&) = default; ShelfFactory& operator =(const ShelfFactory&) = default; }; class Screw1Builder { public: void operator()(ShelfFactory& f) { std::cout << "Screw1Builder" << std::endl; f._worehous.screw = Screw(); } }; class Screw2Builder { public: void operator()(ShelfFactory& f) { std::cout << "Screw2Builder" << std::endl; f._worehous.screw = Screw(); } }; class BoardBuilder { public: void operator()(ShelfFactory& f) { std::cout << "BoardBuilder" << std::endl; f._worehous.bord = Board(); } }; class ShelfBuilder { public: void operator()(ShelfFactory& f) { std::cout << "ShelfBuilder" << std::endl; f._worehous.shelf = Shelf(); } }; template <typename...> struct ProcedureMaker { static void apply(ShelfFactory::ProcedureCreator& p) { } }; template <typename T, typename... Args> struct ProcedureMaker<T, Args...> { static void apply(ShelfFactory::ProcedureCreator& p) { p.next(T()); ProcedureMaker<Args...>::apply(p); } }; class ProductId { public: ProductId(std::string id) : _id(id) {} template <typename ... Args> void procedure() const { ProcedureMaker<Args...>::apply(ShelfFactory::makeProcedure(_id)); } private: std::string _id; };
新しい工程を増やす場合、新しい部品(Glass)とその作り方(GlassBuilder)を新たに作成。
WoreHouseに新しい部品のメンバ変数を追加。
あとはmainで製造手順を追加すればよい。
変更箇所の影響がかなり限定的にできる。
class Glass {}; struct WoreHouse { Screw screw; Board bord; Shelf shelf; Glass glass; //追加 }; class GlassBuilder { public: void operator()(ShelfFactory& f) { std::cout << "GlassBuilder" << std::endl; f._worehous.glass = Glass(); } }; int main() { ProductId("c3").procedure<GlassBuilder, Screw2Builder, BoardBuilder, ShelfBuilder>(); Shelf s3 = ShelfFactory::create(Data("c3")); return 0; }
Decolator, Mix-Inのような継承関係
Decolatorパターンの場合、内部で持っているコンポーネントの振る舞いが分かりにくい。Mix-Inの場合、プロパティとなるClassがtemplate classとなってしまって、複雑になる。
そこで、状況によってはあ下記のような継承もありかな。
- RuleBaseはルール f : double -> doubleを複数保持する。
- RuleBase::apply(double x)は、登録された順にルールをxに適用する。
- RuleAは、ルールx + 1.0をRuleBaseに登録する。
- RuleBは、ルールx * 3.0をRuleBaseに登録する。
- Cは、RuleAとRuleBを持っている。継承の順番にRuleBaseに登録する。
class RuleBase { public: RuleBase() { } virtual ~RuleBase() {} void addRule(const std::function<double(double)>& f) { _f.push_back(f); } double apply(double x) const { for (auto& f : _f) { x = f(x); } return x; } private: std::list<std::function<double(double)> > _f; }; class RuleA : virtual RuleBase { public: RuleA() { addRule([&](double x) {return rule(x); }); } virtual ~RuleA() {} private: double rule(double x) const { return x + 1.0; } }; class RuleB : virtual RuleBase { public: RuleB() { addRule([&](double x) {return rule(x); }); } virtual ~RuleB() {} private: double rule(double x) const { return x * 3.0; } }; class C : RuleA, RuleB, virtual RuleBase { public: C() {} double get(double x) const { return RuleBase::apply(x); } }; int main() { C c; std::cout << c.get(0.5) << std::endl; // (0.5 + 1.0) * 3.0 return 0; }
貯蔵庫サンプリング(Reservoir sampling)
入力(数>n)からn個をランダムに選択する方法。
1. 最初のn個をI[0]...I[n-1]にコピーする。t = n - 1。
2. 入力値がなかったら、5へ。
3. ++tとし、[0, t]の一様乱数uを生成する。 u >= nならば、2へ。
4. I[u] = I[t]とし、2へ。
5. Iをソートして終了。
#include <random> #include <iostream> #include <vector> #include <assert.h> #include <algorithm> template <typename U> const std::vector<int>& generate( U& u, const std::vector<int>& inputs, std::vector<int>& i, std::size_t n, std::size_t t) { if (inputs.size() <= t) { std::sort(i.begin(), i.end()); return i; } std::size_t r = static_cast<std::size_t>(t * u()); ++t; if (r >= n) return generate(u, inputs, i, n, t); i[r] = inputs[t]; return generate(u, inputs, i, n, t); } template <typename U> std::vector<int> generate( U& u, const std::vector<int>& inputs, std::size_t n) { assert(inputs.size() >= n); std::vector<int> results(inputs.begin(), inputs.begin() + n); return generate(u, inputs, results, n, n); } int main() { unsigned int seed = 54635; std::mt19937 mt(seed); std::uniform_real_distribution<double> d; std::size_t size = 100; std::vector<int> inputs(size); for (std::size_t j = 0; j < inputs.size(); ++j) { inputs[j] = j; } std::size_t n = 10; std::vector<int> r = generate([&]() {return d(mt); }, inputs, n); for (auto x : r) std::cout << x << std::endl; return 0; }
選択サンプリング法(selection sampling technique)
N個の中からランダムにn個のを選択するためのアルゴリズム
1. t = 0, m = 0とする。
2. [0, 1]の一様乱数uを生成する。
3. もし、 (N - t) * u >= n - m ならば5へ。それ以外ないなら4へ。
4. tを採用し、++t, ++mとする。もしm < nなら、1へ。 そうでないなら終了。
5. ++tとし、2へ。
template <typename U> void generate(U& u, int size, int n, int t = 0, int m = 0) { if ((size - t) * u() > n - m) { return generate(u, size, n, ++t, m); } std::cout << t << std::endl; if (m + 1 < n) { return generate(u, size, n, ++t, ++m); } } int main() { unsigned int seed = 544; std::mt19937 mt(seed); std::uniform_real_distribution<double> d; int size = 100; int n = 10; generate([&]() {return d(mt); }, size, n); return 0; }
C++のクラスをC#で継承する。swig使用。
まさかこんなことができるとは思っていなかった。
cpp
class Base { public: virtual ~Base() {} virtual void tell() const = 0; }; class Caller { public: void apply(const Base& base) const {base.tell();} };
*.i
%module (directors="1") SwigSample %{ #include "../SwingSample.h" %} %feature("director") Base;
c#
class MyBase: Base { public override void tell() { Console.WriteLine("Yes!!"); } } class Program { static void Main(string[] args) { Caller c = new Caller (); MyBase b = new MyBase(); c.apply(b); } }