JavaScript 速習チュートリアル

JavaScript 配列のソート

1. アルファベット順のソート (Alphabetic Sort)

JavaScript には、配列をソートするための様々なメソッドが用意されています。

  • sort(): 配列をアルファベット順にソートします。
  • reverse(): 配列の要素を反転させます。
  • toSorted(): 元の配列を変更せずにソートした新しい配列を返します(ES2023)。
  • toReversed(): 元の配列を変更せずに反転させた新しい配列を返します(ES2023)。

1.1 sort() メソッド

sort() メソッドは、配列をアルファベット順にソートします。

const fruits = ["バナナ", "オレンジ", "アップル", "マンゴー"];
fruits.sort();

1.2 reverse() メソッド

reverse() メソッドは、配列の要素の順序を反転させます。

const fruits = ["バナナ", "オレンジ", "アップル", "マンゴー"];
fruits.reverse();

1.3 sort() と reverse() の組み合わせ

sort()reverse() を組み合わせることで、配列を降順(アルファベットの逆順)にソートできます。

const fruits = ["バナナ", "オレンジ", "アップル", "マンゴー"];
fruits.sort();
fruits.reverse();

2. JavaScript Array toSorted() メソッド

ES2023 では、元の配列(オリジナル配列)を書き換えずに安全にソートを行う方法として、toSorted() メソッドが追加されました。

toSorted()sort() の違いは、toSorted() が新しい配列を作成して元の配列をそのまま保持するのに対し、sort() は元の配列自体を変更(破壊的変更)する点にあります。

const months = ["1月", "2月", "3月", "4月"];
const sorted = months.toSorted();

3. JavaScript Array toReversed() メソッド

ES2023 では、元の配列を書き換えずに安全に反転させる方法として、toReversed() メソッドが追加されました。

toReversed()reverse() の違いは、前者が新しい配列を作成する非破壊的なメソッドであるのに対し、後者は元の配列を直接変更する点です。

const months = ["1月", "2月", "3月", "4月"];
const reversed = months.toReversed();

4. 数値のソート (Numeric Sort)

デフォルトでは、sort() ファンクションは値を 文字列 としてソートします。

これは文字列(例: "Apple" は "Banana" より前に来る)に対しては正しく機能します。
しかし、数値を文字列としてソートすると、"25" は "100" よりも大きいと判定されます。なぜなら、"2" は "1" よりも大きいからです。

このため、sort() メソッドで数値をソートすると誤った結果を生成することがあります。
これを修正するには、比較関数 (Compare Function) を指定します。

const points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return a - b});

同じテクニックを使って、配列を降順にソートすることも可能です。

const points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return b - a});

5. 比較関数 (The Compare Function)

比較関数の目的は、代替のソート順序を定義することです。
比較関数は、引数に応じて「負の数」、「ゼロ」、「正の数」のいずれかの値を返す必要があります。

function(a, b){return a - b}

sort() ファンクションが2つの値を比較する際、その値を比較関数に送り、返された値(負、ゼロ、正)に基づいてソートを行います。

  • 結果が負の場合、ab の前にソートされます。
  • 結果が正の場合、ba の前にソートされます。
  • 結果が 0 の場合、2つの値のソート順序は変更されません。

例:
比較関数は、配列内のすべての値を一度に2つずつ(a, b)比較します。
40 と 100 を比較するとき、sort() メソッドは比較関数 (40, 100) を呼び出します。
ファンクションは 40 - 100 (a - b) を計算し、結果が負(-60)であるため、ソートファンクションは 40 を 100 より小さい値としてソートします。

以下のコードスニペットを使って、数値ソートとアルファベットソートの実験ができます。

<button onclick="myFunction1()">アルファベット順にソート</button>
<button onclick="myFunction2()">数値順にソート</button>

<p id="demo"></p>

<script>
const points = [40, 100, 1, 5, 25, 10];
document.getElementById("demo").innerHTML = points;

function myFunction1() {
  points.sort();
  document.getElementById("demo").innerHTML = points;
}

function myFunction2() {
  points.sort(function(a, b){return a - b});
  document.getElementById("demo").innerHTML = points;
}
</script>

6. 配列をランダムな順序でソートする

上述の比較関数を利用して、数値配列をランダムな順序にソートできます。

const points = [40, 100, 1, 5, 25, 10];
points.sort(function(){return 0.5 - Math.random()});

7. Fisher Yates メソッド

上記の例で使用した points.sort() による方法は、完全に正確ではありません。一部の数値が他の数値よりも優先される傾向があります。

