BinaryHeap을 이용해 내림차순으로 정렬하기

BinaryHeap은 가장 큰 값을 맨 앞에 위치시키고, 나머지 값들은 임의의 순서대로 저정하지만, pop()을 사용하면 내림차순으로 정렬한 것처럼 보이게 할 수 있습니다.

1. 코드

use std::collections::BinaryHeap;

fn main() {
    let mut jobs = BinaryHeap::new();

    jobs.push((100, "Write back to email from the CEO"));
    jobs.push((80, "Finish the report today"));
    jobs.push((5, "Watch Some YouTube"));
    jobs.push((70, "Tell your team members thanks for always working hand"));
    jobs.push((30, "Plan who to hire next for the team"));

    while let Some(job) = jobs.pop() {
        println!("You need to: {}", job.1)
    }
}

위 코드를 실행하면 아래와 같이 중요한 일부터, 다시 말해 jobs의 첫번째 항목 점수를 기준으로 내림차순으로 정렬됩니다.

2. BinaryHeap의 기본 비교 규칙

BinaryHeap은 내부적으로 T: Ord 트레이트를 요구합니다.
즉, 요소 T 간의 대소 비교(>, <)가 가능해야 하죠.

Rust에서 tuple(예: (i32, &str))도 기본적으로 Ord를 구현하고 있으며, 사전식(lexicographical) 비교를 합니다.

(a₁, b₁) < (a₂, b₂)
⟹ a₁ < a₂ 이면 그걸로 비교 종료,
⟹ a₁ == a₂ 이면 b₁과 b₂를 비교함.

즉,
(100, “CEO”)와 (80, “report”)를 비교할 때
→ 100 > 80이므로 (100, “CEO”)가 “더 큰” 값으로 간주됩니다.

이 때문에 BinaryHeap은 자동으로 첫 번째 원소(i32) 를 기준으로 최대 힙을 구성합니다.

3. “맨 위(root)”만 정렬되어 있는 이유

BinaryHeap은 완전이진트리(Complete Binary Tree) 구조로 되어 있습니다.

  • 힙은 전체가 정렬되어 있지는 않습니다.
  • 단지 루트 노드(맨 위) 가 항상 가장 큰 값입니다.
  • 나머지는 “힙 속성(heap property)”만 유지합니다: 부모 ≥ 자식

그래서 jobs.pop()을 반복할 때마다

  • 매번 가장 큰 (i32, &str) 튜플이 하나씩 빠집니다.
  • 내부적으로 남은 값들이 재배열되어 다시 최대 힙이 됩니다.

결과적으로, pop()을 반복하면 내림차순 정렬된 순서로 출력되는 것처럼 보이는 거예요.

4. 즉, “항상 정렬되어 있는 게 아니라”

BinaryHeap의 내부 저장순서는 무작위입니다.
하지만 pop()할 때마다 가장 큰 값이 나오는 이유는
“루트만 최대값이 되도록 하는 BinaryHeap 속성” 때문입니다.

5. 정리

개념의미
내부 저장 순서무작위 (정렬 아님)
비교 기준Ord 구현(튜플이면 첫 번째 요소부터 비교)
pop()동작항상 최대값 반환
출력 결과내림차순처럼 보임 (사실은 매번 루트 제거 과정 때문)

6. 오름차순 정렬

만약 작은 값부터 꺼내고 싶다면, 즉 “최소 힙(min-heap)”을 원한다면 이렇게 하면 됩니다:

use std::cmp::Reverse;
use std::collections::BinaryHeap;

fn main() {
    let mut jobs = BinaryHeap::new();

    jobs.push(Reverse((100, "CEO")));
    jobs.push(Reverse((80, "Report")));
    jobs.push(Reverse((5, "YouTube")));

    while let Some(Reverse(job)) = jobs.pop() {
        println!("You need to: {}", job.1);
    }
}

이렇게 하면 작은 i32부터 오름차순으로 출력됩니다.

요약하자면 👇

BinaryHeap은 내부가 정렬된 컬렉션이 아니라, 루트에만 최대값을 유지하는 자료구조이고,
(i32, &str) 튜플은 기본적으로 첫 번째 값으로 비교되기 때문에,
pop()을 반복하면 결과적으로 i32 기준 내림차순 출력처럼 보입니다.

패턴 매칭과 제어 흐름 심화 (match, if let, while let)

Rust는 패턴 매칭을 통해 다양한 값 구조를 해체하고 조건에 따라 분기할 수 있도록 지원합니다. match, if let과 while let은 이를 위해 자주 사용하는 문법입니다.


1. 복습: match 표현식

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
  • enum은 Coin의 종류를 열거하고 있습니다.
  • match는 모든 경우를 명시해야 합니다. 위 경우 Coin의 종류 4개를 모두 다루고 있습니다.
  • 컴파일러가 exhaustiveness(완전성 – 모든 경우를 다룸)을 검사합니다.
  • 함수에서 coin을 인수로 받는데, type은 Coin 열거형이고, 반환 type은 u8로 0과 양수입니다.
  • 열거형을 분기할 때 열거형의 이름에 variant(변형)을 ::으로 연결하며,
    반환 값은 => 다음에 적고, 각각의 경우를 ,로 구분합니다.

위 코드를 실행하려면 main 함수가 아래와 같이 필요합니다.

fn main() {
    let coin1 = Coin::Penny;
    println!("{} 센트", value_in_cents(coin1));
}

Coin의 종류를 Coin:: 다음에 입력하면 value_in_cents 함수에 의해 해당하는 센트를 표시합니다.

{}가 값이 출력될 위치(placeholder)이고, 값은 value_in_cents(coin1)으로 구합니다.


2. 패턴 바인딩

enum Message {
    Hello { id: i32 },
}

fn main() {
    let msg = Message::Hello { id: 42 };

    match msg {
        Message::Hello { id: 1 } => println!("ID는 1"),
        Message::Hello { id } => println!("다른 ID: {}", id),
    }
}
  • Message 열거형은 Hello 구조체를 변형으로 갖으므로 id에 해당하는 정수를 값으로 받습니다.
  • let msg = Message::Hello { id: 42 };
    : Message 열거형으로 Hello 구조체를 만드는데 id는 42입니다.
  • 이 패턴은 Hello { id: i32 }에 매칭되며, id 값을 꺼내서 변수 id에 바인딩합니다.
  • 따라서, 두번째 분기만 있어도 되는데, 1인 경우를 별도의 경우로 빼낸 것입니다.
    다시 말해, 특정 값 비교와 변수 추출을 동시에 할 수 있습니다.

3. 와일드카드와 _, |, .., ..=

fn main() {
    let x = 7;

    match x {
        1 => println!("하나"),
        2 | 3 => println!("둘 또는 셋"),
        4..=6 => println!("4에서 6 사이"),
        _ => println!("기타"),
    }
}
  • |: 여러 패턴 매칭(or).
    위 경우 2 | 3은 2또는 3인 경우가 됩니다.
  • ..=6: 범위 매칭(=은 포함, 없으면 미만).
    위 경우 4..=6은 4이상 6이하가 되며, 4..6은 4이상 6미만이므로 4와 5만 해당됩니다.
  • _: 나머지 모든 경우 (wildcard, else)
  • x의 데이터 형식은 정수이므로 i32가 돼서 부호있는 32비트 정수이므로 -2^31 (약 -21억)부터 2^31 – 1 (약 21억)까지의 값을 표현 가능
  • 위 코드를 실행하면 x가 7이므로 _에 해당되어 “기타”가 출력됩니다.

4. if let 표현식

match가 너무 장황할 때 if let을 사용해 간단히 표현할 수 있습니다.

let some_value = Some(5);

if let Some(x) = some_value {
    println!("값은 {}", x);
} else {
    println!("값이 없습니다");
}
  • Some은 Option enum의 variant중 하나로서, if let을 이용해서 match의 단일 분기를 간단히 표현한 것이며,
    match 연산자를 이용하면 아래와 같이 해야 됩니다.
    match some_value {
        Some(x) => println!("값은 {}", x),
        None => println!("값이 없습니다"),
    }
  • if let Some(x) = some_value는 “x가 5라면”이 되므로, “값은 5″가 출력됩니다.
  • match 연산자의 경우는 None을 사용하는데, if 제어문이라 else를 사용했는데 없어도 됩니다.

5. while let 반복문

let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("꺼낸 값: {}", top);
}
  • vec은 아직 설명하지 않았는데, array가 크기가 고정되어 있다면 vec은 가변 길이의 배열입니다.
  • while let을 이용하면 값이 존재하는 동안 반복합니다.
  • Option, Result와 같이 반복 가능한 열거형에 유용
  • 위 코드를 main 함수에 넣고 실행하면 아래와 같이 출력됩니다.

🧠 요약

문법설명
match여러 경우를 완전하게 분기
바인딩패턴 내부에서 변수에 값 대입 가능
_기타 모든 경우 처리
if let한 가지 매칭에 적합한 축약형
while let값이 남아 있는 동안 반복