【TypeScript】いろいろな条件で並べ替えてみる

# 概要

ソート処理はデータを整然と管理するための基本的かつ重要なテクニックです。

この記事ではジュニアエンジニアとシニアエンジニアの対話形式で、50音順/日付順/数字昇順など、様々なカラムでのソート方法をTypeScriptで紹介します。

# シーン1：オフィスの一角

**ジュニアエンジニア（以下、ジュニア）**：「先輩、TypeScriptでデータをソートしたいんですが、どうやったらいいですか？」

**シニアエンジニア（以下、シニア）**：「よし、基本から始めよう。ソートには`Array.prototype.sort`を使うんだ。例えば、数値を昇順にソートするにはこうする。」

```typescript
const numbers = [4, 2, 5, 1, 3];
numbers.sort((a, b) => a - b); // 昇順
console.log(numbers); // [1, 2, 3, 4, 5]
```

**ジュニア**：「簡単ですね！そしたら、文字列を50音順にソートするにはどうしたらいいですか？」

**シニア**：「良い質問だ。日本語の文字列を50音順にソートするには、localeCompareを使うんだ。」

```typescript
const words = ["あい", "いえ", "うお", "あお", "えお"];
words.sort((a, b) => a.localeCompare(b, 'ja')); // 50音昇順
console.log(words); // ["あい", "あお", "いえ", "うお", "えお"]
```

**ジュニア**：「なるほど、日本語ロケールを指定するんですね。localeCompareがどのように働くのか、もっと詳しく知りたいです。どうしてlocaleCompareが必要なんですか？」

**シニア**：「いい質問だね。localeCompareは言語に依存した比較を行うんだ。たとえば、英語と日本語では文字の順序が違うから、ロケールを指定して適切な順序で比較できるようにするんだよ。」

**ジュニア**：「じゃあ、localeCompareを使わないとどうなるんですか？」

**シニア**：「ロケールを指定しないと、文字のコードポイントの順序でソートされるんだ。例えば、’う’が’あ’の前に来るような、カオスな結果になるんだよ。」

**ジュニア**：「それは困りますね。ちゃんとロケールを指定しないと、正しい順序にならないわけですね。」

**シニア（心の中）**：「「こいつ聞き方に難ありだけど、素直だな」」

# シーン2：翌日、再びオフィスにて

**ジュニア**：「先輩、昨日はありがとうございました。次は日付をソートしたいんですが、どうすればいいですか？」

**シニア**：「日付のソートも基本は同じだ。Dateオブジェクトを比較して、ソートするんだ。」

```typescript
const dates = [
    new Date('2023-01-01'),
    new Date('2022-12-31'),
    new Date('2023-01-02')
];
dates.sort((a, b) => a.getTime() - b.getTime());
console.log(dates); // [2022-12-31, 2023-01-01, 2023-01-02]
```

**ジュニア**：「なるほど、DateオブジェクトのgetTimeメソッドを使うんですね。でも、どうしてgetTimeを使うんですか？直接Dateオブジェクトを比較するのではダメなんですか？」

**シニア**：「良い視点だ。Dateオブジェクトを直接比較すると、JavaScript内部での比較が正しく動かないことがある。getTimeを使うと、ミリ秒単位のタイムスタンプを取得できるから、確実に数値として比較できるんだ。」

**ジュニア**：「なるほど、確実に数値として比較するためにgetTimeを使うんですね。でも、タイムスタンプを直接扱うときの注意点とかありますか？」

**シニア**：「そうだな。タイムスタンプを扱うときは、タイムゾーンや夏時間の影響を考慮する必要がある。特に異なるタイムゾーン間での日付比較は注意が必要だね。」

**ジュニア**：「それも考慮しないと、正しい結果が得られないことがあるんですね。」

**シニア（心の中）**：「「こいつどんなソート処理のチケット握ってるんだ、、？」」

# シーン3：別の日、さらに深く学びたい

**ジュニア**：「先輩、オブジェクトの配列を特定のプロパティでソートするにはどうしたらいいですか？」

**シニア**：「例えば、アイテムの価格でソートする場合を見てみよう。」

```typescript
interface Item {
    name: string;
    price: number;
};

const items: Item[] = [
    { name: 'Apple', price: 150 },
    { name: 'Banana', price: 100 },
    { name: 'Cherry', price: 200 }
];

items.sort((a, b) => a.price - b.price);
console.log(items); // [{ name: 'Banana', price: 100 }, { name: 'Apple', price: 150 }, { name: 'Cherry', price: 200 }]
```

