変更を行う STLアルゴリズム | Programming Place Plus C++編【標準ライブラリ】 第20章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 と更新されており、今後も 3年ごとに更新されます。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

この章の概要

この章の概要です。


変更を行う STLアルゴリズム

この章では、STLアルゴリズムの中から、対象の要素の値を変更するものを取り上げます。

要素の値が変更される STLアルゴリズムは、連想コンテナに対しては使用できません。これは、連想コンテナが、要素をソートした状態で管理するためです。値が勝手に変更されると、順序関係の管理が破綻してしまいます。

fill

fill関数は、指定の範囲内の各要素に、指定の値を代入します。

namespace std {
    template <typename ForwardIterator, typename T>
    void fill(ForwardIterator first, ForwardIterator last, const T& value);
}

第3引数に指定した値が、各要素へ(=演算子によって)代入されます。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v(5);

    std::fill(v.begin(), v.end(), 9);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

9
9
9
9
9

fill_n

fill関数は、指定した位置以降の各要素に、指定の値を代入します。

namespace std {
    template <typename OutputIterator, typename Size, typename T>
    void fill_n(OutputIterator first, Size n, const T& value);
}

第1引数に範囲の開始位置を指定し、第2引数に書き込む要素の個数を指定します。当然、相手先の有効な範囲を超えないように注意する必要があります。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v(5);

    std::fill_n(v.begin(), v.size(), 9);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

9
9
9
9
9


generate

generate関数は、指定の範囲内の各要素に、指定の関数や関数オブジェクトによって生成された値を代入します。

namespace std {
    template <typename ForwardIterator, typename Generator>
    void generate(ForwardIterator first, ForwardIterator last, Generator gen);
}

fill関数との違いは、代入する値を、直接指定するか、関数によって生成するかです。ここで使う値生成用の関数は、引数を持たず、戻り値で結果を返すように実装します。

次のサンプルプログラムは、std::rand関数によって生成した値を代入していきます。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v(5);

    std::generate(v.begin(), v.end(), std::rand);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

41
18467
6334
26500
19169

この結果からも分かるように、要素1つごとに、第3引数の関数を呼び出します。したがって、fill関数のように、書き込み先の範囲内の全要素が同じ値になるとは限りません。

generate_n

generate_n関数は、指定した位置以降の各要素に、指定の関数や関数オブジェクトによって生成された値を代入します。

namespace std {
    template <typename ForwardIterator, typename Size, typename Generator>
    void generate_n(OutputIterator first, Size n, Generator gen);
}

第1引数に範囲の開始位置を指定し、第2引数に書き込む要素の個数を指定します。当然、相手先の有効な範囲を超えないように注意する必要があります。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v(5);

    std::generate_n(v.begin(), v.size(), std::rand);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

41
18467
6334
26500
19169


copy

copy関数は、指定した範囲内の要素を、別の範囲へコピーします。

namespace std {
    template <typename InputIterator, typename OutputIterator>
    OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);
}

第1、第2引数がコピー元の範囲で、第3引数がコピー先の範囲の先頭位置です。第3引数は、コピー元の範囲内に含まれていてはいけません。戻り値は、最後にコピーされた要素の次を指すイテレータです。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v(5);
    std::vector<int> v2(5);

    std::fill(v.begin(), v.end(), 9);
    std::copy(v.begin(), v.end(), v2.begin());

    std::for_each(v2.begin(), v2.end(), Println);
}

実行結果

9
9
9
9
9

コピー先に十分な領域があることが前提になることに注意してください。この要件を満たすことが面倒なケースもありますが、その場合には、挿入イテレータ(第25章)という仕組みを使うと簡単になります。

#include <algorithm>
#include <iostream>
#include <vector>
#include <iterator>  // std::back_inserter のために必要

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v(5);
    std::vector<int> v2;  // 空

    std::fill(v.begin(), v.end(), 9);
    std::copy(v.begin(), v.end(), std::back_inserter(v2));  // 挿入イテレータにより push_back による追加が行われる

    std::for_each(v2.begin(), v2.end(), Println);
}

実行結果

