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使用。

まさかこんなことができるとは思っていなかった。

やりたいこと

  • C++で定義されたコードをC#で継承してい使用する。

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);

        }
    }