コンテンツにスキップ

C++/遅延評価メソッドチェイン

出典: フリー教科書『ウィキブックス(Wikibooks)』
< 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;
}