二つの配列の各要素が一致しているかを判定する - C++ プログラミング

PROGRAM


二つの配列の各要素が一致しているかを判定する

C++ の STL (Standard Template Library) には、配列の要素をひとつずつ比較する std::equal という関数が用意されています。

これに比較したい配列と範囲を、ポインタまたは イテレータ を使って指定すると、それらの配列が持っている値をひとつひとつ比較して、全てが一致していれば true を、そうでなければ false を取得できます。

 

この std::equal 関数を使うためには、あらかじめ <algorithm> ヘッダーをインクルードしておく必要があります。

#include <algorithm>

こうした上で、例えば std::vector 型の 二つの配列 v1 と v2 とがあったとき、これらの要素が一致しているかどうかは、次のようにして知ることができます。

bool equal = v1.size() == v2.size() && std::equal(v1.cbegin(), v1.cend(), v2.cbegin());

std::equal だけではないので判りにくいですけど、整理すると次の 2 つの比較結果を AND でまとめています。

v1.size() == v2.size() ふたつの配列の要素数を比較しています。
std::equal(v1.cbegin(), v1.cend(), v2.cbegin()) std::equal でふたつの配列の要素を比較しています。

std::equal ではふたつの配列を同じ範囲(長さ)で比較する必要があるので、まず最初にそもそも要素数が一致しているかを判定しています。

C++ の場合は "&&" 演算の左辺が false になった場合は右辺を評価しない性質があるので、配列のサイズが一致しなければそこで比較処理は false になって終了します。

 

要素数が同じだったときに初めて右辺の std::equal による評価が行われます。

std::equal の最初と 2 番目の引数で、比較するふたつの配列のうちの左辺に当たる最初の要素と末端(最後の要素の次)を指定します。

そして 3 番目の引数では右辺に当たる配列の最初の要素を指定します。右辺の最後の要素は指定しませんが、ここから順に左辺の要素の末端まで比較されます。

 

ちなみに std::equal は、上記のように配列と範囲だけを指定した場合、要素の値を "==" 演算子を使って比較します。

std::vector の場合、比較演算子 "==" が用意されているので、わざわざこのように比較処理を自作する必要はありませんけど、比較用の関数を合わせて指定することで、独自の一致条件で要素を比較することができます。

 

独自の一致条件を使って比較する

std::equal 関数では、一致条件を記載した関数を引数に渡すことができます。

これを使うと、標準の "==" 演算子ではなく、独自の一致条件を使って配列の要素が比較されるので、例えば name と value という 2 つの値を持つ構造体を格納した配列で各要素の name が一致するかというような判定も行えます。

 

比較関数は、次の形式のものを用意しておきます。

bool equalFunction(const T& lhs, const T& rhs);

ここで T というのは、比較対象の配列の各要素のデータ型です。

実装ではここで受け取った lhs と rhs とを使って、それらが一致した場合に true を返すようにします。

 

比較関数ができたら、それを比較したい配列と範囲と合わせて指定します。

bool equal = v1.size() == v2.size() && std::equal(v1.cbegin(), v1.cend(), v2.cbegin(), equalFunction);

こうすることで、ふたつの配列を比較関数で実装した条件を使って一致判定されるようになります。

イテレータを使って範囲を指定しているので、std::string を大文字小文字を区別しないで比較したいようなときにもこの std::equal 関数を使って、std::string を大文字小文字を区別しないで比較する で記したようにして使うことが可能です。

 

ちなみに比較関数を指定しなかった場合には "std::equal_to<T>()" が指定されたのと同じになります。この "std::equal_to<T>()" では、右辺と左辺の要素を "==" 演算しで比較した結果を返す実装になっています。

 

なお、一致しなかった場合を判定する関数として "std::not_equal_to<T>()" もありますが、たとえば "!=" 演算子のような一致しなかった場合を判定したい場合はこれは使わず、次のようにします。

bool notEqual = v1.size() != v2.size() || !std::equal(v1.cbegin(), v1.cend(), v2.cbegin(), equalFunction);

あくまでも std::not_equal_to<T> は "全てが一致しなかった場合に true, ひとつでも一致するものがあった場合に false" という判定で使うものになります。

 

条件として指定できる関数の形式

なお std::equal 関数などで指定できる判定関数には、幾つかの定義方法があるようでした。

std::equal 関数の場合は、最終的にはそれぞれの配列の各要素を引数に取って bool を返す関数を渡せばいいので、C 関数で定義するなら、次のプロトタイプ宣言になります。

bool equalFunction(const T& lhs, const T& rhs);

 

ただ、この std::equal 関数の実装例でよく見られる定義方法は、構造体またはクラスの () 演算子として定義する方法でした。

struct equalStruct

{

bool operator()(const T& lhs, const T& rhs)

{

return lhs == rhs;

}

}

そしてこれを std::equal の第 4 引数に "equalStruct()" というようにして渡します。

このとき equalStruct 構造体のインスタンスが生成されるので、構造体として定義することが、普段使いではどの程度のメリットがあるのかは判りませんが、よく見る実装方法なので覚えておくといいかもしれません。

動きとしては、std::equal で指定した時にインスタンスが生成されて、比較処理ではそのインスタンスの () 演算子の引数としてそれぞれの要素をひとつずつ渡してくれるので、() 演算子で実装した条件で一致判定を行うことができます。

最初にインスタンス化されるので、たとえば、引用符の中では大文字小文字を区別してそれ以外では区別しないような、初期状態や経過を踏まえながら複雑に判定したいようなときには使えそうですね。

 

このほかにも、ラムダ関数 を使ってその場で比較関数を定義する方法もあります。

bool equal = v1.size() == v2.size() && std::equal(v1.cbegin(), v1.cend(), v2.cbegin(), [](const T& lhs, const T& rhs){ return lhs == rhs; });

その場で実装を記載する分コードはややこしくなりがちですけど、比較関数をわざわざ別でかしこまって定義する必要がないので、ここ限りで使う関数の場合などでは見通しが良くなるかもしれません。

関数のポインタ の std::function にラムダ関数をいったん受けてからそれを std::equal で使うことで、より見通しの良いコードが書けそうです。


[ もどる ]