Relinx-C ++での.NET LINQメソッドの別の実装で、「遅延計算」をサポート

レリンクスロゴ

(更新!)

C ++でのLINQのようなライブラリの多くの実装の中には、多くの興味深い、便利で効果的なものがあります。 しかし、私の意見では、それらのほとんどは、言語としてのC ++を無視して書かれています。 これらのライブラリのすべてのコードは、あのさを修正しようとしているように書かれています。 私はC ++が大好きだと認めています。 そして、それがどのように泥に注がれようとも、彼への私の愛は消え去りそうにない。 おそらくこれは、それが私の最初の高度なプログラミング言語であり、アセンブラーの後に学んだのは2番目のプログラミング言語だからかもしれません。







重要な更新 :Relinxの大きな変更! relinx_objectはstd :: enable_shared_from_thisからのmixinになり、std :: shared_ptrとして使用されます。 この変更により、relinx_objectをヒープメモリに配置し、変換チェーン全体のライフサイクルを制御できます。 現在、std :: shared_ptr <relinx_object>は、コンテナーに具体化することなく、関数およびストリームに渡すことができます。 ユーザーコードの唯一の変更点は、ドットではなく->を介したオブジェクトへのアクセスの置き換えです。たとえば、before from({1、2、3}) count()、from from({1、2、3}) -> count()。 最後に、Relinxのコードはnstdと呼ばれる他のプロジェクトに転送されまし 。これはこちらにあります



なんで?



これは永遠の、そして当然の問題です。 「なぜ、LINQのようなライブラリがたくさんあるのに、利用してみますか?」 一部には、そのようなライブラリの実装に関する私自身のビジョンのために、それを書きました。 一部には、LINQメソッドを可能な限り最大限に実装するライブラリを使用する必要があるため、必要に応じて、ある言語から別の言語に最小限の変更でコードを転送できます。



私の実装の特徴:





一部のC ++プログラマーは、イテレーターが気に入らないため、イテレーターを何らかの方法で置き換えようとします(例えば、範囲に置き換えたり、イテレーターをまったく使用しません)。 ただし、新しいC ++ 11標準では、カスタムコレクションオブジェクトのforステートメントをサポートするために、forステートメントに反復子(またはポインターなどの反復可能な型)を提供する必要があります。 そして、この要件は単なるSTLではなく、言語そのものです。



LINQメソッドとRelinxメソッドの対応表:

LINQメソッド Relinxメソッド
集計 集約する
全部 すべて
なし
どれでも どれでも
想定される から
アバラジ アバレッジ
キャスト キャスト
連結 連結
コンテナ 含む
カウント 数える
サイクル
DefaultIfEmpty デフォルト_ if _ empty
明確な 明確な
エレメンタ element_at
ElementAtOrDefault element_at_or_default
空の から
を除く を除く
最初に 最初に
FirstOrDefault first_or_default
for_each for_each_i
Groupby group_by
グループ参加 group_join
交差する 交差する
参加する 参加する
最後 最後の
LastOrDefault last_or_default
ロングカウント 数える
マックス 最大
タイプ of_type
オーダーバイ order_by
OrderByDescending order_by_descending
範囲 範囲
繰り返す 繰り返す
逆に
選択してください select、select_i
SelectMany select_many select_many_i
シーケンス sequence_equal
シングル 独身
SingleOrDefault single_or_default
スキップする 飛ばす
スキップする skip_while、skip_while_i
合計 合計
取る 取る
TakeWhile take_while、take_while_i
それでは then_by
ThenByDescending then_by_descending
トーアレイ to_container、to_vector
辞典 to_map
トーリスト to_list
ルックアップ to_multimap
to_string
連合 union_with
どこで where、where_i
郵便番号 zip


どうやって?



ライブラリのソースコードは、メソッドの使用例とともにDoxygenブロックで文書化されています。 また、メソッド実行の結果を制御してC#の結果に一致させるための、単純な単体テストがあります。ほとんどは私が作成しました。 しかし、それら自体は、ライブラリを使用する簡単な例として役立ちます。 作成とテストには、MinGW / GCC 5.3.0、Clang 3.9.0、およびMSVC ++ 2015コンパイラーを使用しましたが、C MSVC ++ 2015には単体テストのコンパイルに問題があります。 私が理解できた限り、このコンパイラは複雑なラムダ式を誤解しています。 たとえば、ラムダ内でfromメソッドを使用すると、奇妙なコンパイルエラーがクラッシュすることに気付きました。 リストされている他のコンパイラにはこのような問題はありません。



ライブラリはヘッダーファイルのみであり、使用するモジュールに含める必要があります。

使用する前に、便宜上、relinx名前空間を挿入することもできます。



使用例:



簡単な使用。 奇数の数を計算するだけです:



auto result = from({1, 2, 3, 4, 5, 6, 7, 8, 9})->count([](auto &&v) { return !!(v % 2); }); std::cout << result << std::endl; //  : 5
      
      





