C++のラムダ式とデータストリーム処理の実装方法を徹底解説

C++のラムダ式とデータストリーム処理について、その基本概念と実用的な実装方法を解説します。近年のC++の進化により、ラムダ式はコードを簡潔かつ効率的に記述するための重要なツールとなりました。また、データストリーム処理は大量のデータを効率よく処理するための手法として注目されています。本記事では、これらの技術を駆使して、C++でのデータ処理をどのように実装するかについて詳しく説明します。具体的なコード例や応用例を通じて、実務で役立つ知識を提供します。

目次

C++のラムダ式とは

ラムダ式は、C++11から導入された無名関数(匿名関数)の一種です。簡潔な関数オブジェクトを作成するために使用され、特定の範囲内でのみ有効な関数を定義するのに便利です。ラムダ式の基本構文は以下の通りです。

[キャプチャリスト](引数リスト) -> 返り値の型 {
    関数本体
};

例えば、以下のようなコードでラムダ式を使用できます。

auto add = [](int a, int b) -> int {
    return a + b;
};

int result = add(2, 3); // 結果は5

このように、ラムダ式を使うことで、関数を簡潔に定義し、コードの可読性と保守性を向上させることができます。次に、ラムダ式のキャプチャリストについて詳しく見ていきましょう。

ラムダ式のキャプチャ

ラムダ式のキャプチャリストは、ラムダ式の外側にある変数をラムダ式の中で使用できるようにするためのものです。キャプチャリストには、変数を値渡し(コピー)するか参照渡しするかを指定できます。

値渡し(コピー)のキャプチャ

変数を値渡しでキャプチャする場合、キャプチャリストに変数名をそのまま記述します。以下はその例です。

int x = 10;
auto lambda = [x](int y) {
    return x + y;
};
int result = lambda(5); // 結果は15

この例では、変数xがラムダ式内でコピーされて使用されます。

参照渡しのキャプチャ

変数を参照渡しでキャプチャする場合、キャプチャリストに&を付けます。以下はその例です。

int x = 10;
auto lambda = [&x](int y) {
    x += y;
    return x;
};
int result = lambda(5); // 結果は15, xは15に変更される

この例では、変数xが参照渡しでキャプチャされているため、ラムダ式内でxの値が変更されます。

キャプチャリストの全体指定

キャプチャリストには、すべての外部変数を値渡しまたは参照渡しでキャプチャする方法もあります。

int x = 10;
int z = 5;
auto lambda = [=](int y) { // 全ての外部変数をコピーでキャプチャ
    return x + y + z;
};
int result = lambda(3); // 結果は18

auto lambda_ref = [&](int y) { // 全ての外部変数を参照でキャプチャ
    x += y;
    return x + z;
};
result = lambda_ref(3); // 結果は18, xは13に変更される

これにより、外部変数を効率的に使用できるようになります。キャプチャリストの使い方を理解することで、ラムダ式をさらに効果的に活用できるようになります。

データストリーム処理の基礎

データストリーム処理とは、データの連続的な流れを扱う手法です。これにより、大量のデータをリアルタイムで処理したり、複雑なデータ変換をシンプルに実現することができます。C++では、標準ライブラリを用いてデータストリーム処理を実装することができます。

ストリームの基本概念

データストリーム処理の基本は、データの流れを操作するための一連の操作です。一般的なストリーム操作には、フィルタリング、変換、集約などが含まれます。

入出力ストリーム

C++の標準ライブラリでは、std::istream(入力ストリーム)とstd::ostream(出力ストリーム)がデータの読み書きに使用されます。これにより、ファイルやコンソールとのデータのやり取りが簡単に行えます。

以下は基本的な入力ストリームと出力ストリームの使用例です。

#include <iostream>
#include <string>

int main() {
    std::string input;
    std::cout << "Enter your name: ";
    std::cin >> input; // 入力ストリームを使用してデータを読み取る
    std::cout << "Hello, " << input << "!" << std::endl; // 出力ストリームを使用してデータを表示する
    return 0;
}

ストリーム操作

ストリーム操作は、連続したデータを処理する際に便利です。例えば、ファイルからデータを読み込み、フィルタリングして、別のファイルに書き込むといった操作が可能です。

