JavaScript | 配列・オブジェクト:配列の変換・加工 – sort(基本)

JavaScript JavaScript
スポンサーリンク

sort とは何か

Array.prototype.sort は「配列の要素を並べ替える」メソッドです。ここが重要です:sort は“破壊的(インプレース)”で、元の配列そのものを書き換えます。返り値は同じ配列への参照です。比較関数を渡さない場合、要素は“文字列化して”UTF-16 のコード順に並びます。数値を正しく昇順・降順にしたいときは、比較関数(compareFn)を必ず渡してください。


既定の挙動(文字列として並べ替える)

比較関数を省略すると、各要素が文字列に変換されて、その文字列の辞書順で並べ替えられます。例えば、数値の配列 [10, 2, 5] をそのまま sort すると、”10″ < “2” < “5” の順になるため、結果は [10, 2, 5] になり、期待する数値の昇順とは異なります。文字列の配列ではアルファベット順になりますが、大文字と小文字の違い(”Z” と “a” など)や濁点・アクセントの扱いは“単純なコード単位”の比較になる点に注意してください。

console.log([10, 2, 5].sort());        // [10, 2, 5](文字列としての順)
console.log(["b", "a", "A"].sort());   // ["A", "a", "b"](コード順)
JavaScript

数値を正しく並べる(比較関数の基本)

数値の昇順は compareFn として (a, b) => a – b を渡します。返す値が負なら a が先、正なら b が先、0 なら順序を変えません。降順は (a, b) => b – a を渡します。ここが重要です:true/false を返してはいけません。必ず負・ゼロ・正の“数値”を返します。

const nums = [10, 2, 5];
console.log(nums.sort((a, b) => a - b)); // [2, 5, 10](昇順)
console.log(nums.sort((a, b) => b - a)); // [10, 5, 2](降順)
JavaScript

NaN や undefined が混じる場合は、先に除外・置換するか、比較関数で順序を定義します。例えば undefined を末尾へ送るなら次のように書けます。

const xs = [3, undefined, 1, NaN, 2];
const safe = xs.slice().sort((a, b) => {
  const an = Number(a), bn = Number(b);
  const aBad = !Number.isFinite(an);
  const bBad = !Number.isFinite(bn);
  if (aBad && bBad) return 0;
  if (aBad) return 1;      // a を後ろへ
  if (bBad) return -1;     // b を後ろへ
  return an - bn;          // 通常の数値比較
});
// [1, 2, 3, NaN, undefined]
JavaScript

非破壊で並べる方法(元配列を守る)

sort は配列を直接書き換えます。共有されるデータや UI 状態では、まず“浅いコピー”を作ってから並べ替えるのが安全です。スプレッド構文([…])や slice() を使います。

const a = [3, 1, 2];

// 非破壊の昇順
const asc = [...a].sort((x, y) => x - y);

// 非破壊の降順
const desc = a.slice().sort((x, y) => y - x);

console.log(a);    // [3, 1, 2](元はそのまま)
console.log(asc);  // [1, 2, 3]
console.log(desc); // [3, 2, 1]
JavaScript

文字列を言語に合わせて並べる(locale 対応)

人が読む名前や都市名などは、localeCompare で比較すると自然な並びになります。大文字・小文字を無視する、アクセントを無視する等のオプションも指定できます。大量に並べ替える場合は Intl.Collator を一度作って使い回すと高速です。

const names = ["ä", "a", "Z", "z"];

// localeCompare を直接使う
console.log(names.slice().sort((a, b) => a.localeCompare(b, "de", { sensitivity: "base" })));
// ["a", "ä", "Z", "z"](ドイツ語、大小やアクセントを緩く扱う例)

// Collator を使い回す
const collator = new Intl.Collator("ja", { sensitivity: "base" });
console.log(names.slice().sort((a, b) => collator.compare(a, b)));
JavaScript

オブジェクトのキーで並べる(複合条件と安全策)

