JavaScript Tips | 配列ユーティリティ:条件削除

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「条件削除」

「条件削除」は、配列の中から「ある条件に当てはまる要素だけ」を取り除く処理です。
英語だとイメージ的には「removeIf」「reject」「filter-out」みたいな感じですね。

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

特定のステータスのレコードだけを一覧から消したい。
期限切れのデータだけを削除したい。
フラグが立っているものだけを除外して、残りを処理したい。

毎回 filter を直接書いてもいいんですが、「削除したい条件」をユーティリティに閉じ込めておくと、
コードの意図がはっきりして、バグも減ります。


基本形:条件を関数で受け取る removeBy

「削除したい条件」を関数にする

条件削除の一番素直な形は、「削除したいかどうかを判定する関数」を受け取るユーティリティです。

function removeBy(array, shouldRemove) {
  if (!Array.isArray(array)) {
    return [];
  }

  if (typeof shouldRemove !== "function") {
    return array.slice();
  }

  return array.filter((item, index) => !shouldRemove(item, index));
}
JavaScript

ここでの重要ポイントをかみ砕きます。

配列でなければ空配列を返す(安全側)。
shouldRemove が関数でなければ、そのままコピーして返す(何もしない)。
filter の中で「削除したいものは除外する」=!shouldRemove(...) なものだけ残す。

つまり、「shouldRemove が true を返した要素は削除される」「false を返した要素は残る」という動きになります。

動作イメージ

例えば、「値が 0 以下のものを削除したい」場合。

const nums = [10, -5, 0, 20];

const positiveOnly = removeBy(nums, (n) => n <= 0);
// [10, 20]
JavaScript

「削除したい条件」を n <= 0 として書き、それを removeBy に渡しています。
filter を直接書くのとやっていることは同じですが、「削除」という意図が関数名に出ているのがポイントです。


よくある業務パターン別の条件削除

ステータスで削除する

例えば、次のような配列があるとします。

const items = [
  { id: 1, status: "active" },
  { id: 2, status: "deleted" },
  { id: 3, status: "active" },
];
JavaScript

status: "deleted" のものだけ削除したい」場合。

const visibleItems = removeBy(items, (item) => item.status === "deleted");
/*
[
  { id: 1, status: "active" },
  { id: 3, status: "active" },
]
*/
JavaScript

ここでのポイントは、「削除したい条件」を item.status === "deleted" として、
それを removeBy に渡していることです。

期限切れデータを削除する

期限付きのデータを扱う配列を考えます。

const now = new Date("2024-06-01T00:00:00Z");

const items = [
  { id: 1, expiresAt: new Date("2024-05-01T00:00:00Z") },
  { id: 2, expiresAt: new Date("2024-07-01T00:00:00Z") },
];
JavaScript

「期限切れ(expiresAt < now)のものを削除したい」場合。

const validItems = removeBy(items, (item) => item.expiresAt < now);
/*
[
  { id: 2, expiresAt: ... },
]
*/
JavaScript

「削除したい条件」を item.expiresAt < now として書き、
それを removeBy に渡すだけで、「期限切れ削除」が実現できます。


「削除条件」と「残す条件」を意識して書き分ける

filter との違いを整理する

Array.prototype.filter は、「残したい条件」を書く関数です。

const positiveOnly = nums.filter((n) => n > 0);
JavaScript

一方、removeBy は「削除したい条件」を書く関数です。

const positiveOnly = removeBy(nums, (n) => n <= 0);
JavaScript

どちらも結果は同じですが、「頭の中で何を考えるか」が違います。

残したい条件で考えるほうが楽なときは filter を直接使えばいいし、
「削除したい条件」が業務仕様としてはっきりしているときは removeBy のほうが読みやすくなります。

「削除条件を名前付き関数にする」メリット

例えば、「削除条件」が複雑になってきたとき、
それを名前付き関数にしておくと、コードの意図が一気に読みやすくなります。

function shouldRemoveDeleted(item) {
  return item.status === "deleted";
}

