HOWTO · JavaScript

JavaScript で 2つの配列を比較する

このチュートリアルでは、JavaScript で 2つの配列を比較するためのいくつかのメソッドを紹介します。

このページの内容

JavaScript の === 演算子は配列の内容ではなく同一性を比較するため、同じ内容で別々に作成した2つの配列は、この比較では等しくなりません。内容を比較するには、順序と要素の値についてルールを決めます。同じ順序のプリミティブ値なら、まず長さを比較し、次に対応する各要素を比較します。JSON.stringify() は、そのシリアライズ規則がデータに合う場合に限って使い、順序が重要でない場合はコピーをソートして比較します。これらの基本的な方法では、入れ子の配列やオブジェクトは構造ではなく参照で比較されます。

2つの配列が等しいとはどういう意味か

まず、この用途で「等しい」が何を意味するかを決めます。順序を考慮する比較では、長さが同じで、すべてのインデックスの要素が一致する必要があります。順序を考慮しない比較では、配列をコレクションとして扱いますが、重複する値も数えなければなりません。たとえば、[1, 1, 2] と [1, 2, 2] は、含まれる異なる値が同じというだけでは等しくありません。

要素の等しさを浅く判定するのか、構造まで調べるのかも決めてください。数値や文字列などのプリミティブ値を比較するには、厳密等価演算が適しています。オブジェクトや入れ子の配列では、厳密等価演算は両方の位置が同じオブジェクトを参照しているかを調べます。プロパティや入れ子の内容は比較しません。

ループまたは Array.prototype.every() で順序を保って配列を比較する

独立した関数を使えば、組み込みの Array.prototype を変更せずに済みます。この方法では、まず長さを確認し、次に対応する値を厳密等価演算で比較します。順序を考慮する浅い比較です。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

function arraysEqual(left, right) {
  if (left.length !== right.length) return false;
  return left.every((value, index) => value === right[index]);
}

const first = ["red", "green", "blue"];
const second = ["red", "green", "blue"];
console.log(arraysEqual(first, second));
true

値が異なる場合や長さが異なる場合も、この関数は一致しないと判定します。次の呼び出し例は未実行です。

function arraysEqual(left, right) {
  if (left.length !== right.length) return false;
  return left.every((value, index) => value === right[index]);
}

console.log(arraysEqual(["red", "green"], ["red", "blue"]));
console.log(arraysEqual(["red", "green"], ["red", "green", "blue"]));

最初の呼び出しは2番目の要素が異なるため false を返し、2番目の呼び出しは配列の長さが異なるため false を返します。

各インデックスの比較方法を調整したい場合は、for ループでも同じルールを表現できます。長さが異なれば不一致とし、最初の不一致で false を返し、すべての位置が一致したら true を返します。Array.prototype.every() はより簡潔に書け、コールバックが false を返すとすぐに処理を止めます。疎な配列では、値が割り当てられていないスロットをスキップするため、各インデックスを読み取るループとは結果が異なることがあります。この違いが問題になる場合は、密な配列を使うか、空きスロットの扱いを決めてください。

JSON.stringify() で配列を比較する

JSON 互換の値を含む配列なら、シリアライズした形式を比較する方法は簡潔です。JSON.stringify(left) === JSON.stringify(right) のように書けます。この方法は順序を考慮するため、[1, 2] と [2, 1] では比較結果が異なります。シリアライズには固有の規則もあります。配列内の undefined など、通常どおり表現されない値は null としてシリアライズされ、オブジェクトの undefined 値を持つプロパティは省略されます。シリアライズ可能なプロパティが同じ別々のオブジェクトは同じ文字列になることがありますが、これは汎用的な深い等価性の定義ではありません。プロパティの順序や独自のシリアライズ処理も結果に影響することがあります。データの契約がこれらの規則に合う場合にだけ使い、あらゆるデータに使える等価判定と考えたり、大きな配列でより高速だと決めつけたりしないでください。

順序に関係なく配列を比較する

順序が重要でなく、要素がプリミティブ値の場合は、浅いコピーをソートして、並べ替えた各位置の値を比較します。入力配列に直接 sort() を呼び出すと内容が変更されるため、先に slice() でコピーします。既定のソートは文字列表現を基準に比較します。数値など、別の順序が必要な値には比較関数を指定してください。ソートには追加の処理が必要で、型が混在する場合は順序のルールも明示する必要があります。長さと要素を確認することで、重複数も比較できます。

プリミティブな文字列の場合は、コピーをソートし、並べ替え後の各値を厳密等価演算で比較します。次の完全な例は未実行で、比較結果と元の2つの配列を出力します。

このセクションのコードと表示されるラベルや出力は英語のままです。

function arraysHaveSameValues(left, right) {
  if (left.length !== right.length) return false;
  const sortedLeft = left.slice().sort();
  const sortedRight = right.slice().sort();
  return sortedLeft.every((value, index) => value === sortedRight[index]);
}

const left = ["pear", "apple", "pear"];
const right = ["pear", "pear", "apple"];
console.log(arraysHaveSameValues(left, right));
console.log(left);
console.log(right);

この配列では比較結果は true となり、コピーをソートしても入力配列は元の順序のままです。両方のコピーをソートしてすべての位置を比較するため、重複数も保たれます。ある値の個数が異なる配列は一致しません。ここで示した既定のソートと厳密等価演算は文字列を対象としています。型が混在する値には、それぞれの型に合った比較関数と等価性のルールが必要です。オブジェクトを比較する場合は、意図する等価性を表すキーを定義するか、オブジェクト向けの照合方法を使ってください。既定のソートだけではオブジェクトの等価性は判定できません。

入れ子の配列とオブジェクトを扱う

順序を考慮する比較例とソートの例では、各要素に厳密等価演算を使います。そのため、たとえば [{ id: 1 }] のように別々に作成された値は、見た目のプロパティが同じでも、その位置では等しくありません。入れ子の配列も同様です。構造上の等しさが必要なら、対応するすべての型の動作を定義した再帰的な比較を使うか、用途に合った深い等価性比較用のライブラリを選びます。利用する前に、日付、Map、Set、プロトタイプ、循環参照、特殊な数値をどう扱うかを決めてください。深い等価性比較ツールによって、実装しているルールは異なる場合があります。

JSON 互換であることが分かっているデータなら、上記の制約を踏まえたうえで、シリアライズを簡潔な構造比較に使える場合があります。それ以外の場合は、等価性のルールを明確にし、アプリケーションで重要なケースをテストしてください。実用上は、必要な順序と値の意味に合う最も単純な方法を選びます。順序を考慮するプリミティブ値には every() またはループを、制約された JSON データにはシリアライズを、それ以外の要件には意図を明確にした照合方法や深い等価性比較を使います。