例はより複雑です-グループ化:



 struct Customer { uint32_t Id; std::string FirstName; std::string LastName; uint32_t Age; bool operator== (const Customer &other) const { return Id == other.Id && FirstName == other.FirstName && LastName == other.LastName && Age == other.Age; } }; //auto group_by(KeyFunction &&keyFunction) const noexcept -> decltype(auto) std::vector<Customer> t1_data = { Customer{0, "John"s, "Doe"s, 25}, Customer{1, "Sam"s, "Doe"s, 35}, Customer{2, "John"s, "Doe"s, 25}, Customer{3, "Alex"s, "Poo"s, 23}, Customer{4, "Sam"s, "Doe"s, 45}, Customer{5, "Anna"s, "Poo"s, 23} }; auto t1_res = from(t1_data)->group_by([](auto &&i) { return i.LastName; }); auto t2_res = from(t1_data)->group_by([](auto &&i) { return std::hash<std::string>()(i.LastName) ^ (std::hash<std::string>()(i.FirstName) << 1); }); assert(t1_res->count() == 2); assert(t1_res->first([](auto &&i){ return i.first == "Doe"s; }).second.size() == 4); assert(t1_res->first([](auto &&i){ return i.first == "Poo"s; }).second.size() == 2); assert(from(t1_res->first([](auto &&i){ return i.first == "Doe"s; }).second)->contains([](auto &&i) { return i.FirstName == "Sam"s; })); assert(from(t1_res->first([](auto &&i){ return i.first == "Poo"s; }).second)->contains([](auto &&i) { return i.FirstName == "Anna"s; })); assert(t2_res->single([](auto &&i){ return i.first == (std::hash<std::string>()("Doe"s) ^ (std::hash<std::string>()("John"s) << 1)); }).second.size() == 2); assert(t2_res->single([](auto &&i){ return i.first == (std::hash<std::string>()("Doe"s) ^ (std::hash<std::string>()("Sam"s) << 1)); }).second.size() == 2);
      
      





グループ化の結果は、std ::ペアのシーケンスです。最初はキーで、2番目はstd ::ベクトルコンテナー内のこのキーでグループ化されたCustomer要素です。 この例では、同じクラスの複数のフィールドによるグループ化はハッシュキーによって行われますが、これは必須ではありません。



そして、ここにgroup_joinの使用例があります。これは、ちなみに、ラムダ式自体のネストされたrelinx要求のために、MSVC ++ 2015でのみコンパイルされません。



 struct Customer { uint32_t Id; std::string FirstName; std::string LastName; uint32_t Age; bool operator== (const Customer &other) const { return Id == other.Id && FirstName == other.FirstName && LastName == other.LastName && Age == other.Age; } }; struct Pet { uint32_t OwnerId; std::string NickName; bool operator== (const Pet &other) const { return OwnerId == other.OwnerId && NickName == other.NickName; } }; //auto group_join(Container &&container, ThisKeyFunction &&thisKeyFunction, OtherKeyFunction &&otherKeyFunction, ResultFunction &&resultFunction, bool leftJoin = false) const noexcept -> decltype(auto) std::vector<Customer> t1_data = { Customer{0, "John"s, "Doe"s, 25}, Customer{1, "Sam"s, "Doe"s, 35}, Customer{2, "John"s, "Doe"s, 25}, Customer{3, "Alex"s, "Poo"s, 23}, Customer{4, "Sam"s, "Doe"s, 45}, Customer{5, "Anna"s, "Poo"s, 23} }; std::vector<Pet> t2_data = { Pet{0, "Spotty"s}, Pet{3, "Bubble"s}, Pet{0, "Kitty"s}, Pet{3, "Bob"s}, Pet{1, "Sparky"s}, Pet{3, "Fluffy"s} }; auto t1_res = from(t1_data)->group_join(t2_data, [](auto &&i) { return i.Id; }, [](auto &&i) { return i.OwnerId; }, [](auto &&key, auto &&values) { return std::make_pair(key.FirstName + " "s + key.LastName, from(values). select([](auto &&i){ return i.NickName; }). order_by(). to_string(",")); } )->order_by([](auto &&p) { return p.first; })->to_vector(); assert(t1_res.size() == 3); assert(t1_res[0].first == "Alex Poo"s && t1_res[0].second == "Bob,Bubble,Fluffy"s); assert(t1_res[1].first == "John Doe"s && t1_res[1].second == "Kitty,Spotty"s); assert(t1_res[2].first == "Sam Doe"s && t1_res[2].second == "Sparky"s); auto t2_res = from(t1_data)->group_join(t2_data, [](auto &&i) { return i.Id; }, [](auto &&i) { return i.OwnerId; }, [](auto &&key, auto &&values) { return std::make_pair(key.FirstName + " "s + key.LastName, from(values). select([](auto &&i){ return i.NickName; }). order_by(). to_string(",")); } , true)->order_by([](auto &&p) { return p.first; })->to_vector(); assert(t2_res.size() == 6); assert(t2_res[1].second == std::string() && t2_res[3].second == std::string() && t2_res[5].second == std::string());
      
      





この例では、最初の操作の結果は、内部結合メソッドを使用したキーによる2つの異なるオブジェクトの結合であり、それらによってオブジェクトをグループ化します。



2番目の操作では、左結合方式を使用してキー結合が実行されます。 これは、trueに設定されたメソッドの最後のパラメーターによって示されます。



そして、これは多相型のフィルタリングの使用例です:



  //auto of_type() const noexcept -> decltype(auto) struct base { virtual ~base(){} }; struct derived : public base { virtual ~derived(){} }; struct derived2 : public base { virtual ~derived2(){} }; std::list<base*> t1_data = {new derived(), new derived2(), new derived(), new derived(), new derived2()}; auto t1_res = from(t1_data)->of_type<derived2*>(); assert(t1_res->all([](auto &&i){ return typeid(i) == typeid(derived2*); })); assert(t1_res->count() == 2); for(auto &&i : t1_data){ delete i; };
      
      










コードは次の場所にあります。



GitHub: https : //github.com/Ptomaine/nstd、https//github.com/Ptomaine/Relinx



ライブラリの使用に関する質問に答える準備ができており、機能性と気づいたエラーを改善するための建設的な提案に非常に感謝しています。



All Articles