以下はファイルの読み込みと書き込みの例です。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("input.txt");
    std::ofstream outputFile("output.txt");
    std::string line;

    if (inputFile.is_open() && outputFile.is_open()) {
        while (getline(inputFile, line)) {
            outputFile << line << std::endl; // 各行を読み込み、別のファイルに書き込む
        }
        inputFile.close();
        outputFile.close();
    } else {
        std::cerr << "Unable to open file" << std::endl;
    }
    return 0;
}

データストリームの利点

データストリーム処理の主な利点は、以下の通りです。

  • リアルタイム処理: データが到着次第、即座に処理を行うことができる。
  • 効率的なリソース利用: 必要なデータのみをメモリに保持するため、大規模データの処理が効率的。
  • シンプルなコード: 複雑なデータ操作を簡潔に記述できる。

次のセクションでは、具体的なコード例を用いて、C++でのデータストリーム処理の実装方法について説明します。

C++でのストリーム処理の実装

C++では、標準ライブラリを使用してデータストリーム処理を効果的に実装することができます。このセクションでは、具体的なコード例を通じて、データストリーム処理の実装方法を解説します。

ファイルからデータを読み込む

まず、テキストファイルからデータを読み込み、そのデータを加工して別のファイルに書き込む例を見てみましょう。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("input.txt");
    std::ofstream outputFile("output.txt");
    std::string line;

    if (inputFile.is_open() && outputFile.is_open()) {
        while (std::getline(inputFile, line)) {
            // ここで行ごとにデータを処理します
            outputFile << line << std::endl;
        }
        inputFile.close();
        outputFile.close();
    } else {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
    }

    return 0;
}

この例では、input.txtからデータを読み込み、各行をoutput.txtに書き込んでいます。

ストリームでのフィルタリング

次に、ファイルから読み込んだデータをフィルタリングして、特定の条件に一致するデータのみを出力ファイルに書き込む例を示します。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("input.txt");
    std::ofstream outputFile("filtered_output.txt");
    std::string line;

    if (inputFile.is_open() && outputFile.is_open()) {
        while (std::getline(inputFile, line)) {
            // フィルタリング条件:例えば、行に"error"という文字が含まれている場合
            if (line.find("error") != std::string::npos) {
                outputFile << line << std::endl;
            }
        }
        inputFile.close();
        outputFile.close();
    } else {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
    }

    return 0;
}

この例では、input.txtから読み込んだデータのうち、「error」という文字が含まれている行のみをfiltered_output.txtに書き込んでいます。

ストリームでのデータ変換

最後に、データを読み込みながら変換し、出力ファイルに書き込む例を示します。ここでは、読み込んだ行をすべて大文字に変換して出力します。

#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>

int main() {
    std::ifstream inputFile("input.txt");
    std::ofstream outputFile("uppercase_output.txt");
    std::string line;

    if (inputFile.is_open() && outputFile.is_open()) {
        while (std::getline(inputFile, line)) {
            std::transform(line.begin(), line.end(), line.begin(), ::toupper);
            outputFile << line << std::endl;
        }
        inputFile.close();
        outputFile.close();
    } else {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
    }

    return 0;
}

この例では、input.txtから読み込んだ各行を大文字に変換し、uppercase_output.txtに書き込んでいます。

以上のように、C++では標準ライブラリを使用して簡単にデータストリーム処理を実装することができます。次のセクションでは、ラムダ式を使ったデータフィルタリングの方法について詳しく見ていきます。

ラムダ式を用いたデータフィルタリング

ラムダ式を使用すると、データストリーム処理において柔軟で強力なフィルタリングを行うことができます。このセクションでは、具体的なコード例を通じて、ラムダ式を用いたデータフィルタリングの方法を解説します。

基本的なフィルタリングの例

