📌

【JavaScript】配列比較で false になる原因と解決策

に公開

はじめに

2つの配列の内容が合っているかを確かめる際に、エラーが発生しました。
この記事では、2つの配列の要素が同じはずなのに、比較の結果が false になる原因について解説していきます。

目的

2つの配列の要素を正しく比較する方法を知ること

対象読者

  • 配列の比較方法を知りたい方
  • 配列同士の比較をしたら予期しない結果が得られた方

内容

2つの配列の要素が等しいかを確認する方法

事例

文字で構成される配列 charList が与えられるので、大文字は小文字に、小文字は大文字に変換し新しい配列を返す swapCase という関数を map 関数を使用して作成する問題で発生したエラーです。
こちらの問題は Recursion という学習サイトで実際に使用した問題です。

https://recursionist.io/dashboard/problems/439

コード

swap 関数
function swapCase(charList){
    return charList.map(char => {
        if((/[a-z]/).test(char)) return char.toUpperCase();
        else if(/[A-Z]/.test(char)) return char.toLowerCase();
    });
}
テストケース
const tests = {
    "case1": {
        "input": ['l','A','m','b','D','A'],
        "output": ['L','a','M','B','d','a']
    },
    "case2": {
        "input": ['a','E','s','P','A'],
        "output": ['A','e','S','p','a']
    },
    "case3": {
        "input": ['f','U','N','c','T','I','o','n'],
        "output": ['F','u','n','C','t','i','O','N']
    }
}

for(let [key, value] of Object.entries(tests)){
    const output = swapCase(value['input']);
    const result = output === value['output'] ? "True" : "False";
    console.log(`Test ${key}: ${result}`);
}
出力結果
Test case1: False
Test case2: False
Test case3: False

中身とデータ型を確認

コンソールに出力して中身が違うのかを確認

コンソールに出力
for(let [key, value] of Object.entries(tests)){
    const output = swapCase(value['input']);
    const result = output === value['output'] ? "True" : "False";
    console.log(`Test ${key}: ${result}`);
    console.log(` output-data: ${output}`); // 追加
    console.log(` value['output']-data: ${value['output']}`); // 追加
    console.log(` output-type: ${typeof output}`); // 追加
    console.log(` value['output']-type: ${typeof value['output']}`); // 追加
}
出力結果
Test case1: False
 output-data: L,a,M,B,d,a
 value['output']-data: L,a,M,B,d,a
 output-type: object
 value['output']-type: object
Test case2: False
 output-data: A,e,S,p,a
 value['output']-data: A,e,S,p,a
 output-type: object
 value['output']-type: object
Test case3: False
 output-data: F,u,n,C,t,i,O,N
 value['output']-data: F,u,n,C,t,i,O,N
 output-type: object
 value['output']-type: object

中身・データ型が同じなのに比較結果が False になる
→ この比較演算子 === は配列の中身(値)を見ていない

原因

result で比較した内容が、配列の値が等しいかどうかではなく、配列の参照を比較していたこと

改善前
for(let [key, value] of Object.entries(tests)){
    const output = swapCase(value['input']);
    const result = output === value['output'] ? "True" : "False"; // ←ここ
    console.log(`Test ${key}: ${result}`);
}

解決策

JSON.stringify() または toString() によって配列を文字列化し、2つの配列が等しいかどうかをチェックする

(正)値の比較
(誤)参照の比較

改善後
for(let [key, value] of Object.entries(tests)){
    const output = swapCase(value['input']);
    const result = JSON.stringify(output) === JSON.stringify(value['output']) ? "True" : "False"; // JSON.stringify() を追加
    console.log(`Test ${key}: ${result}`);
}

※ ただし、JSON.stringify() は手軽ですが、比較の際に要素の順序(配列の場合)やキーの順序(オブジェクトの場合)が異なると、論理的に同じデータであっても異なる文字列として扱われ、正しい結果が出ません。
要素の順序が比較において重要ではない場合、オブジェクトのキーの順序が保証されない場合や関数や undefined のような JSON.stringify() が無視する値を含む場合には、上記の解決法は適さず、順序を考慮しないデータの中身の厳密な検証が必要になります。

ポイント

配列同士の比較は参照の比較

データ型には、プリミティブ型とオブジェクト型があります。
配列は後者の「オブジェクト型」になります。

このケースでは、配列同士の比較、つまり、オブジェクト型の比較を行なっていると言えます。

オブジェクト型は、メモリ上のアドレスを指す参照として扱われるため、配列同士の比較(=====)は、参照(メモリ位置)の比較になります。

※ プリミティブ型とオブジェクト型の違いについては、以下の記事がわかりやすくまとまっているので、ご参照ください。

https://www.r-staffing.co.jp/engineer/entry/20230825_1

まとめ

配列はオブジェクト型であることから、その配列がどこに格納されているかを表す参照を持ちます。
そのため、配列同士の比較はデータがどこにあるかを表す参照同士の比較となってしまうため、値の確認はされません。
そこで、本記事ではオブジェクトを値に変換して、比較することで配列同士が等しいかどうかをチェックする方法について解説しました。

最後までお読みいただき、ありがとうございました。

参考URL

https://recursionist.io/dashboard/problems/439

https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Equality_comparisons_and_sameness

https://ja.javascript.info/object-copy

https://phoneappli.hatenablog.com/entry/2022/03/31/193022

https://www.r-staffing.co.jp/engineer/entry/20230825_1

Discussion