2つの配列が一致しているか調べる | Programming Place Plus C言語編 逆引き

トップページC言語編逆引き

このページの概要

以下は目次です。

目的

型が同じ2つの配列があるとき、それぞれの要素の値がすべて同じであるかどうかを調べたいとします。

ここでは、文字列ではない配列を対象にします。文字列の比較であれば、標準ライブラリの strcmp関数を使うだけで済みます。

C言語では、配列の要素が一致しているかどうかを調べるために、「==」や「!=」を使うことはできません。配列を比較しようとすると、それぞれがポインタに変換され、アドレスを比較していることになります(第32章)。

実際にやってみると、次のようになります。

#include <stdio.h>

int main(void)
{
    int array1[] = {0, 1, 2, 3, 4};
    int array2[] = {0, 1, 4};
    int array3[] = {0, 1, 2, 3, 4};
    int array4[] = {0, 1, 2, 3, 4, 5};

    printf("%d\n", array1 == array1);
    printf("%d\n", array1 == array2);
    printf("%d\n", array1 == array3);
    printf("%d\n", array1 == array4);    
}

実行結果:

1
0
0
0

array1 と、array1自身を含む4つの配列を比較しています。

array1 と array1 を比較したときは、当然メモリアドレスが同じなので 1(真)になっていますが、それ以外はすべて 0(偽)になっています。

望む結果はこうです。

1
0
1
0

自分自身との比較は当然一致しているとみなすべきですし、同じ内容をもつ array3 との比較も 1(真)にならなければなりません。array4 のように、途中までは一致しているケースが、誤って真にならないように注意しましょう。

方法①(for文で1つずつ代入する)

一番確実な方法は、for文で1要素ずつ比較することです。

冒頭のサンプルプログラムのように、1文で簡単に済ませることは無理があるので、関数にしてみます。

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

#define SIZE_OF_ARRAY(array)    (sizeof(array)/sizeof(array[0]))

/*
    2つの配列の要素がすべて一致しているか調べる

    引数
        array1: 対象の配列
        size1:  array1 の要素数
        array2: 対象の配列
        size2:  array2 の要素数
    戻り値
        一致していたら true、不一致なら false
*/
bool array_equal(const int* array1, size_t size1, const int* array2, size_t size2)
{
    assert(array1 != NULL);
    assert(array2 != NULL);
    assert(size1 != 0);
    assert(size2 != 0);

    // 要素数が違うなら、絶対に一致しない
    if (size1 != size2) {
        return false;
    }

    for (size_t i = 0; i < size1; ++i) {
        if (array1[i] != array2[i]) {
            return false;
        }
    }
    return true;
}

int main(void)
{
    int array1[] = {0, 1, 2, 3, 4};
    int array2[] = {0, 1, 4};
    int array3[] = {0, 1, 2, 3, 4};
    int array4[] = {0, 1, 2, 3, 4, 5};

    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array1, SIZE_OF_ARRAY(array1)));
    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array2, SIZE_OF_ARRAY(array2)));
    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array3, SIZE_OF_ARRAY(array3)));
    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array4, SIZE_OF_ARRAY(array4)));
}

実行結果:

1
0
1
0

SIZE_OF_ARRAYマクロは、配列の要素数を取得するものです。詳細は「逆引き 配列の要素数を求める」を参照してください。

array_equal関数に、2つの配列(を指すポインタ)と、それぞれの要素数を渡します。一致していれば true、不一致なら false が返されます。実行結果のように、望むものが得られました。

array_equal関数の実装を見てみます。

まず、2つの配列が一致しているか調べるという話の本筋とは関係ありませんが、引数に異常がないかを assertマクロで調べています。

次に、要素数の一致を確認します。要素数が異なる配列の内容が完全一致することはあり得ないので、先に調べておきます。この判定で、array2 と array4 との比較はただちに false と判定されます。

そして、for文を使って、要素を1つ1つ比較します。さきほど要素数が一致していることを調べてあるので、ループの終了条件に使う変数は size1 でも size2 でも同じことです。

要素は「==」で比較しても構わないのですが、そうするとコードが書きにくくなるので(やってみるとわかります)、「!=」を使って不一致を調べています。不一致をみつけた時点で、false を返して終了します。

for文から終了条件によって抜け出した場合は、すべての要素が一致していたということですから、true を返して終了です。

方法②(memcmp関数を使う)

もう1つの方法は、標準ライブラリの memcmp関数を使うものです。

memcmp関数は、メモリ上の2つの範囲の内容を比較する関数で、string.h にあります。

int memcmp(const void* s1, const void* s2, size_t size);

仮引数s1、s2 が配列(へのポインタ)です。

size は比較する範囲の大きさ(バイト数)です。要素数ではないことに注意してください。

memcmp関数は本来、大小関係を調べるものですが、一致・不一致を調べるために使って問題ありません。一致していたときにだけ戻り値が 0 になる仕様なので、0 なら一致、0以外なら不一致と判定します。

プログラムは、次のようになります。

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

#define SIZE_OF_ARRAY(array)    (sizeof(array)/sizeof(array[0]))

/*
    2つの配列の要素がすべて一致しているか調べる

    引数
        array1: 対象の配列
        size1:  array1 の要素数
        array2: 対象の配列
        size2:  array2 の要素数
    戻り値
        一致していたら true、不一致なら false
*/
bool array_equal(const int* array1, size_t size1, const int* array2, size_t size2)
{
    assert(array1 != NULL);
    assert(array2 != NULL);
    assert(size1 != 0);
    assert(size2 != 0);

    // 要素数が違うなら、絶対に一致しない
    if (size1 != size2) {
        return false;
    }

    return memcmp(array1, array2, size1 * sizeof(int)) == 0;
}

int main(void)
{
    int array1[] = {0, 1, 2, 3, 4};
    int array2[] = {0, 1, 4};
    int array3[] = {0, 1, 2, 3, 4};
    int array4[] = {0, 1, 2, 3, 4, 5};

    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array1, SIZE_OF_ARRAY(array1)));
    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array2, SIZE_OF_ARRAY(array2)));
    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array3, SIZE_OF_ARRAY(array3)));
    printf("%d\n", array_equal(array1, SIZE_OF_ARRAY(array1), array4, SIZE_OF_ARRAY(array4)));
}

実行結果:

1
0
1
0

注意しなければならないことが2つあります。

まず、構造体の配列に対して使うと、誤った結果になってしまう点です。構造体のメンバとメンバの間や、最後のメンバの後ろには、パディングという余分な空き領域が入ることがあるためです(第26章)。パディングの部分に何があるかは不定ですから、ここを巻き込んだ比較を行うと、どんな結果になるか予測できません。

もう1つは、memcmp関数の第3引数に渡す大きさです。まず、要素数ではなく、バイト数であることに注意が必要ですが、そもそも s1 と s2 のどちらの大きさを指定するべきなのかという問題があります。小さい方を使ってしまうと array1 と array4 が一致しているとみなされることが想像できます。しかし、大きい方を使うと array1 の本来の範囲を超えたところにまで比較が及んでしまいます。そこに何があるのかわからないので、そのような比較は不適切です。

結局、方法① と同様、まず2つの配列の要素数の一致をみる必要があります。要素数が一致しているときにだけ memcmp関数での比較が正しく行えます(構造体でなければ)。


参考リンク


更新履歴

’2019/9/9 新規作成。



逆引きのトップページへ

C言語編のトップページへ

Programming Place Plus のトップページへ



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