きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

使い捨てラムダ

ラムダはどんどん使い捨てていくのがいいと思うお。

int x = 0, y = 0, z = 0;
const int n = 10;

[&]() -> int& {
	if(n % 2 == 0) {
		return x;
	}
	else if(n % 3 == 0) {
		return y;
	}
	else if(n % 5 == 0) {
		return z;
	}
}() = n;

printf("%d, %d, %d\n", x, y, z); // 10, 0, 0

不思議な構造体

ラムダを保存する型があったとしても……そのラムダに上書きできないんじゃ全く意味ないよね;

auto t = std::make_tuple(
	[](int x) { return x * 2; },
	10
);

struct S {
	decltype(t) t;
};

S s;
s.t; // 何も出来ない

3項演算子とラムダ式

こういうときってよくあります。

C c;

const int x = 10;

if(x > 10) {
	const C c1;
	c = c1; // コピーが発生
}
else {
	const C c2;
	c = c2; // コピーが発生
}

// cを使う

でもこれだとconstを付けられません。このときよくある3項演算子の他に、ラムダ式で関数を作って返すという方法があります。

const int x = 10;
const auto var = [x] {
	if(x > 5) {
		return x * 2;
	}
	else {
		return x * 3;
	}
}();
const auto var2 = x > 5 ? x * 2 : x * 3;

unique_ptrでも同様にできますが、スタックにメモリを確保したいことが大半なのであまりいい方法ではないですね。

auto c = std::make_unique<const C>();
	
const int x = 10;

if(x > 10) {
	auto c1 = std::make_unique<const C>();
	c = move(c1);
}
else {
	auto c2 = std::make_unique<const C>();
	c = move(c2);
}

// cを使う

Optionalのように、スタックに確保して、それにplacement newするようなものを書けばそれはそれで使いやすいかもしれません。今度書いてみます。

Project Euler Problem 3

13195 の素因数は 5, 7, 13, 29 である.

600851475143 の素因数のうち最大のものを求めよ.

先ほど実装したOptionalと、LINQ for C++を用いて、簡単に求めてみました。素因数クラスは、インクリメントするたびに次の素数をOptional付きで返します。このため、これ以上十数が出せない場合はOptionalから判定できます。それをgenerateしてmaxを求めますが、PrimeNumbersのインスタンスがgenerateで一度に作られるわけではなく、maxを求めるためにその都度計算されるところがポイントです。コンストラクタが呼び出されるのはOptionalの特性から仕方がないことというわけですね。

#include <stdio.h>

#include <LINQ/cpplinq.hpp>
#include "utils.h"
using namespace utils;

class PrimeNumbers {
public:
	explicit PrimeNumbers(long long n) : n(n), q(n), value(1) { }
	PrimeNumbers(const PrimeNumbers& it) : n(it.n), q(it.q), value(it.value) { }
	PrimeNumbers& operator=(const PrimeNumbers& it) {
		n = it.n;
		q = it.q;
		value = it.value;
		return *this;
	}
	operator long long() const {
		return value;
	}
	Optional<PrimeNumbers> operator++() {
		for(++value; value <= q; ++value) {
			if(q % value == 0) {
				q /= value;
				return *this;
			}
		}
		
		return None<PrimeNumbers>();
	}
	Optional<PrimeNumbers> operator++(int) {
		PrimeNumbers ret = *this;
		operator++();
		return ret;
	}
private:
	long long n;
	long long q;
	long long value;
};

int main() {
	using namespace cpplinq;

	const Optional<PrimeNumbers> n = PrimeNumbers(600851475143);
	auto ret = generate(
		[&n]() {
		return ++(*n) ? to_opt(static_cast<long long>(*n)) : to_opt<long long>();
	})
		>> max();

	printf("%lld", ret);


	getchar();
	return 0;
}
続きを読む

Optionalを実装せよ

Optionalは無効値を持った型です。

Optionalを作るときに、メモリ確保は行わず、スタック上の変数に対してplacement newによって再配置を行います。そのためオーバーヘッドは少ないはずです。

テストケース

正の数の場合は値を返し、そうでない場合は無効値を返す関数funcを考えます。

Optional<int> func(int n) {
	if(n > 0) {
		return Optional<int>(n);
	}
	else {
		return Optional<int>();
	}
}

int main() {
	std::array<int, 2> a = { 10, -10 };

	for(auto it : a) {
		if(auto v = func(it)) { // 型付代入をif文に入れる
			printf("正の数 : %d\n", *v);
		}
		else {
			printf("正の数を与えよ\n");
		}
	}
	

	getchar();
	return 0;
}

Maybeモナド

関数型プログラミングはよく知らないのでそれっぽい感じだとは思いますが、Optionalを続けて実行できるような演算子>>を定義してみました。

using t = Optional<const int>;
using t_none = None<const int>;