オブジェクト配列は、比較したいプロパティを取り出して比較します。数値キーなら引き算、文字列キーなら localeCompare を使うと明確です。ここが重要です:null/undefined を含む可能性に備え、値が無いときの順序も定義しておくと安定します。複数キーで並べる場合は「第一キーが同点なら第二キー」で比較を続けます。

const products = [
  { name: "B", price: 200 },
  { name: "A", price: 200 },
  { name: "C", price: 150 },
  { name: null, price: 300 }
];

// 価格昇順、同価格は名前の辞書順。name が無いものを末尾へ。
const collator = new Intl.Collator("ja", { sensitivity: "base" });
const sorted = products.slice().sort((p, q) => {
  const pn = Number(p.price), qn = Number(q.price);
  const pBad = !Number.isFinite(pn), qBad = !Number.isFinite(qn);
  if (pBad && qBad) return 0;
  if (pBad) return 1;
  if (qBad) return -1;

  if (pn !== qn) return pn - qn;

  const ps = p.name ?? "";
  const qs = q.name ?? "";
  const pEmpty = ps.length === 0, qEmpty = qs.length === 0;
  if (pEmpty && qEmpty) return 0;
  if (pEmpty) return 1;          // 名前なしは後ろ
  if (qEmpty) return -1;

  return collator.compare(ps, qs);
});

console.log(sorted);
/*
[
  { name: "C", price: 150 },
  { name: "A", price: 200 },
  { name: "B", price: 200 },
  { name: null, price: 300 }
]
*/
JavaScript

安定性・穴・undefined の扱い(挙動の理解を深掘り)

安定ソートとは「比較結果が同点(0)の要素は、元の相対順を保つ」性質です。現代的な JavaScript 実装では安定ソートが採用されているため、同値の並びが保たれる前提で書けます。疎配列(空スロット)は並べ替えの過程で位置が再配置されますが、穴そのものは保持されることがあり、表示や後工程で意図せぬ“空白”が生じます。必要なら事前に Array.from や fill で正規化してから sort を使うと安全です。undefined は既定の文字列比較では空文字扱いに近い挙動を見せ、末尾へ追いやられることが多いですが、環境差や比較関数次第で順序が変わります。必ず“undefined をどこに置くか”を比較関数で明示すると、結果が安定します。

const sparse = [ , "b", , "a" ];
console.log(sparse.slice().sort()); // 空スロットが絡む並びは意図通りと限らない
const normalized = Array.from(sparse, x => x ?? "");
console.log(normalized.slice().sort()); // 正規化してから並べ替える
JavaScript

よくある落とし穴と対策

比較関数で true/false を返してしまうと、規定が満たされず不安定な結果になります。必ず負・ゼロ・正の数値を返してください。並べ替えた後に元配列が必要になるのに、破壊的 sort を直接かけると、後工程が壊れます。まずコピーしてから sort する習慣をつけましょう。比較関数が“非推移的”(A<B、B<C だが A<C が成り立たない)だと、結果が不定になります。複合条件は明確な優先順位で、各段階が推移的になるように定義してください。大量データでは O(n log n) の並べ替えコストが効いてきます。事前の正規化(型変換、欠損処理)、Collator の使い回し、必要部分だけの並べ替え(例:上位 N 件なら partial sort や一時の比較関数と slice)で負荷を下げる設計が有効です。


まとめ

sort は「配列を並べ替える」強力なメソッドで、既定は“文字列の辞書順”、数値や複合条件では“比較関数”が鍵になります。破壊的である点を理解し、共有データはコピーしてから並べ替えるのが安全です。文字列は localeCompare/Intl.Collator で人に自然な順序に、数値や null/undefined を含む現実のデータは“順序の定義”を比較関数に明示します。安定性・疎配列・欠損の扱いを押さえれば、初心者でも意図通りで壊れにくいソート処理を短く正確に書けます。

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