Posted at

LINQ for C++ を試してみる


概要

C++で、.Net言語のLINQ機能に似せた?ライブラリ(LINQ for C++)があるとのことだったので、試してみました。

https://github.com/unknown-ds/cpp_linq


環境

windows 10

Visual Studio Express 2017 for Windows Desktop


Nuget

cpplinq ver.2013.8.10

ヘッダファイル1個だけなのでわざわざNugetで入れる必要もないです。

コードはマイクロソフトパブリックライセンスとのことです。


導入

cpplinq.hpp をインクルードするだけです。

https://archive.codeplex.com/?p=cpplinq からDLするかNuget等で入手できます。


テストコード


要素の取得1

単体要素の取得です。

firstなどは条件に合う要素がない場合、例外を投げるので注意。

名前
説明

first
条件を満たす最初の要素を取り出す

first_or_default
最初の要素、またはデフォルト値を取得する

last_or_defalut
最後の要素、またはデフォルト値を取得する


int _Src[] = {41, 20, 6, 12, 33, 6};

// first : 条件を満たす最初の要素を取り出す。
auto _Ret1 = from_array(_Src)
>> first([](int x) { return(x % 2 == 0); });
// -> 20

// first_or_default : 最初の要素、またはデフォルト値を取得する。
auto _Ret2 = from_array(_Src)
>> first_or_default([](int x) { return(x > 99); });
// -> 0

// last_or_default : 最後の要素、またはデフォルト値を取得する。
auto _Ret3 = from_array(_Src)
>> last_or_default([](int x) {return(x % 2 == 0); });
// -> 6


要素の取得2

複数要素の取得です。

条件に合う要素がない場合、空を返します。

名前
説明

where
条件を満たす要素を抽出する

distinct
重複を取り除く

skip
先頭から指定数の要素をスキップして残りを返す

skip_while
先頭から指定条件を満たさなくなるまでスキップし残りを返す

take
先頭から指定数の要素を返す

take_while
先頭から指定条件を満たす要素を返す(条件を満たさない以降の要素は返さない)


int _Src2[] = { 1, 2, 3, 99, 30, 20, 10, 99, 5 };

// where : 条件を満たす要素を抽出する。
auto _Ret4 = from_array(_Src2)
>> where([](int x) {return(x > 10); })
>> to_vector();
// -> {99, 30, 20, 99}

// distinct : 重複を取り除く。
auto _Ret5 = from_array(_Src2)
>> distinct()
>> to_vector();
// -> {1, 2, 3, 99, 30, 20, 10, 5}

// skip : 先頭から指定数の要素をスキップして残りを返す。
auto _Ret6 = from_array(_Src2)
>> skip(5)
>> to_vector();
// -> {20, 10, 99, 5}

// skip_while : 先頭から指定条件を満たさなくなるまでスキップし残りを返す。
auto _Ret7 = from_array(_Src2)
>> skip_while([](int x) {return(x <= 10); })
>> to_vector();
// -> {99, 30, 20, 10, 99, 5}

// take : 先頭から指定数の要素を返す。
auto _Ret8 = from_array(_Src2)
>> take(5)
>> to_vector();
// -> {1, 2, 3, 99, 30}

// take_while : 先頭から指定条件を満たす要素を返す。(条件を満たさない以降の要素は返さない)
auto _Ret9 = from_array(_Src2)
>> take_while([](int x) {return(x <= 10); })
>> to_vector();
// -> {1, 2, 3}


要素の作成

様々な条件下で要素を作成します。

名前
説明

singleton
引数として指定された単一の要素で範囲を作成する

generate
入力されたラムダ式から範囲を作成する

pairwise
入力範囲の隣接する要素をグループ化して、新しい範囲のペアを生成する

zip_with
2つの異なる範囲から要素をグループ化して新しい範囲のペアを生成する(サイズが異なる場合、結果は最小範囲のサイズになる)

range
指定のスタートからカウント分の範囲を作成する

repeat
指定回数繰り返しを作成する

empty
空の要素を作成する


// singleton : 引数として指定された単一の要素で範囲を作成する。
auto _Ret1 = singleton(10)
>> to_vector();
// -> {10}

// generate : 入力されたラムダ式から範囲を作成する。
auto val = 3;
auto _Ret2 = generate([&]() {return (val-- > 0) ? to_opt(val) : to_opt<int>(); })
>> to_vector();
// -> {2, 1, 0}

// pairwise : 入力範囲の隣接する要素をグループ化して、新しい範囲のペアを生成する。
int numbers[] = { 1, 2, 3, 4, 5 };
auto _Ret3 = from_array(numbers)
>> pairwise()
>> to_vector();
// -> {(1,2), (2,3), (3,4), (4,5)}