最も一般的で正しいとされる手法は Fisher Yates シャッフル (Fisher Yates shuffle) と呼ばれるもので、古くは1938年にデータサイエンスの分野で導入されました。JavaScript では、このメソッドを以下のように実装できます。

const points = [40, 100, 1, 5, 25, 10];

for (let i = points.length - 1; i > 0; i--) {
  let j = Math.floor(Math.random() * (i + 1));
  let k = points[i];
  points[i] = points[j];
  points[j] = k;
}

8. 配列の最小値(または最大値)を見つける

JavaScript の配列には、最大値や最小値を見つけるための組み込みのファンクション(max()min() メソッドなど)はありません。
最小値または最大値を見つけるには、主に3つの選択肢があります。

  1. 配列をソートし、最初または最後の要素を読み取る
  2. Math.min() または Math.max() を使用する
  3. 自作のファンクション(カスタム関数)を作成する

9. sort() を使って最小値・最大値を取得する

配列をソートした後、インデックスを使用して最大値と最小値を取得できます。

昇順ソートの例:

const points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return a - b});
// これで points[0] に最小値が含まれます
// points[points.length-1] に最大値が含まれます

降順ソートの例:

const points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return b - a});
// これで points[0] に最大値が含まれます
// points[points.length-1] に最小値が含まれます

最大値(または最小値)を取得したいだけの場合、配列全体をソートするのは非常に効率の悪い方法です。

10. 配列に Math.min() を使用する

Math.min.apply を使用して、配列内の最小値を検索できます。

function myArrayMin(arr) {
  return Math.min.apply(null, arr);
}

Math.min.apply(null, [1, 2, 3])Math.min(1, 2, 3) と等価です。

11. 配列に Math.max() を使用する

Math.max.apply を使用して、配列内の最大値を検索できます。

function myArrayMax(arr) {
  return Math.max.apply(null, arr);
}

Math.max.apply(null, [1, 2, 3])Math.max(1, 2, 3) と等価です。

12. 自作の最小値取得メソッド

JavaScript の配列には最小値を探す組み込み関数がありませんが、最も高速なコードは自作(ホームメイド)のメソッドを使用することです。
このファンクションは配列をループし、見つかった最小値と各値を比較していきます。

例(最小値の検索)

function myArrayMin(arr) {
  let len = arr.length;
  let min = Infinity;
  while (len--) {
    if (arr[len] < min) {
      min = arr[len];
    }
  }
  return min;
}

13. 自作の最大値取得メソッド

最大値についても同様です。ループを用いた実装がパフォーマンス面で有利です。

例(最大値の検索)

function myArrayMax(arr) {
  let len = arr.length;
  let max = -Infinity;
  while (len--) {
    if (arr[len] > max) {
      max = arr[len];
    }
  }
  return max;
}

14. オブジェクト配列のソート

JavaScript の配列には、オブジェクトが含まれることがよくあります。

const cars = [
  {type:"Volvo", year:2016},
  {type:"Saab", year:2001},
  {type:"BMW", year:2010}
];

オブジェクトが異なるデータ型のプロパティを持っていても、sort() メソッドを使用して配列をソートできます。解決策は、プロパティの値を比較するための比較関数を作成することです。

例(数値プロパティでのソート)

cars.sort(function(a, b){return a.year - b.year});

文字列プロパティの比較は、少し複雑になります。

例(文字列プロパティでのソート)

cars.sort(function(a, b){
  let x = a.type.toLowerCase();
  let y = b.type.toLowerCase();
  if (x < y) {return -1;}
  if (x > y) {return 1;}
  return 0;
});

15. 安定ソート(Stable Array sort())

ES2019 では、Array の sort() メソッドの仕様が改訂されました。

2019年以前の仕様では、QuickSort のような不安定なソートアルゴリズムの使用が許容されていました。
ES2019 以降、ブラウザは 安定ソート (Stable sorting algorithm) を使用しなければなりません。

安定ソートとは、特定の値を基準にソートする際、同じ値を持つ要素同士の相対的な位置関係が維持されることを指します。

const myArr = [
  {name:"X00", price:100},
  {name:"X01", price:100},
  {name:"X02", price:100},
  {name:"X03", price:100},
  {name:"X04", price:110},
  {name:"X05", price:110},
  {name:"X06", price:110},
  {name:"X07", price:110}
];

上記の例で price に基づいてソートする場合、同じ 100 という値を持つ要素(X00, X01, X02, X03)の順番が入れ替わって、以下のような結果になることは(現在の仕様では)許されません。

X01 100
X03 100
X00 100
X02 100
...