Scala for Backend Engineering & Functional Programming · 강의

누산기 패턴

재귀를 통해 상태를 전달해 보세요.

레슨 2/413개 단계

누산기 패턴은(는) CoddyKit의 무료 Scala for Backend Engineering & Functional Programming 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Scala for Backend Engineering & Functional Programming 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Scala for Backend Engineering & Functional Programming 강의에는 총 4개의 강의가 포함되어 있습니다.

누산기를 사용하는 이유

일반 재귀는 재귀 호출이 반환된 뒤 호출 스택을 거슬러 올라가며 결과를 만듭니다.

누산기는 대신 실행 중인 결과를 각 호출로 전달하므로, 기저 사례에 도달했을 때 답이 이미 준비되어 있습니다.

이 작은 변화로 꼬리 재귀와 일정한 스택 사용량을 활용할 수 있습니다.

도우미 함수

누산기 패턴은 지금까지의 결과라는 추가 매개변수를 받는 내부 도우미 함수를 사용합니다.

바깥 함수는 시작 값(대개 0 또는 빈 목록)으로 도우미 함수를 시작하기만 합니다.

def sum(xs: List[Int]): Int = {
  def loop(rest: List[Int], acc: Int): Int = rest match {
    case Nil    => acc
    case h :: t => loop(t, acc + h)
  }
  loop(xs, 0)
}

누산기 실행하기

여기서는 누산기를 사용하여 목록의 합을 구하는 전체 프로그램을 살펴봅니다.

기저 사례가 0이 아니라 acc를 직접 반환한다는 점에 주목하세요. 목록을 따라 내려가면서 합계가 누적되었기 때문입니다.

def sum(xs: List[Int]): Int = {
  def loop(rest: List[Int], acc: Int): Int = rest match {
    case Nil    => acc
    case h :: t => loop(t, acc + h)
  }
  loop(xs, 0)
}

@main def run(): Unit =
  println(sum(List(1, 2, 3, 4)))  // 10

두 구조 비교하기

일반 재귀에서는 결합 단계(h + ...)가 내부 호출을 기다립니다.

누산기 버전에서는 호출하기 전에 결합이 이루어지고, 함수가 수행하는 마지막 작업이 호출입니다.

바로 이 마지막 호출이라는 특성 때문에 꼬리 재귀가 됩니다.

// Plain: combine after the call
case h :: t => h + sum(t)

// Accumulator: combine before the call
case h :: t => loop(t, acc + h)

꼬리 재귀

꼬리 재귀 호출은 재귀 호출이 함수의 마지막 동작이며 그 뒤에 수행할 작업이 없는 경우입니다.

Scala는 이를 반복문으로 최적화하여 하나의 스택 프레임을 재사용할 수 있으므로, 재귀가 아무리 깊어져도 스택이 넘치지 않습니다.

import scala.annotation.tailrec

@tailrec
def countDown(n: Int): Unit =
  if (n < 0) ()
  else { println(n); countDown(n - 1) }

@tailrec 어노테이션

@tailrec를 추가하면 컴파일러가 함수가 실제로 꼬리 재귀인지 확인합니다.

꼬리 재귀가 아니면 명확한 오류와 함께 컴파일이 실패합니다. 이렇게 하면 조용히 발생할 수 있는 성능 문제를 빌드 시점에 보장할 수 있습니다.

import scala.annotation.tailrec

def sum(xs: List[Int]): Int = {
  @tailrec
  def loop(rest: List[Int], acc: Int): Int = rest match {
    case Nil    => acc
    case h :: t => loop(t, acc + h)
  }
  loop(xs, 0)
}

@main def run(): Unit = println(sum((1 to 100000).toList))

목록 누적하기

누산기는 숫자만 저장할 필요가 없습니다. 컬렉션을 만들 수도 있습니다.

이 reverse 함수는 각 머리를 누산기의 앞에 붙이므로 자연스럽게 순서가 뒤집힙니다. ::로 앞에 붙이는 작업은 빠르므로 효율적입니다.