まず、ラムダ式を使って、特定の条件に基づいてデータをフィルタリングする方法を見てみましょう。以下の例では、ベクター内の偶数のみをフィルタリングして新しいベクターに格納します。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> evenNumbers;

    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(evenNumbers),
                 [](int num) { return num % 2 == 0; });

    std::cout << "Even numbers: ";
    for (int num : evenNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、std::copy_if関数を使用して、ベクターnumbersから偶数のみをevenNumbersにコピーしています。ラムダ式[](int num) { return num % 2 == 0; }がフィルタリングの条件を定義しています。

ファイルデータのフィルタリング

次に、テキストファイルから読み込んだデータをフィルタリングして、特定の条件に一致する行のみを別のファイルに書き込む例を示します。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>

int main() {
    std::ifstream inputFile("input.txt");
    std::ofstream outputFile("filtered_output.txt");
    std::vector<std::string> lines;
    std::string line;

    if (inputFile.is_open() && outputFile.is_open()) {
        while (std::getline(inputFile, line)) {
            lines.push_back(line);
        }

        inputFile.close();

        // "error"という文字を含む行のみをフィルタリング
        std::vector<std::string> filteredLines;
        std::copy_if(lines.begin(), lines.end(), std::back_inserter(filteredLines),
                     [](const std::string& line) {
                         return line.find("error") != std::string::npos;
                     });

        for (const auto& filteredLine : filteredLines) {
            outputFile << filteredLine << std::endl;
        }

        outputFile.close();
    } else {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
    }

    return 0;
}

この例では、ファイルinput.txtから読み込んだ各行をベクターlinesに格納し、その中から「error」という文字を含む行のみをfilteredLinesにコピーしています。コピーにはラムダ式を使用しています。

データストリームのフィルタリングと変換の組み合わせ

フィルタリングと変換を組み合わせることで、さらに高度なデータストリーム処理を実現することができます。以下の例では、ベクター内の偶数をフィルタリングし、各要素を2倍に変換してから出力しています。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> transformedNumbers;

    // 偶数をフィルタリングして2倍に変換
    std::transform(numbers.begin(), numbers.end(), std::back_inserter(transformedNumbers),
                   [](int num) { return num % 2 == 0 ? num * 2 : num; });

    std::cout << "Transformed numbers: ";
    for (int num : transformedNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、std::transform関数を使用して、偶数をフィルタリングして2倍に変換しています。ラムダ式[](int num) { return num % 2 == 0 ? num * 2 : num; }が変換の条件を定義しています。

次のセクションでは、ラムダ式を用いたデータ変換の方法についてさらに詳しく見ていきます。

ラムダ式を用いたデータ変換

ラムダ式は、データ変換を簡潔かつ効率的に実装するための強力なツールです。このセクションでは、具体的なコード例を通じて、ラムダ式を用いたデータ変換の方法を解説します。

基本的なデータ変換の例

まず、ベクター内のデータをラムダ式を使って変換する基本的な例を見てみましょう。以下のコードでは、ベクター内の各要素を2倍に変換します。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<int> doubledNumbers;

    std::transform(numbers.begin(), numbers.end(), std::back_inserter(doubledNumbers),
                   [](int num) { return num * 2; });

    std::cout << "Doubled numbers: ";
    for (int num : doubledNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、std::transform関数を使用して、ベクターnumbersの各要素を2倍に変換し、doubledNumbersに格納しています。ラムダ式[](int num) { return num * 2; }が変換のロジックを定義しています。

複雑なデータ変換の例

次に、もう少し複雑なデータ変換の例を見てみましょう。以下のコードでは、文字列ベクター内の各要素を大文字に変換します。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

int main() {
    std::vector<std::string> words = {"hello", "world", "c++", "lambda"};
    std::vector<std::string> uppercasedWords;

    std::transform(words.begin(), words.end(), std::back_inserter(uppercasedWords),
                   [](std::string word) {
                       std::transform(word.begin(), word.end(), word.begin(), ::toupper);
                       return word;
                   });

    std::cout << "Uppercased words: ";
    for (const std::string& word : uppercasedWords) {
        std::cout << word << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、std::transform関数を2回使用しています。最初のstd::transform関数は、文字列ベクターwordsの各要素を大文字に変換し、uppercasedWordsに格納します。内側のstd::transform関数は、個々の文字列内の文字を大文字に変換します。ラムダ式内でネストされたstd::transformが文字列変換のロジックを定義しています。

データ変換とフィルタリングの組み合わせ

データ変換とフィルタリングを組み合わせることで、さらに高度なデータ処理を行うことができます。以下の例では、ベクター内の偶数をフィルタリングし、それらを2倍に変換します。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> filteredAndDoubledNumbers;

    std::transform(numbers.begin(), numbers.end(), std::back_inserter(filteredAndDoubledNumbers),
                   [](int num) {
                       return num % 2 == 0 ? num * 2 : num;
                   });

    filteredAndDoubledNumbers.erase(
        std::remove_if(filteredAndDoubledNumbers.begin(), filteredAndDoubledNumbers.end(),
                       [](int num) { return num % 2 != 0; }),
        filteredAndDoubledNumbers.end());

    std::cout << "Filtered and doubled numbers: ";
    for (int num : filteredAndDoubledNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、std::transform関数を使用して、ベクターnumbersの各要素を偶数の場合は2倍に変換し、それ以外はそのままの値を返しています。次に、std::remove_if関数を使用して、偶数でない要素を削除しています。

以上のように、ラムダ式を用いることで、複雑なデータ変換処理をシンプルかつ効果的に実装することができます。次のセクションでは、データストリームのパイプライン処理の実装方法について詳しく見ていきます。

パイプライン処理の実装

データストリームのパイプライン処理は、複数の操作を連続的に適用することでデータを変換・加工する手法です。これにより、複雑なデータ処理をシンプルかつ効率的に実装できます。このセクションでは、パイプライン処理の具体的な実装方法を解説します。

パイプライン処理の基本概念

パイプライン処理では、データが一連の処理ステージを順次通過します。各ステージは独立した操作を実行し、次のステージにデータを渡します。これにより、データ処理の流れを直感的に理解しやすくなります。

ベクターを使ったパイプライン処理の例

以下の例では、整数ベクターを入力として、以下のステージを通じてデータを処理します。

  1. 偶数をフィルタリングする。
  2. 各要素を2倍に変換する。
  3. 結果を出力する。
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 偶数をフィルタリングする
    std::vector<int> filteredNumbers;
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(filteredNumbers),
                 [](int num) { return num % 2 == 0; });

    // 各要素を2倍に変換する
    std::vector<int> transformedNumbers;
    std::transform(filteredNumbers.begin(), filteredNumbers.end(), std::back_inserter(transformedNumbers),
                   [](int num) { return num * 2; });

    // 結果を出力する
    std::cout << "Transformed numbers: ";
    for (int num : transformedNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

パイプライン処理の連結

さらに複雑なパイプライン処理を実現するために、複数のラムダ式を連結することができます。以下の例では、3つのステージを連結しています。

  1. 偶数をフィルタリングする。
  2. 各要素を2倍に変換する。
  3. 各要素に10を加算する。
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // パイプライン処理
    std::vector<int> result;
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(result),
                 [](int num) { return num % 2 == 0; });

    std::transform(result.begin(), result.end(), result.begin(),
                   [](int num) { return num * 2; });

    std::transform(result.begin(), result.end(), result.begin(),
                   [](int num) { return num + 10; });

    // 結果を出力する
    std::cout << "Final result: ";
    for (int num : result) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

標準ライブラリの活用

C++標準ライブラリのstd::rangesを使用すると、パイプライン処理をより直感的に記述できます。以下の例では、std::ranges::viewsを使用して、同じパイプライン処理を行います。

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // パイプライン処理
    auto result = numbers 
                | std::ranges::views::filter([](int num) { return num % 2 == 0; })
                | std::ranges::views::transform([](int num) { return num * 2; })
                | std::ranges::views::transform([](int num) { return num + 10; });

    // 結果を出力する
    std::cout << "Final result: ";
    for (int num : result) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、std::ranges::viewsを使用することで、フィルタリングと変換の操作を直感的かつ簡潔に連結しています。

以上のように、パイプライン処理を利用することで、データストリーム処理をシンプルに記述し、効率的に実装することができます。次のセクションでは、並行処理とデータストリームについて詳しく見ていきます。

並行処理とデータストリーム

並行処理は、大量のデータを効率的に処理するための重要な技術です。C++では、標準ライブラリを用いて並行処理を実装することができます。このセクションでは、データストリーム処理における並行処理の利点と実装方法を解説します。

並行処理の利点

並行処理の主な利点は以下の通りです。

  • パフォーマンス向上: 複数の処理を同時に実行することで、全体の処理時間を短縮します。
  • 効率的なリソース利用: CPUのマルチコアアーキテクチャを最大限に活用できます。
  • リアルタイム処理: データが到着次第、即座に処理を行うことができます。

基本的な並行処理の例

まず、C++の標準ライブラリを使用して並行処理を実装する基本的な例を見てみましょう。以下のコードでは、スレッドを使用してデータのフィルタリングと変換を並行して行います。

#include <iostream>
#include <vector>
#include <algorithm>
#include <thread>

void filterEvenNumbers(const std::vector<int>& input, std::vector<int>& output) {
    std::copy_if(input.begin(), input.end(), std::back_inserter(output),
                 [](int num) { return num % 2 == 0; });
}

void doubleNumbers(std::vector<int>& numbers) {
    std::transform(numbers.begin(), numbers.end(), numbers.begin(),
                   [](int num) { return num * 2; });
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> filteredNumbers;

    std::thread filterThread(filterEvenNumbers, std::ref(numbers), std::ref(filteredNumbers));
    filterThread.join();

    std::thread transformThread(doubleNumbers, std::ref(filteredNumbers));
    transformThread.join();

    std::cout << "Filtered and doubled numbers: ";
    for (int num : filteredNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、filterEvenNumbers関数が偶数をフィルタリングし、doubleNumbers関数がそれらを2倍に変換します。各処理は独立したスレッドで実行され、join関数でスレッドの終了を待機しています。

並行処理とパイプラインの組み合わせ

並行処理をパイプライン処理に組み込むことで、さらに効率的なデータストリーム処理を実現できます。以下の例では、std::asyncを使用して、非同期にデータのフィルタリングと変換を行います。

#include <iostream>
#include <vector>
#include <algorithm>
#include <future>

std::vector<int> filterEvenNumbers(const std::vector<int>& input) {
    std::vector<int> output;
    std::copy_if(input.begin(), input.end(), std::back_inserter(output),
                 [](int num) { return num % 2 == 0; });
    return output;
}

std::vector<int> doubleNumbers(const std::vector<int>& numbers) {
    std::vector<int> output = numbers;
    std::transform(output.begin(), output.end(), output.begin(),
                   [](int num) { return num * 2; });
    return output;
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    auto filterFuture = std::async(std::launch::async, filterEvenNumbers, numbers);
    auto filteredNumbers = filterFuture.get();

    auto transformFuture = std::async(std::launch::async, doubleNumbers, filteredNumbers);
    auto transformedNumbers = transformFuture.get();

    std::cout << "Filtered and doubled numbers: ";
    for (int num : transformedNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

この例では、std::asyncを使用してフィルタリングと変換を非同期に実行しています。これにより、処理の並行性が向上し、効率的にデータストリーム処理を行うことができます。

並行処理のベストプラクティス

並行処理を実装する際には、以下のベストプラクティスに注意することが重要です。

  • データ競合の回避: 共有データのアクセスには適切な同期機構を使用し、データ競合を避ける。
  • 適切なタスク分割: タスクを適切に分割し、スレッド間の負荷を均等にする。
  • リソースの管理: スレッドの作成や破棄に伴うオーバーヘッドを最小限に抑える。

以上のように、並行処理を活用することで、C++におけるデータストリーム処理のパフォーマンスと効率を大幅に向上させることができます。次のセクションでは、学習内容を確認するための演習問題を提供します。

演習問題

これまでのセクションで学んだC++のラムダ式とデータストリーム処理の知識を確認するための演習問題をいくつか提供します。各問題には、具体的なコード例や説明が含まれていますので、ぜひ実際にコードを書いて試してみてください。

問題1: 基本的なラムダ式の使用

次の要件を満たすラムダ式を使用して、整数ベクターのすべての要素を2倍に変換し、出力するプログラムを作成してください。

  • ベクターの初期値は {1, 2, 3, 4, 5} とする。
  • std::transform を使用して、要素を2倍に変換する。
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<int> doubledNumbers;

    std::transform(numbers.begin(), numbers.end(), std::back_inserter(doubledNumbers),
                   [](int num) { return num * 2; });

    std::cout << "Doubled numbers: ";
    for (int num : doubledNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

問題2: フィルタリングと変換の組み合わせ

次の要件を満たすプログラムを作成してください。

  • 整数ベクター numbers を初期値 {10, 15, 20, 25, 30, 35, 40} で初期化する。
  • 偶数のみをフィルタリングし、各要素を3倍に変換する。
  • 結果を出力する。
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {10, 15, 20, 25, 30, 35, 40};
    std::vector<int> result;

    // 偶数をフィルタリングして3倍に変換
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(result),
                 [](int num) { return num % 2 == 0; });
    std::transform(result.begin(), result.end(), result.begin(),
                   [](int num) { return num * 3; });

    std::cout << "Filtered and transformed numbers: ";
    for (int num : result) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

問題3: ファイルのフィルタリング

次の要件を満たすプログラムを作成してください。

  • テキストファイル input.txt からデータを読み込む。
  • 各行に “error” という単語が含まれている行のみをフィルタリングする。
  • フィルタリングされた行を output.txt に書き込む。
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>

int main() {
    std::ifstream inputFile("input.txt");
    std::ofstream outputFile("output.txt");
    std::vector<std::string> lines;
    std::string line;

    if (inputFile.is_open() && outputFile.is_open()) {
        while (std::getline(inputFile, line)) {
            lines.push_back(line);
        }
        inputFile.close();

        std::vector<std::string> filteredLines;
        std::copy_if(lines.begin(), lines.end(), std::back_inserter(filteredLines),
                     [](const std::string& line) {
                         return line.find("error") != std::string::npos;
                     });

        for (const auto& filteredLine : filteredLines) {
            outputFile << filteredLine << std::endl;
        }
        outputFile.close();
    } else {
        std::cerr << "ファイルを開くことができませんでした。" << std::endl;
    }

    return 0;
}

問題4: 並行処理を使ったパイプライン処理

次の要件を満たすプログラムを作成してください。

  • 整数ベクター numbers を初期値 {5, 10, 15, 20, 25, 30, 35, 40, 45, 50} で初期化する。
  • 偶数をフィルタリングし、各要素を2倍に変換する。
  • 並行処理を使用して、フィルタリングと変換を別々のスレッドで実行する。
  • 結果を出力する。
#include <iostream>
#include <vector>
#include <algorithm>
#include <thread>
#include <future>

std::vector<int> filterEvenNumbers(const std::vector<int>& input) {
    std::vector<int> output;
    std::copy_if(input.begin(), input.end(), std::back_inserter(output),
                 [](int num) { return num % 2 == 0; });
    return output;
}

std::vector<int> doubleNumbers(const std::vector<int>& numbers) {
    std::vector<int> output = numbers;
    std::transform(output.begin(), output.end(), output.begin(),
                   [](int num) { return num * 2; });
    return output;
}

int main() {
    std::vector<int> numbers = {5, 10, 15, 20, 25, 30, 35, 40, 45, 50};

    auto filterFuture = std::async(std::launch::async, filterEvenNumbers, numbers);
    auto filteredNumbers = filterFuture.get();

    auto transformFuture = std::async(std::launch::async, doubleNumbers, filteredNumbers);
    auto transformedNumbers = transformFuture.get();

    std::cout << "Filtered and transformed numbers: ";
    for (int num : transformedNumbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

これらの演習問題を通じて、C++のラムダ式とデータストリーム処理の実践的なスキルを磨いてください。次のセクションでは、この記事の内容をまとめます。

まとめ

本記事では、C++のラムダ式とデータストリーム処理について詳しく解説しました。ラムダ式は、コードを簡潔かつ効率的に記述するための強力なツールであり、データストリーム処理と組み合わせることで、複雑なデータ操作をシンプルに実装できます。

具体的には、ラムダ式の基本的な構文やキャプチャリストの使い方、データストリーム処理の基礎から始まり、フィルタリングやデータ変換、パイプライン処理、並行処理の実装方法を具体的なコード例とともに紹介しました。また、演習問題を通じて、学んだ内容を実践的に確認する機会を提供しました。

これらの技術を活用することで、C++でのデータ処理の効率を大幅に向上させることができます。この記事が、皆さんのプログラミングスキルの向上に役立つことを願っています。

コメント

コメントする

目次