LoginSignup
12
15

More than 1 year has passed since last update.

LINQ for C++ を試してみる

Last updated at Posted at 2019-05-25

概要

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等にキャストする術が見当たりません。。: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:

12
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
15