const visibleItems = removeBy(items, shouldRemoveDeleted);
JavaScript

「この配列は“削除済みを除いたもの”なんだな」と、関数名だけで伝わります。
業務ロジックが増えてくるほど、この「名前で伝える」力が効いてきます。


破壊的な条件削除:元の配列を直接いじる版

非破壊版と破壊版の違い

ここまでの removeBy は、「新しい配列を返す非破壊的な関数」でした。
元の配列はそのまま残ります。

一方で、「この配列はここでしか使っていないから、直接中身を削ってしまいたい」という場面もあります。
そのときは、破壊的な条件削除ユーティリティを用意してもいいです。

破壊的 removeByInPlace

function removeByInPlace(array, shouldRemove) {
  if (!Array.isArray(array) || typeof shouldRemove !== "function") {
    return array;
  }

  let write = 0;

  for (let read = 0; read < array.length; read++) {
    const item = array[read];
    if (!shouldRemove(item, read)) {
      array[write++] = item;
    }
  }

  array.length = write;
  return array;
}
JavaScript

ここでの重要ポイントは、「2 つのインデックスを使って上書きしている」ことです。

read で元の配列を順に読み、
削除したくないものだけを write の位置に詰めていく。
最後に array.length = write で、余った要素を切り捨てる。

これで、「元の配列を直接削る」ことができます。

動作例

const nums = [10, -5, 0, 20];

removeByInPlace(nums, (n) => n <= 0);
// nums は [10, 20] に変わる
JavaScript

破壊的なので、「ここで削ると、以降ずっと削られた状態になる」ことを意識して使う必要があります。
ログバッファや一時配列など、「1 箇所でだけ管理している配列」に対して使うのが向いています。


条件削除ユーティリティで意識してほしいポイント

条件の意味を関数名で伝える

削除条件が業務仕様そのものなので、
それを「名前付き関数」にしておくと、コードの意図がとても読みやすくなります。

shouldRemoveDeleted
shouldRemoveExpired
shouldRemoveInactive

こういう名前を付けておくと、「何を削っているのか」が一目で分かります。
item.status === "deleted" があちこちに散らばるより、ずっとメンテしやすいです。

非破壊版を基本にして、破壊版は慎重に使う

基本は removeBy のような「新しい配列を返す非破壊的な関数」を使うほうが安全です。
元の配列をいじらないので、「どこで削られたのか」が追いやすいからです。

どうしてもパフォーマンスやメモリの都合で破壊的にしたいときだけ、
removeByInPlace のような関数を使う、というスタンスがおすすめです。

filter と removeBy を使い分ける

「残したい条件」で考えるほうが自然なら、素直に filter を使えばいいです。
「削除したい条件」が仕様としてはっきりしているなら、removeBy のほうが読みやすくなります。

どちらも中身は同じ filter ですが、
「頭の中で何を考えているか」をコードに反映させることで、
未来の自分やチームメイトが理解しやすいコードになります。


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

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

function removeBy(array, shouldRemove) {
  if (!Array.isArray(array)) {
    return [];
  }
  if (typeof shouldRemove !== "function") {
    return array.slice();
  }
  return array.filter((item, index) => !shouldRemove(item, index));
}

const nums = [10, -5, 0, 20];
const positiveOnly = removeBy(nums, (n) => n <= 0);

const items = [
  { id: 1, status: "active" },
  { id: 2, status: "deleted" },
  { id: 3, status: "active" },
];

const visibleItems = removeBy(items, (item) => item.status === "deleted");
JavaScript

「どの要素が削除されて、どの要素が残るか」「削除条件を変えると結果がどう変わるか」を、自分の目で確かめてみてください。

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

export function removeBy(...) { ... }
// 必要なら
export function removeByInPlace(...) { ... }
JavaScript

を置き、「配列から“条件に合うものだけ”を削除したくなったら、必ずこの“条件削除ユーティリティ”を通す」と決めてみてください。
それだけで、バラバラに書かれた filter 条件が整理されて、業務ロジックの見通しがぐっと良くなります。

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