#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <numeric> // accumulate
#include <algorithm> // max_element
#include <iomanip> // fixed, setprecision
using namespace std;
class ScoreManager
{
private:
// Id를 기반으로 과목과 점수 저장 리스트
multimap<int, pair<string, int>> ScoreList;
// 과목을 기반으로 점수와 Id 저장 리스트
map<string, vector<pair<int, int>>> SubjectScores;
public:
// 성적 입력 기능
void InitScore(int Id, string Subject, int Score);
// 학생의 전체 성적 조회 기능
void DisplayStudentScores(int Id);
// 전체 학생의 평균 점수 출력 기능
void DisplayAverageScore();
// 과목별 최고 점수 학생 조회 기능
void DisplayTopScoreStudent(string Subject);
// 성적 구간 검색 기능
void DisplayStudentsInScoreRange(const string& Subject, const int& MinScore, const int& MaxScore);
};
void ScoreManager::InitScore(int Id, string Subject, int Score)
{
ScoreList.insert({ Id, {Subject, Score} });
SubjectScores[Subject].push_back({ Id, Score });
}
void ScoreManager::DisplayStudentScores(int Id)
{
if (ScoreList.empty())
{
cout << "저장되어 있는 데이터가 없습니다." << endl;
return;
}
cout << "학생 ID " << to_string(Id) << "의 성적:" << endl;
map<string, int> SortedList;
auto Range = ScoreList.equal_range(Id);
for (auto It = Range.first; It != Range.second; ++It)
{
SortedList[It->second.first] = It->second.second;
}
for (auto It = SortedList.begin(); It != SortedList.end(); ++It)
{
cout << "- " << It->first << ": " << It->second << "점" << endl;
}
cout << endl;
}
void ScoreManager::DisplayAverageScore()
{
if (SubjectScores.empty())
{
cout << "저장되어 있는 데이터가 없습니다." << endl;
return;
}
cout << "전체 과목 평균 점수:" << endl;
for (auto It = SubjectScores.begin(); It != SubjectScores.end(); ++It)
{
string Subject = It->first;
vector<pair<int,int>> ScoreList = It->second;
int TotalScore = accumulate(ScoreList.begin(), ScoreList.end(), 0, [](int acc, const auto& p)
{
return acc + p.second;
});
float AverageScore = static_cast<float>(TotalScore) / ScoreList.size();
cout << "- " << Subject << ": " << fixed << setprecision(2) << AverageScore << "점" << endl;
}
cout << endl;
}
void ScoreManager::DisplayTopScoreStudent(string Subject)
{
if (SubjectScores.find(Subject) == SubjectScores.end())
{
cout << Subject + " 과목에 대한 데이터가 없습니다." << endl;
return;
}
auto Top = max_element(SubjectScores[Subject].begin(), SubjectScores[Subject].end(), [](const auto& a, const auto& b)
{
return a.second < b.second;
});
cout << Subject << " 최고 점수: " << Top->second << "점" << endl;
cout << "- 학생 ID: " << Top->first << endl << endl;
}
void ScoreManager::DisplayStudentsInScoreRange(const string& Subject, const int& MinScore, const int& MaxScore)
{
vector<pair<int,int>> ScoreList = SubjectScores[Subject];
set<int> IdList;
for (auto& Score : ScoreList)
{
if (MinScore <= Score.second && Score.second <= MaxScore)
{
IdList.insert(Score.first);
}
}
cout << Subject << "과목 " << to_string(MinScore) << "점 ~ " << to_string(MaxScore) << "점의 점수를 가진 학생:" << endl;
for (int Id : IdList)
{
cout << "- 학생 ID: " << Id << endl;
}
}
int main()
{
ScoreManager SM;
SM.InitScore(1001, "C++", 85);
SM.InitScore(1001, "알고리즘", 90);
SM.InitScore(1002, "C++", 92);
SM.InitScore(1002, "알고리즘", 78);
SM.InitScore(1003, "C++", 95);
SM.InitScore(1003, "알고리즘", 95);
SM.DisplayStudentScores(1001);
SM.DisplayAverageScore();
SM.DisplayTopScoreStudent("알고리즘");
SM.DisplayStudentsInScoreRange("C++", 90, 100);
return 0;
}
📍 multimap과 map의 사용
한 학생이 여러 과목을 수강할 수 있기에 multimap 컨테이너를 선택해 데이터를 저장하였고, 전체 과목 평균 점수를 구한다던가 한 과목에서 최고 점수를 기록한 학생을 찾는 등의 검색이 빠를 수 있도록 Subject를 key 값으로 데이터를 저장하는 map 컨테이너를 추가적으로 사용해서 전체 데이터를 관리하였다.
// Id를 기반으로 과목과 점수 저장 리스트
multimap<int, pair<string, int>> ScoreList;
// 과목을 기반으로 점수와 Id 저장 리스트
map<string, vector<pair<int, int>>> SubjectScores;
📍 accumulate의 사용
전체 과목 평균을 구해 출력하는 DisplayAverageScore 함수에서 과목의 점수 합산을 할 때 사용하여, 코드가 간결해지도록 하였다.
DisplayTopScoreStudent 함수에서 과목 점수 중 가장 높은 점수를 찾는데에 max_element 메서드를 사용했다. vector<pair<int, int>>형태에서 최대 값을 찾는 것이기에 람다 함수를 사용해 두 번째 값을 기준으로 최대 값을 찾도록 하였다.
auto Top = max_element(SubjectScores[Subject].begin(), SubjectScores[Subject].end(), [](const auto& a, const auto& b)
{
return a.second < b.second;
});
람다 함수의 비교연산자를 작성할 때, 최대 값이 나오지 않는 결과가 계속 생겨서 헷갈렸던 부분이 있었다.
return a > b를 해야 최대 값을 갱신한다고 생각했는데, 오히려 반대였다. 결론적으로 return a < b를 해야했고 이는 min_element에서도 같은 조건을 사용해야된다.
max_element: 비교 결과가 true일 때, 현재 값(b)이 더 크다고 간주하고 갱신한다. min_element: 비교 결과가 true일 때, 현재 값 (a)이 더 작다고 간주하고 그대로 유지한다.
둘 다 같은 비교연산자를 사용하지만, 기준이 되는 값이 다르므로 해석 방식의 차이로 인해 다른 결과를 도출하게 된다.
📍 fixed와 setprecision의 사용
fixed는 소수점을 기준으로 소수점 이하의 자릿수를 고정하는 역할을 한다. setprecision은 숫자를 출력할 때 n개의 유효 자릿수(정수부 + 소수부)를 출력도록 설정한다.
fixed와 setprecision을 함께 사용하여 소수점 아래 자릿수를 고정하였다. 이 둘은 iomanip 헤더에 포함된 조정자이다.