JavaScript Tips | 配列ユーティリティ:採番

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「採番」

ここでの「採番」は、配列の要素に「業務で使う番号(連番・管理番号)」を振る処理のことです。
さっきやった「インデックス付与」とよく似ていますが、目的が少し違います。

インデックスは「配列の何番目か」という技術的な情報でした。
採番は「伝票番号」「行番号」「管理 ID」のように、業務上意味を持つ番号を付けるイメージです。

例えば、次のような場面で使います。
画面上の明細行に「行番号」を振りたい。
新規に追加された行に「一意な行 ID」を振りたい。
既存データの最大番号の続きから、新しい番号を振りたい。

ここをユーティリティ化しておくと、「番号の付け方」がコード全体で統一されて、バグが減ります。


インデックス付与との違いを整理する

インデックスは「位置」、採番は「意味のある番号」

インデックス付与は、0, 1, 2, ... のように「配列の位置」をそのままくっつけるものでした。

採番は、次のような特徴を持ちます。

番号の開始値を自由に決めたい(1 から、1000 から、など)。
既存の最大値の続きから振りたい。
桁数を揃えたい(001, 002, …)。
プレフィックスを付けたい(”ORD-001″, “ORD-002″…)。

つまり、「ただの位置」ではなく、「業務ルールに沿った番号」を作るのが採番です。


基本形:単純な連番を振る採番ユーティリティ

シンプルな連番採番

まずは、「配列の要素に 1, 2, 3… のような連番を振る」基本形からいきます。

function assignSequence(array, key = "seq", start = 1, step = 1) {
  if (!Array.isArray(array)) {
    return [];
  }

  return array.map((item, i) => {
    const n = start + i * step;

    if (item != null && typeof item === "object") {
      return { ...item, [key]: n };
    }

    return { value: item, [key]: n };
  });
}
JavaScript

ここでの重要ポイントは次の 3 つです。

start で「どこから始めるか」を決める(デフォルト 1)。
step で「何刻みで増やすか」を決める(デフォルト 1)。
オブジェクトならスプレッドでプロパティ追加、プリミティブなら { value, seq } で包む。

これで、「1, 2, 3…」「10, 20, 30…」など、基本的な採番は全部書けます。

実際の動き

const rows = [
  { name: "A" },
  { name: "B" },
  { name: "C" },
];

assignSequence(rows);
/*
[
  { name: "A", seq: 1 },
  { name: "B", seq: 2 },
  { name: "C", seq: 3 },
]
*/

assignSequence(rows, "lineNo", 10, 10);
/*
[
  { name: "A", lineNo: 10 },
  { name: "B", lineNo: 20 },
  { name: "C", lineNo: 30 },
]
*/
JavaScript

「行番号」「伝票内の明細番号」などは、このパターンで十分なことが多いです。


既存データの「最大値の続き」から採番する

よくある要件:「今ある番号の続きから振ってほしい」

業務で本当によく出るのが、「既に番号が振られているデータがあって、新しく追加されたものにはその続きから番号を振りたい」という要件です。

例えば、既存データがこうなっているとします。

const existing = [
  { name: "A", seq: 1 },
  { name: "B", seq: 2 },
];
JavaScript

ここに新規行を 2 件追加したいとき、「3, 4」と振りたいわけです。

最大値を求めてから採番するユーティリティ

function nextSequenceStart(items, key = "seq") {
  if (!Array.isArray(items)) {
    return 1;
  }

  let max = 0;

  for (const item of items) {
    if (!item || typeof item !== "object") continue;
    const value = item[key];
    if (typeof value === "number" && Number.isFinite(value)) {
      if (value > max) {
        max = value;
      }
    }
  }

  return max + 1;
}

function assignSequenceFromExisting(existingItems, newItems, key = "seq", step = 1) {
  const start = nextSequenceStart(existingItems, key);
  return assignSequence(newItems, key, start, step);
}
JavaScript

nextSequenceStart は、「既存配列の中で key の最大値を探して、その次の値を返す」関数です。
assignSequenceFromExisting は、「既存配列を見て開始値を決め、その値から新規配列に採番する」関数です。

実際の使い方

const existing = [
  { name: "A", seq: 1 },
  { name: "B", seq: 2 },
];

const newRows = [
  { name: "C" },
  { name: "D" },
];

const numberedNew = assignSequenceFromExisting(existing, newRows);
/*
[
  { name: "C", seq: 3 },
  { name: "D", seq: 4 },
]
*/
JavaScript

このように、「既存データの最大値の続きから採番する」という業務あるあるを、ユーティリティに閉じ込めておくと、
画面でもバッチでも同じルールで番号を振れるようになります。


桁数・プレフィックス付きの採番(”ORD-001″ など)

「見た目のルール」がある番号

実務では、「ただの数字」ではなく、次のようなルールが付くことが多いです。

