C++でのファイルI/Oとデータソートの実験

タスク1:コンテスト参加者データの処理

このタスクでは、コンテスト参加者の情報を含むファイルを読み込み、特定のルールでソートし、結果を表示および保存するプログラムを作成します。

1. ソースコード

(1) participant.hpp

#pragma once
#include <iomanip>
#include <iosfwd>
#include <string>

struct Participant {
    long   student_id;        // 学籍番号
    std::string participant_name; // 参加者名
    std::string field_of_study;   // 専門分野
    int    problems_solved;   // 解いた問題数
    int    total_penalty;     // 罰則時間
};

// 出力ストリーム演算子のオーバーロード
inline std::ostream& operator<<(std::ostream& out, const Participant& p) {
    out << std::left;
    out << std::setw(15) << p.student_id
        << std::setw(15) << p.participant_name
        << std::setw(15) << p.field_of_study
        << std::setw(10) << p.problems_solved
        << std::setw(10) << p.total_penalty;

    return out;
}

// 入力ストリーム演算子のオーバーロード
inline std::istream& operator>>(std::istream& in, Participant& p) {
    in >> p.student_id >> p.participant_name >> p.field_of_study >> p.problems_solved >> p.total_penalty;
    return in;
}

(2) utilities.hpp

#pragma once
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "participant.hpp"

// ACMコンテストのソートルール:解いた問題数の降順、罰則時間の昇順
inline bool compare_participants(const Participant& a, const Participant& b) {
    if (a.problems_solved != b.problems_solved) {
        return a.problems_solved > b.problems_solved;
    }
    return a.total_penalty < b.total_penalty;
}

// コンテナの内容を任意の出力ストリームに書き込む
inline void output_to_stream(std::ostream& os, const std::vector<Participant>& v) {
    for (const auto& p : v) {
        os << p << '
';
    }
}

// コンテナの内容を標準出力に表示する
inline void display_to_console(const std::vector<Participant>& v) {
    output_to_stream(std::cout, v);
}

// コンテナの内容をファイルに保存する
inline void save_to_file(const std::string& filename, const std::vector<Participant>& v) {
    std::ofstream os(filename);
    if (!os) {
        throw std::runtime_error("ファイル " + filename + " を開けません");
    }
    output_to_stream(os, v);
}

// ファイルからデータを読み込み、タイトル行をスキップする
inline std::vector<Participant> read_from_file(const std::string& filename) {
    std::ifstream is(filename);
    if (!is) {
        throw std::runtime_error("ファイル " + filename + " を開けません");
    }

    std::string header_line;
    std::getline(is, header_line); // タイトル行をスキップ

    std::vector<Participant> participants;
    Participant temp;
    int sequence_number;
    while (is >> sequence_number >> temp) {
        participants.push_back(temp);
    }
    return participants;
}

(3) task1.cpp

#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "participant.hpp"
#include "utilities.hpp"

const std::string input_filename = "./input_data.txt";
const std::string output_filename = "./sorted_results.txt";

void application() {
    std::vector<Participant> participants;

    try {
        participants = read_from_file(input_filename);
        std::sort(participants.begin(), participants.end(), compare_participants);
        display_to_console(participants);
        save_to_file(output_filename, participants);
    } catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << '
';
        return;
    }
}

int main() {
    application();
    return 0;
}

2. 実験テストコードの実行結果

入力ファイル input_data.txt と出力ファイル sorted_results.txt の内容を確認し、期待通りの結果が得られることを確認します。

タスク2:学生データの管理

このタスクでは、学生の情報を管理するクラスを作成し、ファイルからデータを読み込み、特定のルールでソートし、結果を表示および保存するプログラムを作成します。

1. ソースコード

(1) student.hpp

#pragma once

#include <iosfwd>
#include <string>

class Student {
public:
    Student() = default;
    ~Student() = default;

    const std::string get_major() const;
    int get_score() const;

    friend std::ostream& operator<<(std::ostream& os, const Student& s);
    friend std::istream& operator>>(std::istream& is, Student& s);

private:
    int student_id;
    std::string student_name;
    std::string major;
    int score; // 0-100
};

(2) student.cpp

#include "student.hpp"
#include <iomanip>

const std::string Student::get_major() const {
    return major;
}

int Student::get_score() const {
    return score;
}