for(const auto& i : { t(60), t(5), t(30) }) {
	printf("=====\n");
	
	const auto ret = i
		>> [](const t& v) -> t {
		printf("In first lambda\n");
		if(*v > 10) {
			return *v / 10;
		}
		else {
			return t_none();
		}
	}
		>> [](const t& v) -> t {
		printf("In second lambda\n");
		if(*v % 2 == 0) {
			return *v;
		}
		else {
			return t_none();
		}
	};

	if(ret) {
		printf("ok. ret = %d\n", *ret);
	}
	else {
		printf("error.\n");
	}
}

実行結果はこうなります :

In first lambda
In second lambda
ok. ret = 6

In first lambda
error.

In first lambda
In second lambda
error.

エラーは無効値として返却されるので、頻繁に起こりうる例外はこちらで書いた方がいいかもしれませんね。

bindとか活用して複数回繰り返す

using namespace utils;
using t = Optional<const int>;
using t_none = None<const int>;

auto try_times = [](std::function<t(t)> func, size_t n, const t& v) -> t {
	t ret = v;
	for(size_t i = 0; i < n; ++i) {
		if(!(ret = ret >> func)) {
			return t_none();
		}
	}
	return ret;
};

const t i = 1;

const auto ret = i
	>> std::bind(try_times,
	[](const t& it) { return *it * 2; },
	10, std::placeholders::_1);

printf("%d", *ret);

実装例

#include <new>
#include <type_traits>
#include <functional>

template <typename T>
class None { };

template <typename T>
class Optional {
public:
	Optional() : data_pointer(nullptr) { }
	Optional(const T& it) : data_pointer(new(&data) T(it)) { }
	Optional(const None<T>& it) : data_pointer(nullptr) { }
	Optional(const Optional<T>& it) {
		if(data_pointer) { data_pointer = new(&data) T(*it); }
		else { data_pointer = nullptr; }
	}
	template <typename ...Args>
	Optional(Args ...args) : data_pointer(new(&data) T(args...)) { }
	~Optional() { if(data_pointer) { data_pointer->~T(); } }
	Optional<T> operator=(const T& it) {
		data_pointer = new(&data) T(it);
		return *this;
	}
	Optional<T> operator=(const Optional& it) {
		data_pointer = new(&data) T(*it);
		return *this;
	}
	Optional<T> operator=(const None<T>& it) {
		data_pointer = nullptr;
		return *this;
	}
	bool operator==(const Optional& it) const {
		return data_pointer == it.data_pointer;
	}
	bool operator!=(const Optional& it) const {
		return data_pointer != it.data_pointer;
	}
	T& operator*() const {
		return *data_pointer;
	}
	T* operator->() const {
		return data_pointer;
	}

	template <typename Func>
	Optional<T> operator>>(Func func) const {
		if(data_pointer != nullptr) {
			return func(*this);
		}
		else {
			return None<T>();
		}
	}
	operator bool() const {
		return data_pointer;
	}
private:
	mutable T* data_pointer;
	typename std::aligned_storage<sizeof(T), __alignof(T)>::type data;
};

LINQ for C++ライブラリを使ってみた

ヘッダファイル1つインクルードするだけで使えるこのライブラリは、簡単に使えてちょっとした配列の操作にもいいかもしれないですね。

#include <stdio.h>
#include <LINQ/cpplinq.hpp>

int main() {
	using namespace cpplinq;

	struct student {
		char* name;
		int point;
	} list[] = {
		{ "A", 30 },
		{ "B", 50 },
		{ "C", 60 },
		{ "D", 70 },
	};

	auto result = from_array(list)
		>> where([](student s) { return s.point >= 60; })
		>> to_vector();

	printf("合格者\n");
	for(auto& it : result) {
		printf("%s : %d\n", it.name, it.point);
	}

	getchar();
	return 0;
}

並び替えてみよう

他のコードは殆ど同じなので一部省略しています。

auto result = from_array(list)
	>> where([](student s) { return s.point >= 60; })
	>> orderby_ascending([](student s) { return s.point; })
	>> to_vector();

for(auto& it : result) {
	printf("%s : %d\n", it.name, it.point);
}

同じ点だったらID(学籍番号的なもの)について並び替えてみよう

int main() {
	using namespace cpplinq;

	struct student {
		char* name;
		int point;
		int id;
	} list[] = {
		{ "A", 66, 1 },
		{ "B", 66, 2 },
		{ "C", 58, 3 },
		{ "D", 66, 4 },
		{ "E", 74, 5 },
	};

	auto result = from_array(list)
		>> orderby_ascending([](student s) { return s.point; })
		>> thenby_ascending([](student s) { return -s.id; })
		>> to_vector();

	for(auto& it : result) {
		printf("%s : %d\n", it.name, it.point);
	}

	getchar();
	return 0;
}

1から100整数の内、奇数のものだけの合計

auto result = range(1, 100)
	>> where([](int n) { return n % 2 == 1; })
	>> sum();

printf("%d", result);

計算