**ジュニア**：「簡単に実現できるんですね！次は複数の条件でソートする方法を教えてください。」

**シニア（心の中）**：「「こいつ聞き方だめだな、、」」

**シニア**：「複数条件でのソートは少し複雑になるが、基本は同じだ。例えば、まず名前で昇順、次に価格で昇順にソートする場合を見てみよう。」

```typescript
interface Item {
    name: string;
    price: number;
};

const items: Item[] = [
    { name: 'Apple', price: 150 },
    { name: 'Banana', price: 100 },
    { name: 'Cherry', price: 200 }
];

items.sort((a, b) => {
    const nameComparison = a.name.localeCompare(b.name);
    if (nameComparison !== 0) {
        return nameComparison;
    }
    return a.price - b.price;
});
console.log(items); // [{ name: 'Apple', price: 150 }, { name: 'Banana', price: 100 }, { name: 'Cherry', price: 200 }]
```

**ジュニア**：「なるほど、まず名前を比較して、名前が同じ場合は価格を比較するんですね。でも、複数条件でソートする場合、どの順番で条件を並べるのがいいんですか？」

**シニア**：「それはデータの特性と、何を優先するかによるね。重要度の高い条件を最初に持ってきて、次にそれが同じ場合にどうするかを考える。つまり、プライマリキー、セカンダリキーといった感じだ。」

**ジュニア**：「なるほど。じゃあ、どんな場合でも使える万能なソート方法ってありますか？」

**シニア（心の中）**：「「ここまで親切に教えてしまった俺の責任だな、、」」

**シニア**：「万能な方法はないが、localeCompareや数値の比較を組み合わせることで、ほとんどのケースに対応できる。ただ、特定のケースではカスタムロジックが必要になることもある。」

# シーン4：実践的なサンプル

**シニア（心の中）**：「「もう聞かれないように先行して理解させよ、、」」

**シニア**：「もちろんだ。ユーザーリストを年齢、名前、登録日の順でソートする例を見てみよう。」

```typescript
interface User {
    name: string;
    age: number;
    registrationDate: Date;
}

const users: User[] = [
    { name: 'Alice', age: 25, registrationDate: new Date('2023-01-02') },
    { name: 'Bob', age: 30, registrationDate: new Date('2023-01-01') },
    { name: 'Charlie', age: 25, registrationDate: new Date('2023-01-03') }
];

users.sort((a, b) => {
    if (a.age !== b.age) {
        return a.age - b.age;
    }
    const nameComparison = a.name.localeCompare(b.name);
    if (nameComparison !== 0) {
        return nameComparison;
    }
    return a.registrationDate.getTime() - b.registrationDate.getTime();
});

console.log(users);
// [
//   { name: 'Alice', age: 25, registrationDate: new Date('2023-01-02') },
//   { name: 'Charlie', age: 25, registrationDate: new Date('2023-01-03') },
//   { name: 'Bob', age: 30, registrationDate: new Date('2023-01-01') }
// ]
```

**ジュニア（心の中）**：「「もうソート処理のチケットは消化できたのになんだこの人、、」」

**ジュニア**：「年齢、名前、登録日の順にソートできますね！でも、年齢、名前、登録日の順でソートする場合、どんな場面で役立つんですか？」

**シニア**：「例えば、イベントの参加者リストを管理するときに役立つよ。同じ年齢の人をグループ化して、その中でアルファベット順に並べ、その後に登録日を基準に並べることで、新しい参加者をすぐに確認できる。」

**ジュニア**：「なるほど、それでイベントの管理がしやすくなるんですね。でも、実際のデータではもっと複雑な条件があるかもしれませんね。」

**シニア**：「その通り。だからこそ、ソートのロジックを柔軟に組み合わせることが重要なんだよ。ケースバイケースで最適な方法を見つけることが、エンジニアの腕の見せ所だ。」

**ジュニア**：「わかりました、ありがとうございます。」

# 終わりに
TypeScriptでのソート処理について、ジュニアエンジニアとシニアエンジニアの対話形式で学びましたね。基本的なソートから複数条件でのソートまで、具体的なサンプルコードと共に理解を深めてください。