def reverse[A](xs: List[A]): List[A] = {
  def loop(rest: List[A], acc: List[A]): List[A] = rest match {
    case Nil    => acc
    case h :: t => loop(t, h :: acc)
  }
  loop(xs, Nil)
}

reverse 실행 과정

누산기는 비어 있는 상태에서 시작하여 입력을 처리하면서 커집니다.

각 머리를 acc의 앞에 넣기 때문에 첫 번째 요소가 마지막에 오며, 일정한 스택 비용으로 뒤집힌 목록이 만들어집니다.

def reverse[A](xs: List[A]): List[A] = {
  def loop(rest: List[A], acc: List[A]): List[A] = rest match {
    case Nil    => acc
    case h :: t => loop(t, h :: acc)
  }
  loop(xs, Nil)
}

@main def run(): Unit =
  println(reverse(List(1, 2, 3)))  // List(3, 2, 1)

여러 누산기 사용하기

도우미 함수는 여러 누산기를 동시에 전달할 수 있습니다.

여기서는 같은 반복에서 누적 곱과 개수를 추적하고 둘 다 튜플로 반환합니다.

각 누산기는 갱신된 값을 다음 호출로 전달합니다.

def stats(xs: List[Int]): (Int, Int) = {
  def loop(rest: List[Int], prod: Int, count: Int): (Int, Int) =
    rest match {
      case Nil    => (prod, count)
      case h :: t => loop(t, prod * h, count + 1)
    }
  loop(xs, 1, 0)
}

초기값 선택하기

시작 누산기 값은 연산의 항등원이어야 합니다.

덧셈에는 0, 곱셈에는 1, 목록 만들기에는 빈 목록, 문자열 결합에는 빈 문자열을 사용합니다.

잘못된 초기값은 조용히 잘못된 결과를 만듭니다.

// addition  -> seed 0
// product   -> seed 1
// list      -> seed Nil
// string    -> seed ""

결과의 순서

누산기 재귀는 요소를 왼쪽에서 오른쪽으로 처리하지만, 앞에 붙이는 누산기는 요소의 순서를 뒤집습니다.

목록을 만들 때 순서를 유지해야 한다면 마지막에 reverse를 사용하거나 뒤에 추가하세요. 다만 뒤에 추가하는 방식이 더 느립니다. 앞에 붙인 후 뒤집는 방식이 일반적인 관용구입니다.

def mapInc(xs: List[Int]): List[Int] = {
  def loop(rest: List[Int], acc: List[Int]): List[Int] = rest match {
    case Nil    => acc.reverse
    case h :: t => loop(t, (h + 1) :: acc)
  }
  loop(xs, Nil)
}

빠른 확인

누산기 재귀에 대한 정확한 설명을 고르세요.

복습

누산기는 실행 중인 결과를 재귀 호출을 따라 전달하므로 기저 사례가 그 값을 직접 반환할 수 있습니다.

이렇게 하면 재귀 호출이 꼬리 위치에 놓여 Scala의 꼬리 호출 최적화와 @tailrec 안전성 검사를 활용할 수 있습니다.

누산기는 연산의 항등원으로 초기화하고, 순서가 중요할 때는 마지막에 뒤집으세요.

무료로 시작

AI 튜터와 함께 Scala을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
39
레슨
143

자주 묻는 질문

“누산기 패턴” 강의는 무료인가요?

네 — “누산기 패턴” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Scala for Backend Engineering & Functional Programming 강의 전체를 잠금 해제할 수 있습니다. Scala for Backend Engineering & Functional Programming 강의에는 총 4개의 강의가 포함되어 있습니다.

“누산기 패턴”에서 뭘 배우나요?

재귀를 통해 상태를 전달해 보세요. 브라우저에서 직접 실행하는 실습 코드로 Scala for Backend Engineering & Functional Programming을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Scala for Backend Engineering & Functional Programming을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Scala for Backend Engineering & Functional Programming은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“누산기 패턴” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Scala for Backend Engineering & Functional Programming 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Scala for Backend Engineering & Functional Programming 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 재귀적으로 사고하기
  2. 누산기 패턴
  3. foldLeft와 foldRight
  4. reduce와 집계
← Scala for Backend Engineering & Functional Programming(으)로 돌아가기