std::ostream& operator<<(std::ostream& os, const Student& s) {
    os << std::left;
    os << std::setw(10) << s.student_id
        << std::setw(10) << s.student_name
        << std::setw(10) << s.major
        << std::setw(10) << s.score;
    return os;
}

std::istream& operator>>(std::istream& is, Student& s) {
    is >> s.student_id >> s.student_name >> s.major >> s.score;
    return is;
}

(3) student_manager.hpp

#pragma once
#include <string>
#include <vector>
#include "student.hpp"

class StudentManager {
public:
    void load_data(const std::string& file); // データファイルをロードする(スペース区切り)
    void sort_students();                   // ソート:専門分野の辞書順昇順、同専門のスコア降順
    void display() const;                   // 画面に表示する
    void save_data(const std::string& file) const; // ファイルに保存する

private:
    void write_to_stream(std::ostream& os) const; // 任意の出力ストリームにデータを書き込む

private:
    std::vector<Student> students;
};

(4) student_manager.cpp

#include "student_manager.hpp"
#include <fstream>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <algorithm>

void StudentManager::load_data(const std::string& file) {
    std::ifstream is(file);
    if (!is) {
        throw std::runtime_error("ファイル " + file + " を開けません");
    }

    std::string line;
    std::getline(is, line); // タイトル行をスキップ
    int line_number = 1;

    while (std::getline(is, line)) {
        std::istringstream iss(line);
        Student s;
        if (iss >> s) {
            if (s.get_score() < 0 || s.get_score() > 100) {
                std::cerr << "[警告] 行 " << line_number << " の形式が不正です。スキップしました: " << s << std::endl;
            } else {
                students.push_back(s);
            }
        } else {
            std::cerr << "[警告] 行 " << line_number << " のスコアが無効です。スキップしました: " << s << std::endl;
        }
        ++line_number;
    }
}

void StudentManager::sort_students() {
    if (students.empty()) {
        std::cerr << "(空です)" << std::endl;
        return;
    }

    std::sort(students.begin(), students.end(),
        [](const Student& a, const Student& b) {
            if (a.get_major() != b.get_major()) {
                return a.get_major() < b.get_major();
            }
            return a.get_score() > b.get_score();
        });
}

void StudentManager::display() const {
    write_to_stream(std::cout);
}

void StudentManager::save_data(const std::string& file) const {
    std::ofstream os(file);
    if (!os) {
        throw std::runtime_error("ファイル " + file + " を開けません");
    }
    write_to_stream(os);
}

void StudentManager::write_to_stream(std::ostream& os) const {
    for (const Student& s : students) {
        os << s << '
';
    }
}

(5) task2.cpp

#include <iostream>
#include <limits>
#include <string>
#include "student_manager.hpp"

const std::string input_file = "./student_data.txt";
const std::string output_file = "./sorted_students.txt";

void show_menu() {
    std::cout << "
********** 簡易アプリケーション **********
"
              "1. ファイルをロード
"
              "2. ソート
"
              "3. 画面に表示
"
              "4. ファイルに保存
"
              "5. 終了
"
              "選択してください: ";
}

void application() {
    StudentManager manager;

    while (true) {
        show_menu();
        int choice;
        std::cin >> choice;

        try {
            switch (choice) {
            case 1:
                manager.load_data(input_file);
                std::cout << "ロード成功
";
                break;
            case 2:
                manager.sort_students();
                std::cout << "ソート完了
";
                break;
            case 3:
                manager.display();
                std::cout << "表示完了
";
                break;
            case 4:
                manager.save_data(output_file);
                std::cout << "保存成功
";
                break;
            case 5:
                return;
            default:
                std::cout << "無効な入力
";
            }
        } catch (const std::exception& e) {
            std::cout << "エラー: " << e.what() << '
';
        }
    }
}

int main() {
    application();
    return 0;
}

2. テストコードの実行結果

入力ファイル student_data.txt と出力ファイル sorted_students.txt の内容を確認し、期待通りの結果が得られることを確認します。

実験のまとめ

ファイルストリームを利用することで、ファイルと標準出力間のデータのやり取りが可能になります。しかし、ファイル操作にはエラーが伴う可能性があるため、例外処理(throwtry-catch)を用いて、プログラムの継続的な実行を妨げずにエラーを適切に処理する必要があります。

タグ: C++ ファイルI/O データソート 例外処理 データ構造

7月2日 17:16 投稿