트레이트와 반복자 연습 문서의 문제 4 풀이 코드다.

//! 22 트레이트와 반복자 연습 p4: 대소문자를 가리지 않는 사용자 이름 키.
//! PartialEq·Eq·Hash·PartialOrd·Ord를 같은 기준(소문자)으로 맞춰 HashSet과 정렬이 함께 맞게 동작하게 한다.
//! 변형: Ord만 대소문자를 구별하게 두면 BTreeSet의 개수가 만드는 방법(collect·insert)에 따라 달라지는 것을 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/22_traits-iterators-practice_p4.rs"

use std::cmp::Ordering;
use std::collections::{BTreeSet, HashSet};
use std::hash::{Hash, Hasher};

#[derive(Debug, Clone)]
struct Username(String);

impl Username {
    fn key(&self) -> impl Iterator<Item = u8> + '_ {
        self.0.bytes().map(|b| b.to_ascii_lowercase())
    }
}

impl PartialEq for Username {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq_ignore_ascii_case(&other.0)
    }
}

impl Eq for Username {}

impl Hash for Username {
    fn hash<H: Hasher>(&self, state: &mut H) {
        for b in self.key() {
            state.write_u8(b);
        }
        state.write_u8(0xff);
    }
}

impl Ord for Username {
    fn cmp(&self, other: &Self) -> Ordering {
        self.key().cmp(other.key())
    }
}

impl PartialOrd for Username {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

/// 변형: Eq·Hash는 대소문자를 무시하는데 Ord만 원래 문자열로 비교한다(규칙 위반).
#[derive(Debug, Clone)]
struct CaseSensitiveOrd(String);

impl PartialEq for CaseSensitiveOrd {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq_ignore_ascii_case(&other.0)
    }
}

impl Eq for CaseSensitiveOrd {}

impl Hash for CaseSensitiveOrd {
    fn hash<H: Hasher>(&self, state: &mut H) {
        for b in self.0.bytes() {
            state.write_u8(b.to_ascii_lowercase());
        }
        state.write_u8(0xff);
    }
}

impl Ord for CaseSensitiveOrd {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.cmp(&other.0)
    }
}

impl PartialOrd for CaseSensitiveOrd {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

/// 처음 나온 표기를 남기고 중복을 없앤 뒤, 대소문자를 무시한 사전 순으로 돌려준다.
fn unique_sorted(names: &[&str]) -> Vec<String> {
    let mut seen = HashSet::new();
    let mut kept: Vec<Username> = names
        .iter()
        .map(|n| Username(n.to_string()))
        .filter(|u| seen.insert(u.clone()))
        .collect();
    kept.sort();
    kept.into_iter().map(|u| u.0).collect()
}

fn main() {
    let input = ["Kim", "kim", "LEE", "Park", "lee", "KIM"];
    let out = unique_sorted(&input);
    println!("{out:?}");
    assert_eq!(out, ["Kim", "LEE", "Park"]);

    // HashSet과 BTreeSet이 같은 개수를 센다: Eq·Hash·Ord가 같은 기준이라서다.
    let h: HashSet<Username> = input.iter().map(|n| Username(n.to_string())).collect();
    let b: BTreeSet<Username> = input.iter().map(|n| Username(n.to_string())).collect();
    assert_eq!((h.len(), b.len()), (3, 3));
    println!("HashSet {}개, BTreeSet {}개", h.len(), b.len());

    // 변형: Ord만 어긋나면 결과가 정해지지 않는다. 같은 입력인데 만드는 방법에 따라 개수가 다르다.
    let h2: HashSet<CaseSensitiveOrd> = input
        .iter()
        .map(|n| CaseSensitiveOrd(n.to_string()))
        .collect();
    let b2: BTreeSet<CaseSensitiveOrd> = input
        .iter()
        .map(|n| CaseSensitiveOrd(n.to_string()))
        .collect();
    let mut b3: BTreeSet<CaseSensitiveOrd> = BTreeSet::new();
    for n in input {
        b3.insert(CaseSensitiveOrd(n.to_string()));
    }
    let o2: Vec<&str> = b2.iter().map(|u| u.0.as_str()).collect();
    let o3: Vec<&str> = b3.iter().map(|u| u.0.as_str()).collect();
    println!("변형: HashSet {}개", h2.len());
    println!("변형: BTreeSet collect {}개 {o2:?}", b2.len());
    println!("변형: BTreeSet insert {}개 {o3:?}", b3.len());
    assert_eq!(h2.len(), 3);
    // 아래 두 값은 rustc 1.99.0 표준 라이브러리에서 본 결과다. 규칙을 어긴 타입이라 약속된 값이 아니다.
    assert_eq!(o2, ["Kim", "LEE", "Park", "kim", "lee"]);
    assert_eq!(o3, ["KIM", "Kim", "LEE", "Park", "kim", "lee"]);
    println!("ALL CHECKS PASSED");
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::hash::DefaultHasher;

    fn h(u: &Username) -> u64 {
        let mut s = DefaultHasher::new();
        u.hash(&mut s);
        s.finish()
    }

    #[test]
    fn equal_means_same_hash() {
        let pairs = [("Kim", "KIM"), ("a", "A"), ("Rust", "rUST")];
        for (x, y) in pairs {
            let (a, b) = (Username(x.into()), Username(y.into()));
            assert_eq!(a, b);
            assert_eq!(h(&a), h(&b));
            assert_eq!(a.cmp(&b), Ordering::Equal);
        }
    }

    #[test]
    fn prefix_free() {
        // ("ab", "c")와 ("a", "bc")를 구별하기 위한 끝 표시
        let a = (Username("ab".into()), Username("c".into()));
        let b = (Username("a".into()), Username("bc".into()));
        let mut s1 = DefaultHasher::new();
        a.hash(&mut s1);
        let mut s2 = DefaultHasher::new();
        b.hash(&mut s2);
        assert_ne!(s1.finish(), s2.finish());
    }
}