nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

C++で抽出・変換・集計処理を行う

はじめに

JavaやC#の関数型プログラミングをC++で行ってみました。

数列から、奇数を抽出して、その結果に対して2倍し、統計します。

コード

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
#include <numeric>
using namespace std;

void print_num(int num){
    std::cout<< num << std::endl;
}

int filter_map_fold(const std::vector<int>& nums)
{
    std::vector<int> odds;
    
    // filter(抽出)(奇数を抽出)
    std::copy_if(nums.begin(), nums.end(), std::back_inserter(odds), [](int num){ return num % 2;});
    
    // 表示内容を確認
    std::for_each(odds.begin(), odds.end(), print_num);
    
    // Map(変換)(2倍する)
    std::vector<int> doubles;
    std::transform(odds.begin(), odds.end(), std::back_inserter(doubles), [](int num){ return num * 2; } );
    
    // 表示内容を確認
    std::for_each(doubles.begin(), doubles.end(), print_num);
    
    // fold(集計)(統計する)
    int result = std::accumulate(doubles.begin(), doubles.end(), 0, [](int acc, int n) { return acc + n;});
    
    return result;
}

int main(void){
    std::vector<int> nums = {1,2,3,4,5,6,7,8,9,10};
    
    int result = filter_map_fold(nums);
    
    std::cout << "result : " << result << std::endl;
}

Linq for C++

拡張ライブラリであるLinq for C++を使用して、上記のコードを書き直してみます。 コード行数も抑えながらシンプルに記載することができました。

LINQ for C++ (cpplinq) is an extensible C++11 library of higher-order functions for range manipulation. cpplinq draws inspiration from LINQ for C#.

https://archive.codeplex.com/?p=cpplinq

#include <iostream>
#include <vector>
#include "cpplinq.hpp"

void print_num(int num){
    std::cout<< num << std::endl;
}

int filter_map_fold(const std::vector<int>& nums)
{
    return cpplinq::from(nums)
        >> cpplinq::where([](int n) { return (n % 2) != 0; })
        >> cpplinq::select([](int n) { return n * 2; })
        >> cpplinq::aggregate(0, [](int result, int n) { return result + n; });
}

int main(void){
    std::vector<int> nums = {1,2,3,4,5,6,7,8,9,10};
    
    int result = filter_map_fold(nums);
    
    std::cout << "result : " << result << std::endl;
}

参考資料

クロージャデザインパターン

std::functionについて [C++]

はじめに

functionクラステンプレートは、パラメータの型リストArgTypes...、戻り値の型Rに合致する、あらゆる関数ポインタ、関数オブジェクト、メンバ関数ポインタ、メンバ変数ポインタを保持できるクラスです。

使用例

#include <iostream>
#include <functional>

using namespace std;

// 通常の関数
int add(int a, int b)
{
    return a + b;
}

// 関数オブジェクト
class Functor
{
    public:
        int operator() (int a, int b)
        {
            return a + b;
        }
};

// クラスを宣言
class Calculater
{
    public:
        static int AddMethod(int a, int b)
        {
            return a + b;
        }
};


int main(void){
    std::function<int (int,int)> f;
    
    auto func = [](int a, int b)->int { return a + b; };
    
    f = func;
    // ラムダ式を代入
    std::cout << "To assign Class template std::function to lambd : "<< f(1, 2) << std::endl;
    
    f = nullptr;
    f = &add;
    // 関数ポインタ代入
    std::cout << "To assign Class template std::function to function pointer : "<< f(3, 4) << std::endl;
    
    f = nullptr;
    f = Functor();
    // 関数オブジェクト代入
    std::cout << "To assign Class template std::function to Functor : "<< f(5, 6) << std::endl;
    
    f = nullptr;
    
    Calculater calculate;
    
    f = calculate.AddMethod;
    // クラスの静的メンバ関数を代入
    std::cout << "To assign Class template std::function to Class Member Method : "<< f(7, 8) << std::endl;
}
To assign Class template std::function to lambd : 3
To assign Class template std::function to function pointer : 7
To assign Class template std::function to Functor : 11
To assign Class template std::function to Class Member Method : 15

ラムダ式攻略 [C++]

はじめに

「ラムダ式(lambda expressions)」は、簡易的な関数オブジェクトをその場で定義するための機能です。

この機能によって、「高階関数(関数を引数もしくは戻り値とする関数)」をより使いやすくできます。

仕様

構文

[キャプチャリスト](パラメータリスト) mutable 例外仕様 属性 -> 戻り値の型 { 関数の本体 }