9
9
9
9
9

copy_backward

copy_backward関数は、指定した範囲内の要素を、後ろにある要素から順に、別の範囲へコピーします。

namespace std {
    template <typename BidirectionalIterator1, typename BidirectionalIterator2>
    BidirectionalIterator2 copy_backward(BidirectionalIterator1 first, BidirectionalIterator1 last, BidirectionalIterator2 result);
}

第1、第2引数がコピー元の範囲で、第3引数がコピー先の範囲の末尾の要素が来る位置です。第3引数は、コピー元の範囲内に含まれていてはいけません。戻り値は、最後にコピーされた要素の次を指すイテレータです。

copy関数と同様に、コピー先に十分な領域が必要であることに注意してください

コピー元範囲の後ろ側にある要素を、コピー先範囲へコピーし、それを前方の要素に向かって繰り返します。この動作のおかげで、次のサンプルプログラムのように、コピー元とコピー先の範囲に重複があっても正しい結果を得られます。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    // v[0]~v[2] の要素を v[2]~v[4] へコピーする
    std::copy_backward(v.begin(), v.begin() + 3, v.begin() + 5);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

0
1
0
1
2


transform

transform関数は、指定した範囲内の要素に、何らかの変換を加えながら、別の範囲へコピーします。なお、コピー先とコピー元が同一であっても構いません

namespace std {
    template <typename InputIterator, typename OutputIterator, typename UnaryOperation>
    OutputIterator transform(InputIterator first, InputIterator last, OutputIterator result, UnaryOperation op);

    template <typename InputIterator1, typename InputIterator2, typename OutputIterator, typename BinaryOperation>
    OutputIterator transform(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, OutputIterator result, BinaryOperation binary_op);
}

1つ目の形式では、第1、2引数でコピー元の範囲を指定し、第3引数でコピー先の先頭の位置を指定します。コピーが行われるとき、(コピー元の要素を elem とすると)、op(elem) を呼び出して、その戻り値をコピー先へ代入します。戻り値は、最後にコピーされた要素の次を指すイテレータです。

copy関数と同様に、コピー先に十分な領域が必要であることに注意してください

次のサンプルプログラムは、文字列の前後を [] で囲んでコピーします。

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

namespace {
    const std::string Quate(const std::string& s)
    {
        return std::string("[") + s + "]";
    }

    void Println(const std::string& elem)
    {
        std::cout << elem.c_str() << std::endl;
    }
}

int main()
{
    std::vector<std::string> v;
    v.push_back("xx");
    v.push_back("xxxxx");
    v.push_back("xxx");

    std::vector<std::string> result(3);
    std::transform(v.begin(), v.end(), result.begin(), Quate);

    std::for_each(result.begin(), result.end(), Println);
}

実行結果

[xx]
[xxxxx]
[xxx]

2つ目の形式では、第1、2引数でコピー元の範囲を指定し、第3引数でもう1つのコピー元範囲の先頭を指定します。第4引数に、コピー先の先頭の位置を指定します。コピーが行われるとき、2つのコピー元範囲から1つずつ要素を得て(それぞれ elem1、elem2 とします)、binary_op(elem1, elem2) を呼び出して、その戻り値をコピー先へ代入します。

次のサンプルプログラムは、2つの文字列を比較して、長い方をコピーします。

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

namespace {
    const std::string ChooseLonger(const std::string& s1, const std::string& s2)
    {
        return s1.length() < s2.length() ? s2 : s1;
    }

    void Println(const std::string& elem)
    {
        std::cout << elem.c_str() << std::endl;
    }
}

int main()
{
    std::vector<std::string> v1;
    v1.push_back("xx");
    v1.push_back("xxxxx");
    v1.push_back("xxx");

    std::vector<std::string> v2;
    v2.push_back("yyyy");
    v2.push_back("yyy");
    v2.push_back("yy");

    std::vector<std::string> result(3);
    std::transform(v1.begin(), v1.end(), v2.begin(), result.begin(), ChooseLonger);

    std::for_each(result.begin(), result.end(), Println);
}

実行結果

yyyy
xxxxx
xxx