// zip_with : 2つの異なる範囲から要素をグループ化して新しい範囲のペアを生成する。サイズが異なる場合、結果は最小範囲のサイズになる。
int data1[] = { 0, 1, 2 };
string data2[] = { "zero", "one", "two", "three", "four", "five" };
auto _Ret4 = from_array(data1)
>> zip_with(from_array(data2))
>> to_vector();
// -> {(0, "zero"), (1,"one"), (2,"two")}

// range : 指定のスタートからカウント分の範囲を作成する。
auto _Ret5 = range(10, 3)
>> to_vector();
// -> {10, 11, 12}

// repeat : 指定回数繰り返しを作成する。
auto _Ret6 = repeat("LINQ", 3)
>> to_vector();
// -> {"LINQ", "LINQ", "LINQ"}

// empty : 空の要素を作成する。
auto _Ret7 = empty<int>()
>> to_vector();
// -> {}


集計

max, min, avg, sum, は引き数にラムダ式を指定して各要素に処理を加えることもできます。

countは引き数にラムダ式を入れて条件に合う要素を指定することもできます。

avg時に要素をfloat等にキャストする術が見当たりません。。:dizzy_face:

名前
説明

max
最大値を取得する

min
最小値を取得する

avg
平均を取得する

sum
合計を取得する

count
要素数を取得する

aggregate
アキュムレーター関数で計算した結果を取得する


int _Src[] = {5, 7, 11, 2, 6, 10};

// max : 最大値を取得する。
auto _Ret1 = from_array(_Src)
>> max();
// -> 11

// min : 最小値を取得する。
auto _Ret2 = from_array(_Src)
>> min();
// -> 2

// avg : 平均を取得する。
auto _Ret3 = from_array(_Src)
>> avg();
// -> 6 #intなので小数値は切り捨てられている#

// sum : 合計を取得する。
auto _Ret4 = from_array(_Src)
>> sum();
// -> 41

// count : 要素数を取得する。
auto _Ret5 = from_array(_Src)
>> count();
// 6

// aggregate : アキュムレーター関数で計算した結果を取得する。
printf("aggregate = ");
auto _Ret6 = from_array(_Src)
>> aggregate(0, [](int a, int b) { printf("(%d, %d)->", a, b); return(a + b); });
// -> (0, 5)->(5, 7)->(12, 11)->(23, 2)->(25, 6)->(31, 10)-> = 41


判定

bool型で判定結果を返します。

名前
説明

all
全ての要素が条件を満たしているか判定する

any
条件を満たす要素が含まれているか判定する

contains
指定した要素が含まれているか判定する

sequence_equal
2つのシーケンスが等しいか判定する


int _Src[] = { 1, 2, 3, 4, 5, 6 };
int _Src2[] = { 2, 3, 4, 5, 6, 1 };

// all : 全ての要素が条件を満たしているか判定する。
auto _Ret1 = from_array(_Src)
>> all([](int x) {return(x >= 5); });
// -> false

// any : 条件を満たす要素が含まれているか判定する。
auto _Ret2 = from_array(_Src)
>> any([](int x) {return(x >= 5); });
// -> true

// contains : 指定した要素が含まれているか判定する。
auto _Ret3 = from_array(_Src)
>> contains(0);
// -> false

// sequence_equal : 2つのシーケンスが等しいか判定する。
auto _Ret4 = from_array(_Src)
>> sequence_equal(from_array(_Src2));
// -> false


集合

和集合union_withは元要素が重複していた場合まとめる。

差集合exceptの順序に注意。

名前
説明

union_with
和集合を求める

except
差集合を求める

intersect_with
積集合を求める


int _Src1[] = { 1, 2, 3, 10, 20 };
int _Src2[] = { 0, 2, 4, 10, 11 };

// union_with : 和集合を求める。
auto _Ret1 = from_array(_Src1)
>> union_with(from_array(_Src2))
>> to_vector();
// -> {1, 2, 3, 10, 20, 0, 4, 11}

// except : 差集合を求める。
auto _Ret2 = from_array(_Src1)
>> except(from_array(_Src2))
>> to_vector();
// -> {1, 3, 20}

auto _Ret3 = from_array(_Src2)
>> except(from_array(_Src1))
>> to_vector();
// -> {0, 4, 11}

// intersect_with : 積集合を求める。
auto _Ret4 = from_array(_Src1)
>> intersect_with(from_array(_Src2))
>> to_vector();
// -> {2, 10}


ソート

並び替えです。

orderby_***ではなく、orderbyで、第2引き数を指定してもOK。

名前
説明

orderby_ascending
要素を昇順にソートする

thenby_ascending
ソートした要素に対し、キーが等しい要素を昇順にソートする

thenby_descending
ソートした要素に対し、キーが等しい要素を降順にソートする

orderby_decending
要素を降順にソートする