count, sum, max, min, avg。sumと使い方は同じです。

skip, take

最初の10個の要素を飛ばして、その後10個の要素だけ対象に、ある条件の要素をだけを集める。

auto result = range(1, 100)
	>> skip(10)
	>> take(10)
	>> where([](int n) { return n % 2 == 0; })
	>> to_vector();

for(auto it : result) {
	printf("%d\n", it);
}

take_while, skip_whileもあって、これはある条件の間の要素を撮ったりスキップしたり出来る。

同じものを繰り返し並べる

repeat(同じもの, 繰り返し数)で可能。

要素を生成する

to_optはOptional型のようなもので、何も引数を与えなければnullptrのような扱い、引数を与えればそのものというふうな型です。generateでは、逐次引数の関数が実行され、to_opt(~)で値を作成しつづけ、to_opt()を返して終了とします。ただし、型を与えないといけません。

int x = 5;
using T = tuple < int, int > ;
auto result = generate(
	[x]() mutable {
		--x;
		return x <= 0 ? to_opt<T>() : to_opt(T(x, 5 - x));
})
	>> to_vector();

for(auto& it : result) {
	printf("(%d, %d)\n", get<0>(it), get<1>(it));
}

結果は、

(4, 1)
(3, 2)
(2, 3)
(1, 4)

すべての要素に適用

例えば、range(1, 10)で1から10までの数を作って、それを2倍して、2から20までの数を作る例 :

auto result = range(1, 10)
	>> select([](int n) { return n * 2; })
	>> to_vector();

for(auto it : result) {
	printf("%d\n", it);
}

重複する要素

int a[] = { 0, 1 };

auto result = range(1, 10)
	>> select([](int n) { return n % 3; })
	>> intersect_with(from_array(a))
	>> to_vector();

for(auto it : result) {
	printf("%d\n", it);
}

重複しない要素

int a[] = { 0, 1 };

auto result = range(1, 10)
	>> select([](int n) { return n % 3; })
	>> except(from_array(a))
	>> to_vector();

for(auto it : result) {
	printf("%d\n", it);
}

特定の要素を基準としてみる

#include <stdio.h>
#include <tuple>
#include <LINQ/cpplinq.hpp>

int main() {
	using namespace cpplinq;
	using std::tuple;
	using std::get;

	auto lookup = range(1, 10)
		>> select([](int n) { return std::make_tuple(n, n % 3); })
		>> to_lookup([](tuple<int, int> t) { return get<1>(t); });

	auto result = lookup[1]
		>> where([](tuple<int, int> t) { return get<0>(t) % 2 == 0; })
		>> select([](tuple<int, int> t) { return get<0>(t); })
		>> to_vector();

	for(auto& it : result) {
		printf("%d\n", it);
	}

	getchar();
	return 0;
}

まず、selectで10個の整数からtupleで、(実際の数, 3で割ったあまりの数)を作ります。次に、to_lookupであまりの数に着目します。そして、lookup[1]で余りが1のものだけを抽出し、その中で実際の数が偶数のものだけを抜き取ります。この場合は結局、3で割った余りが1の偶数を抜き出したことになります。

すべての要素が等しいか判定

auto is_equal = range(1, 10)
	>> sequence_equal(range(1, 10));
printf("%d", is_equal);

最初の要素を取得

条件を与えることも、与えないことも出来ます。取得できないと例外cpplinq::sequence_empty_exceptionが投げられます。

auto result = range(1, 10)
	>> first([](int n) { return n % 2 == 0; });

printf("%d", result);

同じキー列の2つのデータ列をまとめる

struct Data {
	char* name;
	int point;
	int id;
};

Data data_a[] = {
	{ "A", 33, 1 },
	{ "B", 44, 2 },
	{ "C", 55, 3 },
	{ "D", 66, 4 },
};

Data data_b[] = {
	{ "A", 77, 1 },
	{ "E", 20, 5 },
	{ "F", 30, 6 },
	{ "B", 40, 2 }
};

auto result = from_array(data_a)
	>> join(
	from_array(data_b),
	[](Data d) { return d.id; },
	[](Data d) { return d.id; },
	[](Data da, Data db) { return std::make_tuple(da.name, da.point, db.point); })
	>> to_vector();

for(auto& it : result) {
	printf("(%s, %d, %d)\n", get<0>(it), get<1>(it), get<2>(it));
}

とすると

(A, 33, 77)
(B, 44, 40)

と返って来ます。つまり、data_a, data_b2つのデータのうち共通のIDであり、それらをtupleで結合したデータを出力しているのですね。

~が 少なくとも1つ, すべて, 含まれる

int a[] = { 3, 5, 8, 9 };

printf("%d\n", from_array(a)
	>> any([](int n) { return n > 5; })); // true
printf("%d\n", from_array(a)
	>> all([](int n) { return n > 5; })); // false
printf("%d\n", from_array(a)
	>> contains(5)); // true