とあるプログラマーの技術ブログ

JavaScriptの配列のソートの話 (1) – Array.prototype.sort()

管理人
10:34 pm

JavaScriptでの配列のソートについて簡単にまとめてみた。

問題:以下の操作を行った場合の配列要素の並びは?(答えは最後)
[null, undefined, "foo", "null", "undefined"].sort()

Array.prototype.sort()

配列自体のsortメソッドには通常比較関数を渡すが、何も渡さなかった場合は要素が文字列に変換されてUTF-16のコード順でソートされる。配列自体が書き換わる破壊的操作である点に注意が必要だ。

        it('文字列の配列をソートできる', () => {
            // Arrange
            const array = ['melon', 'banana', 'apple', 'lemon'];
            // Act
            array.sort();
            // Assert
            expect(array).toEqual(['apple', 'banana', 'lemon', 'melon']);
        });

数値の配列は一見数値でソートできているように見えるので気をつけないとバグる。

        it('数値の配列をソートできるように見える', () => {
            // Arrange
            const array = [0, 2, -1, 1];
            // Act
            array.sort();
            // Assert
            expect(array).toEqual([-1, 0, 1, 2]);
        });

以下の例を見れば、文字列としてソートされていることがわかる。

        it('数値の配列は文字列としてソートされる', () => {
            // Arrange
            const array = [0, 2, -1, 1, 10];
            // Act
            array.sort();
            // Assert
            // expect(array).toEqual([-1, 0, 1, 2, 10]);
            expect(array).toEqual([-1, 0, 1, 10, 2]);
        });

比較関数を渡せばちゃんと数値としてソートできる。比較関数が負値を返せば、第1引数の値が第2引数の値より前に位置することを表す。
比較関数はアロー関数で書いてやるのが楽だ。

        it('数値の配列を比較関数で正しくソートできる', () => {
            // Arrange
            const array = [0, 2, -1, 1, 10];
            // Act
            array.sort((a, b) => a - b);
            // Assert
            expect(array).toEqual([-1, 0, 1, 2, 10]);
        });

2つの要素を比較して負値・ゼロ・正値のいずれかを返す関数を渡してあげれば、任意のソートを実現できる。

        it('比較関数でソートできる', () => {
            // Arrange
            const array = ['hand', 'hip', 'shoulder', 'waist'];
            // Act
            array.sort((a, b) => a.length - b.length); // 文字数の小さい順
            // Assert
            expect(array).toEqual(['hip', 'hand', 'waist', 'shoulder']);
        });

もちろんオブジェクト配列のソートだって。
以下の例では名前、年齢の順でソートをかけている。

        it('比較関数でオブジェクト配列をソートできる', () => {
            // Array
            const users = [
                {'user': 'fred', 'age': 48},
                {'user': 'barney', 'age': 36},
                {'user': 'fred', 'age': 40},
                {'user': 'barney', 'age': 34},
            ];
            // Act
            // 名前、年齢の順
            users.sort((a, b) =>
                a.name !== b.name ? a.name - b.name : a.age - b.age);
            // Assert
            expect(users).toEqual([
                {'user': 'barney', 'age': 34},
                {'user': 'barney', 'age': 36},
                {'user': 'fred', 'age': 40},
                {'user': 'fred', 'age': 48},
            ]);
        });

さて、デフォルトのソートでnullundefinedはどうなるか?

        it('nullとundefined', () => {
            // Arrange
            const array = ['melon', undefined, 'apple', null];
            // Act
            array.sort();
            // Assert
            expect(array).toEqual(['apple', 'melon', null, undefined]);
        });

undefinedは最後に来る仕様となっている。nullはその前、ではなく、文字列nullと評価されているだけだ。

        it('nullとundefinedその2', () => {
            // Arrange
            const array = ['melon', undefined, 'apple', null, 'null', 'undefined'];
            // Act
            array.sort();
            // Assert
            expect(array).toEqual(['apple', 'melon', null, 'null', 'undefined', undefined]);
        });

null < 'null'の順となるのは、これらの差は0と評価されるため、元の配列での順序が保持されているのだ。

問題の答え:
["foo", null, "null", "undefined", undefined]

次回lodash編へ続く

技術情報
%d人のブロガーが「いいね」をつけました。