C++/遅延評価メソッドチェイン
表示
< C++
概要
[編集]遅延評価(Lazy Evaluation)は、計算リソースを効率的に使用するための強力なプログラミング技法です。データ全体を一度に生成・処理するのではなく、必要な時点で要素を生成・処理することで、メモリ使用量を抑え、処理速度を最適化できます。
遅延評価の基本的な利点
[編集]- メモリ効率: 大量のデータを一度に生成せず、必要な時点で生成する
- 計算コストの最適化: 実際に必要な要素のみを計算する
- 無限列の表現が可能: 理論上、無限の長さのシーケンスを扱える
実装のポイント
[編集]ジェネレータ関数
[編集]遅延評価の核心は、データを即時に生成せず、要求に応じて生成する関数です。以下の例では、std::function
と高階関数を使用して実現します:
// 自然数列を遅延生成するジェネレータ static Seq<int> Naturals() { return Seq<int>([](std::function<bool(int)> yield) { for (int i = 1; yield(i); i++) { } return true; }); }
メソッドチェイン
[編集]遅延評価は、メソッドチェインと組み合わせることで非常に強力になります:
// メソッドチェインの例 auto result = Seq<int>::Naturals() .Filter([](int x) { return x % 2 == 0; }) // 偶数のみ .Map([](int x) { return x * x; }) // 2乗に変換 .Take(10); // 先頭10個
実装の詳細
[編集]テンプレートとジェネリクス
[編集]汎用的な遅延評価シーケンスを実現するには、C++テンプレートが不可欠です:
template<typename T> class Seq { private: std::function<bool(std::function<bool(T)>)> generator; public: // 遅延評価のコア操作 Seq<T> Filter(std::function<bool(T)> predicate) const; Seq<T> Map(std::function<T(T)> transform) const; Seq<T> Take(int n) const; T Reduce(T initialValue, std::function<T(T, T)> accumulator) const; // ストリーム出力演算子のフレンド宣言 template<typename U> friend std::ostream& operator<<(std::ostream& os, const Seq<U>& seq); };
主要な遅延評価メソッド
[編集]各メソッドは、即時に計算を行わず、新しいジェネレータを返します:
Filter
[編集]述語関数に基づいて要素をフィルタリングします:
Seq<T> Filter(std::function<bool(T)> predicate) const { return Seq<T>([this, predicate](std::function<bool(T)> yield) { return generator([yield, predicate](T v) { return !predicate || predicate(v) ? yield(v) : true; }); }); }
Map
[編集]各要素に変換関数を適用します:
Seq<T> Map(std::function<T(T)> transform) const { return Seq<T>([this, transform](std::function<bool(T)> yield) { return generator([yield, transform](T v) { return yield(transform(v)); }); }); }
ストリーム出力演算子
[編集]各要素に変換関数を適用します:
// シーケンスのストリーム出力演算子 template<typename T> std::ostream& operator<<(std::ostream& os, const Seq<T>& seq) { seq.generator([&os](T v) { os << v << " "; return true; }); return os; }
実践的な使用例
[編集]素数列の生成
[編集]遅延評価は、複雑な生成ロジックにも適用できます:
static Seq<int> Primes() { return Seq<int>([](std::function<bool(int)> yield) { std::vector<int> primes; for (int i = 2; ; i++) { bool isPrime = true; for (int prime : primes) { if (i % prime == 0) { isPrime = false; break; } } if (isPrime) { primes.push_back(i); if (!yield(i)) break; } } return true; }); }
活用例
[編集]// 最初の10個の素数を取得 auto primesResult = Seq<int>::Primes().Take(10).Vector(); // 最初の10個の偶数の2乗の和を計算 auto sumOfSquaredEvens = Seq<int>::Naturals() .Filter([](int x) { return x % 2 == 0; }) .Map([](int x) { return x * x; }) .Take(10) .Reduce(0, [](int a, int b) { return a + b; });
注意点と制限
[編集]- パフォーマンスオーバーヘッドに注意
- 複雑な遅延評価は可読性を下げる可能性がある
- 一部の操作(
Vector()
)は遅延性を破壊する
まとめ
[編集]遅延評価とメソッドチェインは、C++で関数型プログラミングのパラダイムを実現する強力な技術です。適切に使用することで、メモリ効率と柔軟な data processing が可能になります。
実装例
[編集]#include <functional> #include <iostream> #include <utility> #include <vector> // Tの遅延評価シーケンスを表すクラス template <typename T> class Seq { private: std::function<bool(std::function<bool(T)>)> generator; public: // 生成器関数を受け取るコンストラクタ explicit Seq(std::function<bool(std::function<bool(T)>)> gen) : generator(std::move(gen)) {} // フィルタ操作 auto Filter(std::function<bool(T)> predicate) const -> Seq<T> { return Seq<T>([this, predicate](std::function<bool(T)> yield) { return generator([yield, predicate](T v) { return !predicate || predicate(v) ? yield(v) : true; }); }); } // マップ操作 auto Map(std::function<T(T)> transform) const -> Seq<T> { return Seq<T>([this, transform](std::function<bool(T)> yield) { return generator( [yield, transform](T v) { return yield(transform(v)); }); }); } // 先頭n個を取得する操作 [[nodiscard]] auto Take(int n) const -> Seq<T> { return Seq<T>([this, n](std::function<bool(T)> yield) { int count = 0; return generator([yield, &count, n](T v) { if (count < n) { count++; return yield(v); } return false; }); }); } // 畳み込み操作 auto Reduce(T initialValue, std::function<T(T, T)> accumulator) const -> T { T result = initialValue; generator([&result, accumulator](T v) { result = accumulator(result, v); return true; }); return result; } // ベクターに変換 [[nodiscard]] auto Vector() const -> std::vector<T> { std::vector<T> result; generator([&result](T v) { result.push_back(v); return true; }); return result; } // 自然数列を生成する静的メソッド static auto Naturals() -> Seq<int> { return Seq<int>([](std::function<bool(int)> yield) { for (int i = 1; yield(i); i++) { } return true; }); } // 素数列を生成する静的メソッド static auto Primes() -> Seq<int> { return Seq<int>([](std::function<bool(int)> yield) { std::vector<int> primes; for (int i = 2;; i++) { bool isPrime = true; for (int const prime : primes) { if (i % prime == 0) { isPrime = false; break; } } if (isPrime) { primes.push_back(i); if (!yield(i)) { break; } } } return true; }); } // ストリーム出力演算子のフレンド宣言 template <typename U> friend auto operator<<(std::ostream& os, const Seq<U>& seq) -> std::ostream&; }; // シーケンスのストリーム出力演算子 template <typename T> auto operator<<(std::ostream& os, const Seq<T>& seq) -> std::ostream& { seq.generator([&os](T v) { os << v << " "; return true; }); return os; } auto main() -> int { // 様々なシーケンス操作のデモ std::cout << "最初の10個の自然数: " << Seq<int>::Naturals().Take(10) << std::endl; std::cout << "最初の10個の偶数: " << Seq<int>::Naturals() .Filter([](int x) { return x % 2 == 0; }) .Take(10) << std::endl; std::cout << "最初の10個の素数: " << Seq<int>::Primes().Take(10) << std::endl; std::cout << "最初の10個の自然数の2乗: " << Seq<int>::Naturals().Map([](int x) { return x * x; }).Take(10) << std::endl; std::cout << "最初の10個の偶数の3倍: " << Seq<int>::Naturals() .Filter([](int x) { return x % 2 == 0; }) .Map([](int x) { return x * 3; }) .Take(10) << std::endl; std::cout << "最初の10個の自然数の和: " << Seq<int>::Naturals().Take(10).Reduce( 0, [](int a, int b) { return a + b; }) << std::endl; std::cout << "最初の10個の自然数の積: " << Seq<int>::Naturals().Take(10).Reduce( 1, [](int a, int b) { return a * b; }) << std::endl; return 0; }