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

この章の概要

この章の概要です。

変更を行う 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

C++11(戻り値の変更)

C++11 になって、戻り値に変更が加わりました。

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

第2引数 n が 1以上の場合には first + n を、そうでない場合には first を返します。

この変更は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも対応されています。

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

C++11(戻り値の変更)

C++11 になって、戻り値に変更が加わりました。

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

第2引数 n が 1以上の場合には first + n を、そうでない場合には first を返します。

この変更は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも対応されています。

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

C++11 (copy_if)

C++11

C++11 で、指定の条件を満たす要素のみをコピーする copy_if関数が追加されました。

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

第1、第2引数がコピー元の範囲で、第3引数がコピー先の範囲の先頭位置です。 第3引数は、コピー元の範囲内に含まれていてはいけません
第4引数が、単項叙述関数になっていて、ここに条件を記述した関数や関数オブジェクトを渡します。 この叙述関数が true を返す要素のみがコピーの対象になります。
戻り値は、最後にコピーされた要素の次を指すイテレータです。

次のサンプルプログラムは、偶数の要素のみをコピーしています。

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

int main()
{
	using IntVector = std::vector<int>;

	IntVector v = { 0, 1, 2, 3, 4 };

	IntVector v2(5);
	auto it = std::copy_if(std::begin(v), std::end(v), std::begin(v2), [](int elem){ return elem % 2 == 0; });

	std::for_each(std::begin(v2), it, [](int elem){ std::cout << elem << std::endl; });
}

実行結果

0
2
4

copy_if関数は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも使用できます。

C++11 (copy_n)

C++11

C++11 で、指定した位置から指定した位置へ、要素の個数を指定してコピーする copy_n関数が追加されました。

namespace std {
	template <typename InputIterator, typename Size, typename OutputIterator>
	OutputIterator copy_n(InputIterator first, Size n, OutputIterator result);
}

第1引数がコピー元の先頭を指すイテレータ、第3引数がコピー先の先頭を指すイテレータです。 第2引数に、コピーする要素の個数を指定します。
戻り値は、最後にコピーされた要素の次を指すイテレータです。

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

int main()
{
	using IntVector = std::vector<int>;

	IntVector v = { 0, 1, 2, 3, 4 };

	IntVector v2(5);
	auto it = std::copy_n(std::begin(v), 3, std::begin(v2));

	std::for_each(std::begin(v2), it, [](int elem){ std::cout << elem << std::endl; });
}

実行結果

0
2
4

copy_n関数は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも使用できます。

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 について、その違いを説明して下さい。

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

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


解答ページはこちら

参考リンク

更新履歴

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

'2016/11/23 新規作成。



前の章へ

次の章へ

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

Programming Place Plus のトップページへ