`

  • パラメータ、mutable、例外仕様、属性、戻り値の型のいずれも明示的に指定しない場合は、パラメータリストの丸カッコを省略できる
  • コピーキャプチャした変数を書き換える必要がない場合、mutableを省略できる
  • 例外仕様を指定しない場合、それを省略できる
  • 属性を指定しない場合、それを省略できる
  • 戻り値の型を推論に任せる場合、->記号および戻り値の型を省略できる

もっとも短いラムダ式は、以下のようになります。 []{}

キャプチャ

ラムダ式には、ラムダ式の外にある自動変数を、ラムダ式内で参照できるようにする「キャプチャ(capture)」という機能があります。

キャプチャは、ラムダ導入子(lambda-introducer)と呼ばれる、ラムダ式の先頭にある[ ]ブロックのなかで指定します。

キャプチャには、コピーキャプチャと参照キャプチャがあり、デフォルトでどの方式でキャプチャし、個別の変数をどの方式でキャプチャするかを指定できます。

キャプチャ記法 説明
[&] デフォルトで環境にある変数を参照して、ラムダ式のなかで使用する
[=] デフォルトで環境にある変数をコピーして、ラムダ式のなかで使用する
[&x] 変数xを参照して、ラムダ式のなかで使用する
[x] 変数xをコピーして、ラムダ式のなかで使用する
[&, x] デフォルトで参照キャプチャ、変数xのみコピーして、ラムダ式のなかで使用する
[=, &x] デフォルトでコピーキャプチャ、変数xのみ参照して、ラムダ式のなかで使用する
[this] *thisのメンバを参照して、ラムダ式のなかで使用する
[this, x] *thisのメンバを参照し、変数xのみコピーして、ラムダ式のなかで使用する

実際にラムダ式を使用してみる

ラムダ式を変数に格納する。引数を与える。

ラムダ式を変数に格納します。また、ラムダ式に引数を与えます。

#include <iostream>
int main(void){

    auto func1 = [](){ std::cout << "Paramter No Set." << std::endl; };
    
    func1();
    
    auto func2 = [](int x){ std::cout << "Paramter Set : " << x << std::endl; };
    
    func2(5);
    
    return 0;
}
Paramter No Set.
Paramter Set : 5

関数の引数内にラムダ式を適用する

ラムダ式は関数の引数にも適用可能です。

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

using namespace std;

int main(void)
{
    std::vector<int> v1 = {1,2,3,4};
    
    // 関数の引数内に関数作成
    std::for_each(v1.begin(), v1.end(), [](int n){ std::cout << "Paramter : " << n << std::endl; });
}
Paramter : 1
Paramter : 2
Paramter : 3
Paramter : 4

キャプチャを使用する

キャプチャを使用することで、ラムダ式外にある変数を使用可能です。 以下は、コピーキャプチャを行う場合です。

ラムダ式の中で、キャプチャした変数を書き換えることができません。書き換える場合は、mutable指定子が必要です。

#include <iostream>

int main(void)
{
    int a = 1;
    int b = 2;
    
    auto func = [a, b](){ return a + b; };
    
    std::cout << "a : " << a << std::endl;
    std::cout << "b : " << b << std::endl;
    std::cout << "a + b : " << func() << std::endl;
}
a : 1
b : 2
a + b : 3

ラムダ式の中で値を書き換える場合は、 [&a, &b]のように記載します。

#include <iostream>

int main(void)
{
    int a = 1;
    int b = 2;
    
    auto func = [&a, &b]()
    {
        a = 5;
        b = 10;
        return a + b;
    };
    
    std::cout << "a : " << a << std::endl;
    std::cout << "b : " << b << std::endl;
    std::cout << "a + b : " << func() << std::endl;
}

変数を羅列するのが面倒なら=と書きます。すべての外側の変数がリードオンリーで見えるようになります。

auto f_offset = [=](int n){ return a * n + b; };

初期化キャプチャ [C++14]

C++14から、キャプチャで変数を初期化できるようになりました。

#include <iostream>

int main(void)
{
    int a = 1;
    int b = 2;
    
    auto func = [a = 5, b = 10]()
    {
        return a + b;
    };
    
    std::cout << "a : " << a << std::endl;
    std::cout << "b : " << b << std::endl;
    std::cout << "a + b : " << func() << std::endl;
}
a : 1
b : 2
a + b : 15

ジェネリックラムダ (C++14)

ジェネリックラムダ(generic lambdas)は、C++11のラムダ式を拡張して、パラメータにテンプレートを使用できるようにした機能です。

auto plus = [](auto a, auto b) { return a + b; };

このラムダ式は、以下のような関数呼び出し演算子を持つ関数オブジェクトを生成します。

template <class T1, class T2>
auto operator()(T1 a, T2 b) const
{
  return a + b;
}
#include <iostream>
#include <string>

using namespace std::string_literals;

int main(void){
    
  auto plus = [](auto a, auto b) { return a + b; };
  // ラムダ式のパラメータ型をautoにすることで、
  // 任意の型をパラメータとして受け取れる
  int result1 = plus(3, 2);
  std::string result2 = plus("Hello"s, "World"s);

  std::cout << result1 << std::endl;
  std::cout << result2 << std::endl;
}
5
HelloWorld

上記コードでは、sリテラルを使用しています。 文字列リテラルを受け取り、各文字型のbasic_stringオブジェクトを構築します。 std::string型に文字列を格納する際に、文字列を厳密に指定可能です。

参考リンク

C++ でのラムダ式 | Microsoft Docs

ラムダ式 - cpprefjp C++日本語リファレンス

クロージャデザインパターン

Pythonの勉強サイトまとめ

はじめに

Pythonを勉強する上で役に立ちそうなサイトを紹介します。

機械学習

Python Django入門

コピーコンストラクタと代入演算子の禁止方法

はじめに

C++11の文法のdeleteを使うことで、クラスのインスタンスのコピーや代入を禁止することができます。

#include <iostream>

class Person
{
public:
    // 参照
    const std::string m_name;
    int m_age;
    
    Person(const std::string name, int age) : m_name(name), m_age(age)
    {
    }
    
    // コピー禁止 (C++11)
    Person(const Person &) = delete;
    Person &operator=(const Person &) = delete;
};

int main(void){
    Person person1("Mickey", 15);
    Person person2("Tom", 30);
    
    // 名前はconstであるため、不変。年齢だけ変更されます。
    //person1 = person2;
    std::cout << person1.m_name << std::endl;
    std::cout << person1.m_age << std::endl;
    
}

参考リンク

const メンバ変数がいるときの代入演算子

はじめに

自分で作成したクラスのメンバ変数が、constメンバ変数であった場合、デフォルトの代入演算子では正しく代入できません。

そのため、代入演算子を自分で実装します。

例えば、nameとageというメンバ変数があったとしまして、ageだけconstメンバ変数の場合は、代入演算子でageの値のみ変更させるようにします。

コード

#include <iostream>

class Person
{
public:
    // 参照
    const std::string m_name;
    int m_age;

    // コンストラクタ
    Person(const std::string name, int age) : m_name(name), m_age(age)
    {
    }
    
    // 代入演算子
    Person &operator=(const Person &obj){
        // ageだけコピー。nameはconstなのでコピーしない
        this->m_age = obj.m_age;
        return *this;
    }
};

int main(void){
    Person person1("Mickey", 15);
    Person person2("Tom", 30);
    
    // 名前はconstであるため、不変。年齢だけ変更されます。
    person1 = person2;
    std::cout << person1.m_name << std::endl;
    std::cout << person1.m_age << std::endl;
    
}
Mickey
30

参考リンク

メンバイニシャライザ(初期化リスト)[C++]

はじめに

メンバイニシャライザ(初期化リスト)を使うと、クラスのメンバ変数を代入ではなく、初期化することができます。

コンストラクタの定義

X::X() : メンバ変数名A(初期化子A), メンバ変数名B(初期化子B), ...
{
}

厳密には、メンバ変数1つ1つの初期化を行っている部分がメンバイニシャライザで、全体としてはメンバイニシャライザリストと呼びます

重要ポイント

  • メンバイニシャライザで記述した初期化子は、メンバ変数のコンストラクタの実引数として渡されます
  • メンバ変数のコンストラクタが、引数無しで呼び出せるのであれば、メンバイニシャライザに記述しなくても結果は変わりませんが、空の ( ) を指定して、明示的に初期化できます
  • メンバ変数が const の場合、メンバイニシャライザで初期化しなければなりません
#include <iostream>
#include <string>
#include <iostream>


class Person
{
public:
    std::string name;
    int age;
    
    Person(std::string name, int age) : name(name), age(age){
        
    }
};

int main(void){
    
    // Your code here!
    Person person("Mickey", 15);
    
    std::cout << person.name << std::endl;
    std::cout << person.age << std::endl;
    
}

参考リンク

コンストラクタとデストラクタ | Programming Place Plus Modern C++編【言語解説】 第7章