replace

replace関数は、指定した範囲内の要素のうち、指定の値と等しい要素を、別の値に置き換えます。

namespace std {
    template <typename ForwardIterator, typename T>
    void replace(ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value);
}

第3引数に指定した値と等しい(==演算子による比較)要素を、第4引数の値で置き換えます。

次のサンプルプログラムは、値が 0 の要素を、1 に置き換えています。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    std::replace(v.begin(), v.end(), 0, 1);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

1
1
2
3
4

replace_if

replace_if関数は、指定した範囲内の要素のうち、指定の条件を満たす要素を、別の値に置き換えます。

namespace std {
    template <typename ForwardIterator, typename Predicate, typename T>
    void replace_if(ForwardIterator first, ForwardIterator last, Predicate pred, const T& new_value);
}

第3引数に指定した単項叙述関数が true を返す要素について、第4引数の値で置き換えます。

次のサンプルプログラムは、値が 0以下の要素を、1 に置き換えています。

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

namespace {
    bool ZeroOrLess(int elem)
    {
        return elem <= 0;
    }

    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(-2);
    v.push_back(-1);
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);

    std::replace_if(v.begin(), v.end(), ZeroOrLess, 1);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

1
1
1
1
2

replace_copy

replace_copy関数は、指定した範囲内の要素をコピーしますが、指定の値と等しい要素については、別の値に置き換えます。つまり、replace関数copy関数を組み合わせたものです。

namespace std {
    template <typename InputIterator, typename OutputIterator, typename T>
    OutputIterator replace_copy(InputIterator first, InputIterator last, OutputIterator result, const T& old_value, const T& new_value);
}

第1、2引数がコピー元の範囲、第3引数がコピー先の先頭です。第4引数に指定した値と等しい要素については、第5引数の値に置き換えられます。

次のサンプルプログラムは、値が 0 の要素を、1 に置き換えて、コピーを行っています。

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

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    std::vector<int> result(5);
    std::replace_copy(v.begin(), v.end(), result.begin(), 0, 1);

    std::for_each(result.begin(), result.end(), Println);
}

実行結果

1
1
2
3
4

replace_copy_if

replace_copy_if関数は、指定した範囲内の要素をコピーしますが、指定の条件を満たす要素については、別の値に置き換えます。つまり、replace_if関数copy関数を組み合わせたものです。

namespace std {
    template <typename InputIterator, typename OutputIterator, typename Predicate, typename T>
    OutputIterator replace_copy_if(InputIterator first, InputIterator last, OutputIterator result, Predicate pred, const T& new_value);
}

第1、2引数がコピー元の範囲、第3引数がコピー先の先頭です。第4引数に指定した単項叙述関数が true を返す要素については、第5引数の値に置き換えられます。

次のサンプルプログラムは、値が 0以下の要素を、1 に置き換えて、コピーを行っています。

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

namespace {
    bool ZeroOrLess(int elem)
    {
        return elem <= 0;
    }

    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(-2);
    v.push_back(-1);
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);

    std::vector<int> result(5);
    std::replace_copy_if(v.begin(), v.end(), result.begin(), ZeroOrLess, 1);
    std::for_each(result.begin(), result.end(), Println);
}

実行結果

1
1
1
1
2

swap_ranges

swap_ranges関数は、指定した2つの範囲内にある要素同士をまとめて交換します。この関数については、第15章で解説しています。


練習問題

問題① fill/fill_n と generate/generate_n について、その違いを説明してください。

問題② copy関数がどのように実装されているか、想像して自力の実装を書いてみてください。

問題③ 正負が混じった int型の値が格納された std::list があるとき、すべての要素の符号を反転させる処理を STLアルゴリズムを使って記述してください。


解答ページはこちら

参考リンク


更新履歴

’2018/1/5 コンパイラの対応状況について、対応している場合は明記しない方針にした。

’2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

’2017/3/25 VisualC++ 2017 に対応。

’2016/11/23 新規作成。



前の章へ (第19章 readonly な STLアルゴリズム)

次の章へ (第21章 削除の STLアルゴリズム)

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る