概要
C++で、.Net言語のLINQ機能に似せた?ライブラリ(LINQ for C++)があるとのことだったので、試してみました。
環境
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等にキャストする術が見当たりません。。
名前 | 説明 |
---|---|
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++標準で実装ではないのでそう言う意味では使いづらいと思います。