C言語で スケジュール管理アプリ(ミニ)を作ろう!第4回 ~ 日付順への並び替え~

スポンサーリンク
C言語

はじめに

前回のVer.3では、予定の編集・削除に対応した。今回のVer.4では、登録されている予定を日付順(昇順)に並び替える機能を追加する。

並び替えのアルゴリズムには、基本情報技術者試験アルゴリズムシリーズで扱ったバブルソートをそのまま応用する。今回は「数値の配列」ではなく「Schedule 構造体の配列」を、日付というキーで比較・並び替える点が新しい。

対応するGitHubリポジトリは以下の通りである。

Ver.3からの変更点

  • メニューに 5: 日付順に並び替え を追加した。
  • 並び替え後は、そのまま一覧を表示する。
  • 登録されている予定が0件・1件のときは、並び替えの必要が無いため、その旨を表示するだけで処理を終える。

日付の大小を比較する

Schedule 構造体は年・月・日をそれぞれ別のフィールドとして持っているため、単純に > で比較することはできない。そこで、2件の予定を受け取り、片方がもう片方より後ろの日付かどうかを判定する関数を用意する。

/* aの日付がbより後ろ(大きい)であれば1を返す。年→月→日の順で比較する。 */
int is_later(const Schedule *a, const Schedule *b) {
    if (a->year != b->year) {
        return a->year > b->year;
    }
    if (a->month != b->month) {
        return a->month > b->month;
    }
    return a->day > b->day;
}

年が異なればその時点で年だけで大小が決まる。年が同じなら月を比較し、月も同じなら最後に日を比較する。この「上位の桁から順に比較し、同じなら次の桁を見る」という考え方は、日付に限らず複数のキーで順序を決めたい場合によく使われる。

バブルソートを適用する

バブルソートの基本的な考え方は、「隣り合う2つの要素を比較し、順序が逆であれば入れ替える」という操作を、配列の先頭から末尾まで繰り返すというものである。1回配列の先頭から末尾まで見終えると、その周回でもっとも大きい値が末尾側に移動する(これを1周=1パスと呼ぶ)。これを、入れ替えが1件も発生しなくなるまで繰り返す。

/*
 * 日付(年→月→日の順)を基準に、予定を昇順に並び替える。
 * 基本情報技術者試験アルゴリズムシリーズのバブルソートと同じ考え方を、
 * Schedule構造体の配列に対して適用したもの。
 * 隣り合う要素を比較し、順序が逆であれば入れ替える処理を、
 * 1周ごとに入れ替えが発生しなくなるまで繰り返す。
 */
void sort_schedules_by_date(void) {
    if (schedule_count < 2) {
        printf("並び替えの対象となる予定がありません。\n");
        return;
    }

    for (int i = 0; i < schedule_count - 1; i++) {
        int swapped = 0;

        for (int j = 0; j < schedule_count - 1 - i; j++) {
            if (is_later(&schedules[j], &schedules[j + 1])) {
                Schedule temp = schedules[j];
                schedules[j] = schedules[j + 1];
                schedules[j + 1] = temp;
                swapped = 1;
            }
        }

        if (!swapped) {
            break;
        }
    }

    printf("日付順に並び替えました。\n");
    list_schedules();
}

schedules[j]schedules[j + 1] を比較し、is_later が1(前の方が後ろの日付)を返した場合に、構造体ごと丸々入れ替えている。数値の配列を対象にしたときと違い、日付だけでなく予定の内容(title)も含めて構造体単位で入れ替わるため、「どの予定がどの日付か」という対応関係は崩れない。

外側のループを1周するごとに swapped を確認し、1件も入れ替えが起きなければ、その時点ですでに並び終えているとみなしてループを抜ける。これはFE-algorithmシリーズのバブルソートでも扱った、無駄な周回を省く工夫である。

並び替えの動き(トレース例)

4件の予定を、登録した順に「B予定(8/20)・A予定(8/1)・Z予定(前年12/31)・B2予定(8/20、同日)」という並びで持っているとする(分かりやすさのため、ここでは日付部分だけを比較対象として示す)。

状態1件目2件目3件目4件目
並び替え前8/20 B8/1 A前年12/31 Z8/20 B2
1周目終了後8/1 A8/20 B8/20 B2前年12/31 Z
2周目終了後8/1 A8/20 B前年12/31 Z8/20 B2
3周目終了後(入れ替え無し)前年12/31 Z8/1 A8/20 B8/20 B2

このように、1周ごとに一番「後ろ」の日付が末尾側に確定していき、最終的に前年12/31のZ予定が先頭に来る形で並び替えが完了する。8/20のB予定とB2予定は日付が同じである。しかし比較に > を使っているため、等しい場合は入れ替えが起こらない。そのため、元の登録順(B予定が先、B2予定が後)がそのまま保たれる。このように、同じキーを持つ要素の順序が保たれる性質を「安定」という。

動作確認

日付がバラバラの順で4件登録した状態で 5 を選ぶと、次のように日付順に並び替えられる。

番号を入力してください: 5
日付順に並び替えました。

--- 予定一覧 ---
1: 2025-12-31  Z予定
2: 2026-08-01  A予定
3: 2026-08-20  B予定
4: 2026-08-20  B2予定(同日)

登録されている予定が0件または1件のときに 5 を選ぶと、比較する相手がいないため、並び替え処理そのものを行わずにメッセージだけを表示する。

番号を入力してください: 5
並び替えの対象となる予定がありません。

注意点

並び替えは配列そのものを並び替えるため、並び替えた後にプログラムを終了すると、schedules.txt の中身も日付順で保存される。またVer.1からの日付チェック(月1〜12、日1〜31の範囲チェック)は今回も変わっておらず、2月31日のような実在しない日付までは弾いていない点は引き続き同じである。そうした日付が万が一登録されていた場合も、値としてはそのまま比較・並び替えの対象になる。

次回予告

次回のVer.5では、祝日カレンダー企画(multi-pg-lang-calendar)で扱っている祝日データと連携し、一覧表示の際に祝日を判別できるようにする予定である。

コメント

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