set と multiset 解答ページ | Programming Place Plus C++編【標準ライブラリ】 第8章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 と更新されており、今後も 3年ごとに更新されます。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

問題①

問題① 次のような3つのメンバ変数から構成される Studentクラスがあります。

class Student {
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点
};

複数の Studentオブジェクトを、得点の降順で管理する set で管理するプログラムを書いてください。Studentクラスに、自由にメンバを追加して構いません。


set に格納する要素の型のうち、一部だけを使ってソート基準にするには、自分で大小関係を表現する関数を作る必要があります。

この問題の場合、得点の「降順」という要求があるので、デフォルトで使われる std::less<> ではなく、std::greater<> を使うようにします。すると、Studentオブジェクトの比較を行える >演算子が必要になります。

まず、set の型の別名を定義しておきます。これは必須ではありませんが、実用上は、短い型名を作っておいた方が便利です。

typedef std::set<Student, std::greater<Student> > StudentsSet;

Studentオブジェクトの比較を行える >演算子が必要なので、Studentクラスに operator> を追加します。

class Student {
public:
    inline bool operator>(const Student& rhs) const
    {
        return mScore > rhs.mScore;
    }

    // 他のメンバは省略
};

本来、operator> を定義したなら、他の不等号も定義しておいた方が良いですが、今回は本筋ではないので省略しています。この辺りは、【言語解説】第19章を参照してください。

あとは、各メンバ変数に初期値を与えるためのコンストラクタや、確認のためにメンバ変数の値を出力する関数を用意すれば良いでしょう。すべてまとめると、次のようになります。

#include <iostream>
#include <set>
#include <string>
#include <functional>


class Student {
public:
    Student(const std::string& name, int grade, int score);

    inline bool operator>(const Student& rhs) const
    {
        return mScore > rhs.mScore;
    }

    void Print() const;

private:
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点
};

Student::Student(const std::string& name, int grade, int score) :
    mName(name), mGrade(grade), mScore(score)
{
}

void Student::Print() const
{
    std::cout << mName << " " << mGrade << " " << mScore << std::endl;
}


typedef std::set<Student, std::greater<Student> > StudentsSet;

int main()
{
    StudentsSet students;
    students.insert(Student("Itou Yuki", 2, 75));
    students.insert(Student("Takahashi Yuuta", 3, 88));
    students.insert(Student("Sugimoto Kouji", 1, 59));
    students.insert(Student("Matsuda Kouta", 3, 73));
    students.insert(Student("Endou Ami", 1, 86));

    StudentsSet::const_iterator itEnd = students.end();
    for (StudentsSet::const_iterator it = students.begin(); it != itEnd; ++it) {
        it->Print();
    }
}

実行結果

Takahashi Yuuta 3 88
Endou Ami 1 86
Itou Yuki 2 75
Matsuda Kouta 3 73
Sugimoto Kouji 1 59

問題②

問題② 問題①を変形して、名前の昇順や、学年の昇順での管理が行えるようにしてください。


キーを変更できるようにするには、比較用のクラスを導入して、関数オブジェクトを使えるようにします。その前に、比較用クラスから、Studentクラスのメンバ変数の値を知る必要があるので、Studentクラスに getter となるメンバ関数を追加しておきましょう。

【上級】フレンドクラスとして登録する方法もあります(【言語解説】第25章)。ソートの方法が複雑な場合(メンバ変数に対して、何らかの計算を施す必要がある等)には、フレンドにした方がいいかもしれません。

class Student {
public:
    inline const std::string GetName() const
    {
        return mName;
    }
    inline int GetGrade() const
    {
        return mGrade;
    }
    inline int GetScore() const
    {
        return mScore;
    }

    // 他のメンバは省略
};

次に、比較用のクラスを定義します。

class StudentCompare {
public:
    enum Mode {
        MODE_SCORE_DESCENDING,  // 得点の降順
        MODE_SCORE_ASCENDING,   // 得点の昇順
        MODE_NAME_DESCENDING,   // 名前の降順
        MODE_NAME_ASCENDING,    // 名前の昇順
        MODE_GRADE_DESCENDING,  // 学年の降順
        MODE_GRADE_ASCENDING,   // 学年の昇順
    };

public:
    explicit StudentCompare(Mode mode = MODE_SCORE_DESCENDING) :
        mMode(mode)
    {}