3 桁ゼロ埋め(001, 002, …)。
プレフィックス付き(”ORD-001″, “ORD-002″…)。
日付+連番(”20240601-001″ など)。

こういうときは、「数値としての連番」と「文字列としてのフォーマット」を分けて考えるとスッキリします。

ゼロ埋め+プレフィックスのフォーマッタ

function formatSequence(n, { width = 3, prefix = "", suffix = "" } = {}) {
  const numStr = String(n).padStart(width, "0");
  return `${prefix}${numStr}${suffix}`;
}
JavaScript

padStart で桁数を揃え、前後に文字列を付けるだけです。

採番と組み合わせる

function assignCode(array, key = "code", options = {}) {
  if (!Array.isArray(array)) {
    return [];
  }

  const { start = 1, step = 1, width = 3, prefix = "", suffix = "" } = options;

  return array.map((item, i) => {
    const n = start + i * step;
    const code = formatSequence(n, { width, prefix, suffix });

    if (item != null && typeof item === "object") {
      return { ...item, [key]: code };
    }

    return { value: item, [key]: code };
  });
}
JavaScript

実際の動き

const orders = [
  { customer: "Alice" },
  { customer: "Bob" },
];

assignCode(orders, "orderNo", {
  start: 1,
  width: 4,
  prefix: "ORD-",
});
/*
[
  { customer: "Alice", orderNo: "ORD-0001" },
  { customer: "Bob",   orderNo: "ORD-0002" },
]
*/
JavaScript

「数値としての連番」と「文字列としての見た目」を分けておくと、
途中でフォーマットを変えたくなったときも、採番ロジックをいじらずに済みます。


採番で特に意識してほしい重要ポイント

1. 「一意性」がどこまで必要かを決める

採番は、「一意であること」が求められることが多いです。

伝票内だけ一意ならよいのか。
システム全体で一意である必要があるのか。
日付ごとにリセットしてよいのか。

これによって、採番の設計が大きく変わります。

配列ユーティリティとして扱うのは、「あくまで“その配列の中での採番”」です。
システム全体で一意な ID が必要な場合は、DB のシーケンスや UUID など、別の仕組みと組み合わせる必要があります。

2. 「再採番」するかどうかを決める

行の追加・削除があったとき、「番号を振り直すかどうか」も重要です。

常に 1 から振り直す(見た目の行番号として使う)。
一度振った番号は変えない(業務上の識別子として使う)。

前者なら、毎回 assignSequence で振り直せばよいです。
後者なら、「新規行にだけ採番する」ロジックが必要になります。

例えば、「seq が未定義のものにだけ番号を振る」ユーティリティはこう書けます。

function assignSequenceIfMissing(array, key = "seq") {
  if (!Array.isArray(array)) return [];

  const start = nextSequenceStart(array, key);

  let current = start;

  return array.map((item) => {
    if (!item || typeof item !== "object") {
      return item;
    }

    if (typeof item[key] === "number") {
      return item;
    }

    return { ...item, [key]: current++ };
  });
}
JavaScript

「番号を変えたくないのか」「見た目だけ揃えたいのか」を、ユーティリティのレベルで分けておくと、後で混乱しません。

3. 「表示用の番号」と「内部 ID」を混ぜない

採番した番号を、そのまま「内部 ID」として使ってしまうと、
後から仕様変更したときに破綻しやすいです。

例えば、「画面の行番号(1, 2, 3…)」をそのまま DB のキーに使ってしまうと、
行の並び替えや削除で番号が変わったときに、整合性が崩れます。

内部 ID(変わらない識別子)と、表示用の番号(変わってもよい連番)は、
プロパティ名も用途も分けておくのが安全です。


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

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

const rows = [
  { name: "A" },
  { name: "B" },
  { name: "C" },
];

assignSequence(rows);
assignSequence(rows, "lineNo", 10, 10);

const existing = [
  { name: "A", seq: 1 },
  { name: "B", seq: 2 },
];

const newRows = [
  { name: "C" },
  { name: "D" },
];

assignSequenceFromExisting(existing, newRows);

const orders = [
  { customer: "Alice" },
  { customer: "Bob" },
];

assignCode(orders, "orderNo", {
  start: 1,
  width: 4,
  prefix: "ORD-",
});
JavaScript

「どんな番号が振られているか」「既存の続きからちゃんと採番されているか」「フォーマット付きの番号がどう作られているか」を、自分の目で確認してみてください。

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

export function assignSequence(...) { ... }
export function nextSequenceStart(...) { ... }
export function assignSequenceFromExisting(...) { ... }
export function assignCode(...) { ... }
JavaScript

のような関数を置き、「業務で番号を振りたくなったら、必ずこの“採番ユーティリティ”を通す」というルールを作ってみてください。
そうすると、「その場しのぎの連番」から、「意図と一貫性を持った採番設計」に一段ステップアップしていきます。

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