JavaScript Tips | 配列ユーティリティ:配列比較

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「配列比較」

「配列比較」は、「この2つの配列は同じか?」を判定する処理です。
ここでいう「同じか?」は、基本的に次の3つを満たすことを意味します。

  1. 要素数(長さ)が同じ
  2. 各インデックスの要素が同じ
  3. 要素の順番も同じ

つまり、[1, 2, 3][1, 2, 3] は「同じ」。
[1, 2, 3][3, 2, 1] は「違う」。
[1, 2][1, 2, 3] も「違う」です。

業務では、例えばこういう場面で使います。

  • 「前回の検索条件と同じかどうか」を判定したい
  • 「選択中のIDリストが変わったかどうか」を知りたい
  • 「APIから返ってきた配列が前回と同じなら再描画をスキップしたい」

このとき、「配列同士を比較するユーティリティ」があると、毎回同じロジックを安全に使い回せます。


基本形:プリミティブ配列の比較 equalsArray

まずは「中身がプリミティブ(数値・文字列など)」の配列から

一番よくあるのは、ID やコード、文字列など「プリミティブ値」の配列を比較するケースです。
ここでは「要素数」「順番」「値」がすべて同じかどうかをチェックします。

function equalsArray(a, b) {
  if (a === b) {
    return true;
  }

  if (!Array.isArray(a) || !Array.isArray(b)) {
    return false;
  }

  if (a.length !== b.length) {
    return false;
  }

  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) {
      return false;
    }
  }

  return true;
}
JavaScript

重要なポイントをかみ砕きます。

まず a === b をチェックしているのは、「同じ配列インスタンスなら即 true」でいいからです。
次に「どちらも配列でなければ false」。
長さが違えば、その時点で絶対に違うので false。
最後に、各インデックスの要素を !== で比較し、1つでも違えば false。
全部同じなら true。

これで、「プリミティブ配列の完全一致」を判定できます。

動作例

equalsArray([1, 2, 3], [1, 2, 3]); // true
equalsArray([1, 2, 3], [3, 2, 1]); // false
equalsArray([1, 2], [1, 2, 3]);    // false
equalsArray(null, [1, 2]);         // false
equalsArray([1, 2], [1, "2"]);     // false
JavaScript

「順番も含めて同じかどうか」を見たいときは、この equalsArray で十分です。


オブジェクト配列を比較したいときの考え方

そのまま === で比べると「同じ参照かどうか」になる

オブジェクト配列を === で比べると、「中身が同じかどうか」ではなく「同じオブジェクトかどうか」になります。

const a = [{ id: 1 }];
const b = [{ id: 1 }];

a[0] === b[0]; // false(別オブジェクト)
JavaScript

なので、「オブジェクト配列の中身が同じかどうか」を見たいときは、
「何をもって“同じ”とみなすか」を決める必要があります。

例えば、次のようなルールが考えられます。

  • id が同じなら同じとみなす
  • 全プロパティが同じなら同じとみなす
  • 特定のキーだけ比較する

ここをユーティリティの引数として渡せるようにすると、業務で使いやすくなります。

比較関数を受け取る equalsArrayBy

function equalsArrayBy(a, b, equalsItem) {
  if (a === b) {
    return true;
  }

  if (!Array.isArray(a) || !Array.isArray(b)) {
    return false;
  }

  if (a.length !== b.length) {
    return false;
  }

  if (typeof equalsItem !== "function") {
    return false;
  }

  for (let i = 0; i < a.length; i++) {
    if (!equalsItem(a[i], b[i], i)) {
      return false;
    }
  }

  return true;
}
JavaScript

ここでは、「各要素同士をどう比較するか」を equalsItem に任せています。
equalsItem が true を返し続ければ配列は等しい、どこかで false を返したら等しくない、というルールです。

動作例:id が同じなら同じとみなす

const arr1 = [
  { id: 1, name: "A" },
  { id: 2, name: "B" },
];

const arr2 = [
  { id: 1, name: "A (changed)" },
  { id: 2, name: "B" },
];

const sameById = equalsArrayBy(
  arr1,
  arr2,
  (x, y) => x.id === y.id
); // true

const sameByAll = equalsArrayBy(
  arr1,
  arr2,
  (x, y) => x.id === y.id && x.name === y.name
); // false
JavaScript

「id だけ見れば同じ配列」とみなすか、「全プロパティまで含めて同じ」とみなすかを、
equalsItem の中身で自由に決められます。


業務でよくある「配列比較」のパターン

検索条件が変わったかどうかを判定する

検索条件を配列で持っているケースを考えます。

let prevIds = [];

function shouldSearch(nextIds) {
  const same = equalsArray(prevIds, nextIds);
  prevIds = nextIds;
  return !same;
}
JavaScript

ここでは、「前回と同じ ID 配列なら検索しない」「違っていたら検索する」という判定をしています。
equalsArray があることで、「配列が変わったかどうか」を一行で書けます。

選択中の ID リストが変わったかどうか

