42. 비교자의 strict weak ordering 위반과 표준 알고리즘의 정의되지 않은 동작

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C++
// [주제] 비교자(comparator)의 strict weak ordering 요구사항과
//        표준 알고리즘·연관 컨테이너의 동작
//
// 게임서버는 랭킹 정렬, 매칭 대기열 우선순위, std::map/std::set 키 비교에
// 커스텀 비교자를 자주 쓴다. 아래는 실제 코드에서 흔히 보이는 다섯 가지 비교자다.
//
// [과제]
//  1) Case1 ~ Case5 를 각각 실행했을 때 프로그램이 어떻게 동작할지 예측하라.
//     "정렬 결과가 이렇게 나온다" / "결과가 정해지지 않는다" / "예측 불가" 중
//     무엇에 해당하는지, 그리고 그렇게 판단한 근거를 말하라.
//  2) std::sort 가 잘못된 비교자를 받았을 때, 왜 단순히 "잘못 정렬된 결과"가 아니라
//     세그폴트나 힙 손상으로 이어질 수 있는가?
//     libstdc++ 의 introsort 구현이 어떤 루프를 도는지 수준에서 설명하라.
//  3) Case4 의 std::map 은 어떤 키를 "같은 키"로 취급하는가?
//     std::map 이 동치(equivalence)를 판정하는 식을 쓰고, operator== 와 무엇이 다른지 설명하라.
//  4) Case5 처럼 NaN 이 섞였을 때 비교자가 어떤 성질을 잃는지 설명하라.
//  5) 각 비교자를 어떻게 고쳐야 하는지 제시하라.

#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include <algorithm>

struct Entry {
    std::uint32_t id;
    float         score;
    int           tier;     // 0 = bronze, 1 = silver, 2 = gold
    std::string   name;
};

static std::vector<Entry> MakeRanking() {
    std::vector<Entry> v;
    v.reserve(64);
    for (std::uint32_t i = 0; i < 64; ++i) {
        Entry e;
        e.id    = i;
        e.score = static_cast<float>(i % 8) * 100.0f;   // 같은 점수가 8개씩 겹친다
        e.tier  = static_cast<int>(i % 3);
        e.name  = "player" + std::to_string(i);
        v.push_back(e);
    }
    return v;
}

static void Dump(const char* tag, const std::vector<Entry>& v) {
    std::cout << tag << ": ";
    for (std::size_t i = 0; i < v.size() && i < 8; ++i) std::cout << v[i].id << ' ';
    std::cout << "...\n";
}

// ── Case 1: 랭킹 내림차순 정렬 ────────────────────────────────────────────
void Case1() {
    auto v = MakeRanking();
    std::sort(v.begin(), v.end(), [](const Entry& a, const Entry& b) {
        return a.score >= b.score;
    });
    Dump("Case1", v);
}

// ── Case 2: 티어 우선, 같은 티어면 점수 높은 순 ───────────────────────────
void Case2() {
    auto v = MakeRanking();
    std::sort(v.begin(), v.end(), [](const Entry& a, const Entry& b) {
        return a.tier < b.tier || a.score > b.score;
    });
    Dump("Case2", v);
}

// ── Case 3: 점수가 "거의 같으면" 같은 순위로 본다 ─────────────────────────
void Case3() {
    auto v = MakeRanking();
    for (auto& e : v) e.score = static_cast<float>(e.id) * 0.4f;   // 0.0, 0.4, 0.8, ...
    std::sort(v.begin(), v.end(), [](const Entry& a, const Entry& b) {
        return a.score < b.score - 1.0f;   // 1.0 이내면 동점 취급
    });
    Dump("Case3", v);
}

// ── Case 4: 매칭 대기열을 std::map 으로 관리 ──────────────────────────────
struct QueueKey {
    int           mmr;
    std::uint32_t playerId;
};

struct QueueKeyLess {
    bool operator()(const QueueKey& a, const QueueKey& b) const {
        if (a.mmr / 100 != b.mmr / 100) return a.mmr / 100 < b.mmr / 100;
        return a.playerId <= b.playerId;
    }
};

void Case4() {
    std::map<QueueKey, std::string, QueueKeyLess> q;
    q[QueueKey{1250, 7}]  = "alice";
    q[QueueKey{1299, 7}]  = "bob";
    q[QueueKey{1250, 42}] = "carol";
    q[QueueKey{1310, 7}]  = "dave";

    std::cout << "Case4: size=" << q.size() << '\n';
    auto it = q.find(QueueKey{1250, 7});
    std::cout << "Case4: find(1250,7) -> " << (it == q.end() ? "miss" : it->second) << '\n';
    auto it2 = q.find(QueueKey{1280, 7});
    std::cout << "Case4: find(1280,7) -> " << (it2 == q.end() ? "miss" : it2->second) << '\n';
}

// ── Case 5: 점수 계산에 NaN 이 섞이는 경우 ────────────────────────────────
void Case5() {
    auto v = MakeRanking();
    v[10].score = std::numeric_limits<float>::quiet_NaN();
    v[37].score = std::numeric_limits<float>::quiet_NaN();

    std::sort(v.begin(), v.end(), [](const Entry& a, const Entry& b) {
        return a.score < b.score;
    });
    Dump("Case5", v);

    bool found = std::binary_search(v.begin(), v.end(), v[0],
        [](const Entry& a, const Entry& b) { return a.score < b.score; });
    std::cout << "Case5: binary_search -> " << found << '\n';
}

int main() {
    Case1();
    Case2();
    Case3();
    Case4();
    Case5();
    return 0;
}
내 리뷰 · C++
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.