    inline bool operator()(const Student& s1, const Student& s2) const
    {
        switch (mMode) {
        default:
        case MODE_SCORE_DESCENDING:
            return s1.GetScore() > s2.GetScore();
        case MODE_SCORE_ASCENDING:
            return s1.GetScore() < s2.GetScore();
        case MODE_NAME_DESCENDING:
            return s1.GetName() > s2.GetName();
        case MODE_NAME_ASCENDING:
            return s1.GetName() < s2.GetName();
        case MODE_GRADE_DESCENDING:
            return s1.GetGrade() > s2.GetGrade();
        case MODE_GRADE_ASCENDING:
            return s1.GetGrade() < s2.GetGrade();
        }
    }

private:
    Mode  mMode;
};

この比較用クラスを使えるように、set の型別名も書き換えます。

typedef std::set<Student, StudentCompare> StudentsSet;

このスタイルでソート基準を指定する場合、set をインスタンス化するときに、コンストラクタで関数オブジェクトを渡します。

StudentCompare cmp(StudentCompare::MODE_NAME_ASCENDING);
StudentsSet students(cmp);

これで、StudentCompare の Mode を変えることで、任意の方法でソートできるようになりました。すべてのコードをまとめると、次のようになります。

#include <iostream>
#include <set>
#include <string>


class Student {
public:
    Student(const std::string& name, int grade, int score);

    inline const std::string GetName() const
    {
        return mName;
    }
    inline int GetGrade() const
    {
        return mGrade;
    }
    inline int GetScore() const
    {
        return mScore;
    }

    void Print() const;

private:
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点
};

Student::Student(const std::string& name, int grade, int score) :
    mName(name), mGrade(grade), mScore(score)
{
}

void Student::Print() const
{
    std::cout << mName << " " << mGrade << " " << mScore << std::endl;
}


class StudentCompare {
public:
    enum Mode {
        MODE_SCORE_DESCENDING,  // 得点の降順
        MODE_SCORE_ASCENDING,   // 得点の昇順
        MODE_NAME_DESCENDING,   // 名前の降順
        MODE_NAME_ASCENDING,    // 名前の昇順
        MODE_GRADE_DESCENDING,  // 学年の降順
        MODE_GRADE_ASCENDING,   // 学年の昇順
    };

public:
    explicit StudentCompare(Mode mode = MODE_SCORE_DESCENDING) :
        mMode(mode)
    {}

    inline bool operator()(const Student& s1, const Student& s2) const
    {
        switch (mMode) {
        default:
        case MODE_SCORE_DESCENDING:
            return s1.GetScore() > s2.GetScore();
        case MODE_SCORE_ASCENDING:
            return s1.GetScore() < s2.GetScore();
        case MODE_NAME_DESCENDING:
            return s1.GetName() > s2.GetName();
        case MODE_NAME_ASCENDING:
            return s1.GetName() < s2.GetName();
        case MODE_GRADE_DESCENDING:
            return s1.GetGrade() > s2.GetGrade();
        case MODE_GRADE_ASCENDING:
            return s1.GetGrade() < s2.GetGrade();
        }
    }

private:
    Mode  mMode;
};



typedef std::set<Student, StudentCompare> StudentsSet;

int main()
{
    StudentCompare cmp(StudentCompare::MODE_NAME_ASCENDING);
    StudentsSet students(cmp);

    students.insert(Student("Itou Yuki", 2, 75));
    students.insert(Student("Takahashi Yuuta", 3, 88));
    students.insert(Student("Sugimoto Kouji", 1, 59));
    students.insert(Student("Matsuda Kouta", 3, 73));
    students.insert(Student("Endou Ami", 1, 86));

    StudentsSet::const_iterator itEnd = students.end();
    for (StudentsSet::const_iterator it = students.begin(); it != itEnd; ++it) {
        it->Print();
    }
}

実行結果

Endou Ami 1 86
Itou Yuki 2 75
Matsuda Kouta 3 73
Sugimoto Kouji 1 59
Takahashi Yuuta 3 88


参考リンク


更新履歴

’2015/9/23 新規作成。



第8章のメインページへ

C++編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る