チェックボックスなどで「選択中の ID リスト」を配列で持っているケース。

function hasSelectionChanged(prevSelectedIds, nextSelectedIds) {
  return !equalsArray(prevSelectedIds, nextSelectedIds);
}
JavaScript

ここでのポイントは、「配列の比較ロジックをコンポーネントや画面にベタ書きしない」ことです。
ユーティリティに閉じ込めておくことで、どこでも同じルールで比較できます。


「順番を無視して比較したい」場合との違い

ここまでの equalsArray / equalsArrayBy は、「順番も含めて同じかどうか」を見ています。
しかし、業務によっては「順番はどうでもよくて、要素の集合が同じなら OK」というケースもあります。

例えば、「選択中のタグ ID の集合が同じなら、順番は気にしない」などです。

その場合は、「順番を無視した比較」用のユーティリティを別で用意するのが安全です。

順番を無視して比較する equalsArrayAsSet(プリミティブ版)

function equalsArrayAsSet(a, b) {
  if (!Array.isArray(a) || !Array.isArray(b)) {
    return false;
  }

  if (a.length !== b.length) {
    return false;
  }

  const setA = new Set(a);
  const setB = new Set(b);

  if (setA.size !== setB.size) {
    return false;
  }

  for (const v of setA) {
    if (!setB.has(v)) {
      return false;
    }
  }

  return true;
}
JavaScript

これで、次のような判定ができます。

equalsArrayAsSet([1, 2, 3], [3, 2, 1]); // true
equalsArrayAsSet([1, 2, 2], [1, 2]);    // false(重複数が違う)
JavaScript

「順番も見る equalsArray」と「順番は無視する equalsArrayAsSet」を分けておくと、
「この比較は何を見ているのか」がとても分かりやすくなります。


配列比較ユーティリティで意識してほしいポイント

何をもって「同じ」とみなすかをはっきりさせる

配列比較は、「何を同一性の基準にするか」がすべてです。

プリミティブ配列なら「値と順番が同じ」。
オブジェクト配列なら「id が同じ」「全プロパティが同じ」「特定のキーだけ同じ」など。
順番を含めるか、無視するか。

これをユーティリティの関数名や引数(equalsArray / equalsArrayBy / equalsArrayAsSet)で表現しておくと、
コードを読む人が迷いません。

非破壊であること

比較は「見るだけ」であり、配列の中身を変える必要は一切ありません。
ユーティリティの中で sort などの破壊的操作をしないように注意してください。

もし順番を無視した比較のためにソートしたくなったら、
必ず slice() でコピーしてからソートするようにします。

「配列じゃないもの」が来ても安全に扱う

equalsArray / equalsArrayBy / equalsArrayAsSet は、すべて最初に Array.isArray をチェックしています。
現実の業務コードでは、「本当は配列のはずだけど、何かの拍子に null やオブジェクトが来る」ことが普通にあります。

そこで例外を投げて落ちるより、「false を返す」と決めておくほうが、呼び出し側のコードがシンプルになります。


手を動かして感覚をつかむ

コンソールで、次のコードを実際に打ってみてください。

function equalsArray(a, b) {
  if (a === b) return true;
  if (!Array.isArray(a) || !Array.isArray(b)) return false;
  if (a.length !== b.length) return false;
  for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) return false;
  }
  return true;
}

function equalsArrayBy(a, b, equalsItem) {
  if (a === b) return true;
  if (!Array.isArray(a) || !Array.isArray(b)) return false;
  if (a.length !== b.length) return false;
  if (typeof equalsItem !== "function") return false;
  for (let i = 0; i < a.length; i++) {
    if (!equalsItem(a[i], b[i], i)) return false;
  }
  return true;
}

function equalsArrayAsSet(a, b) {
  if (!Array.isArray(a) || !Array.isArray(b)) return false;
  if (a.length !== b.length) return false;
  const setA = new Set(a);
  const setB = new Set(b);
  if (setA.size !== setB.size) return false;
  for (const v of setA) {
    if (!setB.has(v)) return false;
  }
  return true;
}

equalsArray([1, 2, 3], [1, 2, 3]);
equalsArray([1, 2, 3], [3, 2, 1]);

const arr1 = [{ id: 1 }, { id: 2 }];
const arr2 = [{ id: 1 }, { id: 2 }];
equalsArrayBy(arr1, arr2, (x, y) => x.id === y.id);

equalsArrayAsSet([1, 2, 3], [3, 2, 1]);
JavaScript

「どの比較が true になって、どれが false になるか」を自分の目で確かめてみてください。

そのうえで、自分のプロジェクトに

export function equalsArray(...) { ... }
export function equalsArrayBy(...) { ... }
export function equalsArrayAsSet(...) { ... }
JavaScript

のような関数を置き、「配列が“同じかどうか”を判定したくなったら、必ずこの“配列比較ユーティリティ”を通す」と決めてみてください。
それだけで、「なんとなく比較しているコード」から、「意図がはっきりした比較ロジック」に一段レベルアップできます。

タイトルとURLをコピーしました