reverse
要素を逆順にソートする


class CData {
public:
int Id;
string Name;

CData(int _Id, string _Name):Id(_Id), Name(_Name){}
};

auto _Data = list<CData>{
{CData(0, "alpha")},
{CData(1, "beta")},
{CData(3, "delta")},
{CData(1, "gamma")},
};

// orderby_ascending : 要素を昇順にソートする。
auto _Ret1 = from(_Data)
>> orderby_ascending([](CData x) {return(x.Id); })
>> thenby_descending([](CData x) {return(x.Name.length()); })
>> to_list();
// -> {{0, alpha}, {1, gamma}, {1, beta}, {3, delta}}

// thenby_ascending : ソートした要素に対し、キーが等しい要素を昇順にソートする。
// thenby_descending : ソートした要素に対し、キーが等しい要素を降順にソートする。
auto _Ret2 = from(_Data)
>> orderby_ascending([](CData x) {return(x.Id); })
>> thenby_descending([](CData x) {return(x.Name.length()); })
>> to_list();
// -> {{0, alpha}, {1, gamma}, {1, beta}, {3, delta}}

// orderby_descending : 要素を降順にソートする。
auto _Ret3 = from(_Data)
>> orderby_descending([](CData x) {return(x.Id); })
>> to_list();
// -> {{3, delta}, {1, beta}, {1, gamma}, {0, alpha}}

// reverse : 要素を逆順にソートする。
auto _Ret4 = from(_Data)
>> reverse()
>> to_list();
// -> {{1, gamma}, {3, delta}, {1, beta}, {0, alpha}}


射影

条件を指定して、別のシーケンスを作成します。

名前
説明

select
1つの要素を単一の要素に射影する

select_many
1つの要素から複数の要素に射影し、その結果を1つのシーケンスで返す


class CData {
public:
int Id;
string Name;

CData(int _Id, string _Name) :Id(_Id), Name(_Name) {}
};

auto _Data = list<CData>{
{CData(0, "aaa")},
{CData(1, "bbb")},
{CData(2, "ccc")},
{CData(3, "ddd")},
};

// select : 1つの要素を単一の要素に射影する。
auto _Ret1 = from(_Data)
>> select([](const auto& x) {return(x.Id + 10); })
>> to_list();
// -> {10, 11, 12, 13}

// select_many : 1つの要素から複数の要素に射影し、その結果を1つのシーケンスで返す。
auto _Ret2 = from(_Data)
>> select_many([](const auto& x) {
auto _dst = vector<int>{ x.Id, x.Id * 10 };
return(from_copy(_dst));
})
>> to_list();
// -> {0, 0, 1, 10, 2, 20, 3, 30}


結合

2つのシーケンスを結合します。

名前
説明

join
内部結合を行ったシーケンスを返す

concat
2つのシーケンスを結合する


class CData1 {
public:
string Name;
int Param1;

CData1(string _Name, int _Param) :Name(_Name), Param1(_Param) {}
};
class CData2 {
public:
string Name;
string Param2;

CData2(string _Name, string _Param) :Name(_Name), Param2(_Param) {}
};

auto _Outer1 = list<CData1>{
{CData1("aaa", 10)},
{CData1("bbb", 11)},
{CData1("ccc", 12)},
};
auto _Outer2 = list<CData1>{
{CData1("ddd", 20)},
{CData1("eee", 30)},
};
auto _Inner = list<CData2>{
{CData2("aaa", "alpha")},
{CData2("bbb", "beta")},
{CData2("ddd", "delta")},
{CData2("aaa", "epsilon")},
};

// join : 内部結合を行ったシーケンスを返す。
auto _Ret1 = from(_Outer1)
>> join(from(_Inner),
[](const auto& o) {return(o.Name); },
[](const auto& i) {return(i.Name); },
[](const auto& o, const auto& i) {return(make_tuple(o.Name, o.Param1, i.Param2)); })
>> to_list();
// -> {{aaa, 10, alpha}, {aaa, 10, epsilon}, {bbb, 11, beta} }

// concat : 2つのシーケンスを連結する。
auto _Ret2 = from(_Outer1)
>> concat(from(_Outer2))
>> to_list();
// -> {{aaa, 10}, {bbb, 11}, {ccc, 12}, {ddd, 20}, {eee, 30}}


その他

名前
説明

from
対象のコンテナを設定する

from_array
対象の配列を設定する

from_copy
return時などに対象のコンテナをコピーする

to_vector
std::vectorに変換

to_list
std::listに変換

for_each
各要素へ処理をする


まとめ

型のキャストなどができなかったですが、ヘッダのみ実装になっており使いやすくなっていました。使い勝手もC#などに似ています。

ただC++標準で実装ではないのでそう言う意味ではは使いづらいと思います。:sweat_smile: