🔥 재귀 열거형

349자
4분

열거형(enumeration)은 관련된 값들을 하나의 그룹으로 묶어서 표현하는 타입입니다. 그런데 가끔은 열거형의 case가 열거형 자신의 인스턴스를 연관값(associated value)으로 가지는 경우가 있어요. 이런 열거형을 재귀 열거형(recursive enumeration)이라고 부릅니다.

재귀 열거형을 정의할 때는 컴파일러에게 간접 계층(layer of indirection)을 삽입하도록 지시하기 위해 indirect 키워드를 case 앞에 써줘야 해요. 예를 들어, 간단한 산술 표현식을 저장하는 열거형을 아래와 같이 정의할 수 있겠죠.

swift
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
swift
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

위 코드에서 additionmultiplication case는 연관값으로 ArithmeticExpression 타입을 가지고 있어요. 즉, 산술 표현식이 다른 산술 표현식을 포함할 수 있다는 뜻이죠.

만약 열거형의 모든 case가 연관값을 가진다면 indirect 키워드를 열거형 시작 부분에 한 번만 써주면 됩니다.

swift
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
swift
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

이렇게 정의한 ArithmeticExpression 열거형은 세 종류의 산술 표현식을 저장할 수 있어요.

  1. 하나의 정수(number)
  2. 두 개의 표현식을 더한 것(addition)
  3. 두 개의 표현식을 곱한 것(multiplication)

예를 들어 (5 + 4) * 2 라는 표현식을 나타내 보겠습니다. 곱셈의 오른쪽에는 숫자가, 왼쪽에는 덧셈 표현식이 있네요. 이렇게 데이터가 중첩되어 있으므로 이를 저장하는 열거형도 중첩, 즉 재귀적이어야 합니다.

swift
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
swift
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

재귀적인 구조의 데이터를 다루기에 적합한 방법 중 하나는 재귀 함수를 사용하는 거예요. 아래는 위에서 정의한 산술 표현식을 계산하는 함수를 재귀적으로 구현한 예시입니다.

swift
func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}
 
print(evaluate(product))
// "18" 출력
swift
func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}
 
print(evaluate(product))
// "18" 출력

위 함수는 다음과 같이 동작해요.

  • .number case일 때는 연관값을 그대로 반환합니다.
  • .addition case일 때는 왼쪽 표현식과 오른쪽 표현식을 각각 재귀적으로 계산한 뒤 그 결과를 더합니다.
  • .multiplication case일 때는 왼쪽 표현식과 오른쪽 표현식을 각각 재귀적으로 계산한 뒤 그 결과를 곱합니다.

이렇게 열거형의 case가 자기 자신을 연관값으로 가질 수 있게 하면 계층적이고 재귀적인 데이터 구조를 표현하기에 아주 유용하답니다. 🌳

text
           product
             / \\
            /   \\
           /     \\
          /       \\
         /         \\
       sum         number
       / \\           |
      /   \\          2
     /     \\
 number   number
    |       |
    5       4
text
           product
             / \\
            /   \\
           /     \\
          /       \\
         /         \\
       sum         number
       / \\           |
      /   \\          2
     /     \\
 number   number
    |       |
    5       4

YouTube 영상

채널 보기
벡터의 정의와 덧셈 연산 | 선형대수학
Trie 자료구조 파이썬 구현: Search와 Starts With 연산 | Trie 자료구조 이야기
스칼라 곱셈과 내적의 기하학적 의미 | 선형대수학
마지막편, 10억 개 데이터 검색이 0.3ms면 끝나는 이유와 LSM-Tree의 등장
우리가 매일 쓰는 맞춤법 검사기와 라우터 속에 숨겨진 알고리즘은? | Trie 자료구조 이야기
마지막편, 트라이 노드를 50% 이상 줄이는 방법? 압축 트라이 성능 분석 | Trie 자료구조 이야기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
내적의 기하학적 의미와 코사인 유사도 원리 | 선형대수학