알고리즘(algorithm2e/algpseudocode)

“latex 알고리즘 패키지”로 검색하면 거의 똑같은 이름이 네 개 돌아옵니다——algorithm, algorithmic, algorithmicx, algorithm2e. 게다가 지금 고치고 있는 프리앰블이 같은 문서에 공존할 수 없는 두 개를 함께 읽고 있을 가능성이 꽤 높습니다. 이 얽힘이야말로 이 페이지의 본론입니다. 한 번 풀고 나면 LaTeX으로 의사코드를 조판하는 일은 어렵지 않기 때문입니다. 기억할 점은 의사코드가 코드를 그대로 붙여 넣은 것이 아니라는 것입니다. 실제 소스를 싣는 자매 페이지와 달리, 알고리즘은 수식에 가까운 방식으로 조판됩니다. 변수는 이탤릭, 대입은 화살표, 키워드는 굵은 글씨이며, 그것이 \ref로 참조할 수 있는 번호 붙은 플로트 안에 들어갑니다. 아래에서는 결국 어떤 조합을 읽어야 하는지, 잘못 읽었을 때 실제로 나오는 오류 문구, 그리고 두 계열의 완전한 작성법을 차례로 봅니다.

의사코드와 listings의 차이: 붙여 넣기인가, 조판인가

한 문장으로 말하면 listings는 문자를 보존하고, 의사코드는 의미를 조판합니다. listingsminted에 넘긴 내용은 축자 환경으로 처리되어, 입력한 공백과 기호가 그대로 지면에 나옵니다. 실제 소스 코드에 필요한 성질이며, 그 선택은 자매 페이지의 몫입니다. 의사코드는 정반대입니다. $y \gets 1$이라고 쓰면 y는 이탤릭 변수로, \gets는 화살표 ←로 조판됩니다. 즉 본문이 축자 환경이 아니라 수식 모드에 있습니다. 키워드만 굵은 로만체로 일어서서 변수와 시각적으로 구분됩니다. “변수는 이탤릭, 키워드는 굵게”라는 이 약속은 커누스의 『The Art of Computer Programming』 이래 교과서의 작법을 그대로 지면에 옮긴 것입니다. 참고로 \gets라는 이름 자체가 전산학에서 왔으며, x \gets 1을 “x gets 1”, 곧 대입으로 읽는 데서 유래했습니다.

두 번째 차이는 놓이는 자리입니다. lstlisting은 본문 흐름 안에 그대로 머물지만, 의사코드는 관례상 figuretable과 똑같이 플로트 안에 들어갑니다. 그러면 LaTeX가 어색한 페이지 경계를 피해 적당한 자리로 옮기고, 위쪽에 “Algorithm 1”이라는 번호 붙은 제목을 달아 주며, \listofalgorithms 목록에도 올려 줍니다. 입구(플로트)와 내용(의사코드)이 서로 다른 패키지에서 온다는 구조적 사실 하나만 삼키면, 헷갈리던 네 이름이 단번에 정리됩니다.

결국 무엇을 \usepackage 해야 하는가

새로 쓰기 시작한다면 답은 두 줄입니다. \usepackage{algorithm}\usepackage{algpseudocode}를 나란히 두거나, \usepackage{algorithm2e}를 단독으로 읽거나입니다. 이 둘 말고 다른 조합은 대개 사고로 이어집니다. 헷갈리는 이유는 CTAN의 패키지 이름과 스타일 파일 이름이 어긋나 있기 때문입니다. algorithm.styalgorithm이라는 이름의 패키지가 아니라 algorithms 번들(복수형)에서 옵니다. 그리고 algpseudocode.styalgorithmicx에서 옵니다. 즉 프리앰블에는 파일 이름 두 개를 적지만, 실제로 설치되는 것은 이름이 전혀 다른 번들 두 개입니다.

번들들어 있는 스타일 파일역할
algorithmsalgorithm.sty, algorithmic.sty입구(algorithm 플로트)와, 1994년까지 거슬러 올라가는 옛 내용 algorithmic. 후자는 대문자 명령(\STATE)을 쓰며 지금은 대체되었습니다
algorithmicxalgorithmicx.sty, algpseudocode.sty, algcompatible.sty내용만 제공합니다. Szász János가 2005년에 다시 쓴 것으로, algpseudocode가 표준 레이아웃입니다. 입구는 전혀 들어 있지 않습니다
algorithm2ealgorithm2e.sty입구와 내용을 한꺼번에 제공하는 독립 계열. 1996년부터 Christophe Fiorio가 관리합니다. algorithm과 함께 읽지 마세요
preamble
% Camp 1 -- two packages, two jobs. This is the usual choice.
\usepackage{algorithm}      % the float: \begin{algorithm}, \caption, \listofalgorithms
\usepackage{algpseudocode}  % the body:  \begin{algorithmic}, \State, \If, \For

% Camp 2 -- one package does everything. Do NOT also load algorithm.
\usepackage[ruled,vlined,linesnumbered]{algorithm2e}

이 분업이 얼마나 철저한지는 algorithm.sty를 열어 보면 바로 드러납니다. 전체가 100줄이 채 안 되고, 의사코드를 언급하는 줄은 하나도 없습니다. 하는 일은 float 패키지를 읽고, \newfloat{algorithm}{htbp}{loa}로 새 플로트 형식을 하나 선언하고, 그 이름을 “Algorithm”으로 정하고, \listofalgorithms를 만드는 것뿐입니다(TeX Live 2024에 실린 판은 2009년 8월 24일자 v0.1). 재미있는 부작용도 있습니다. 알지 못하는 옵션은 모두 플로트의 이름으로 해석합니다. \usepackage[Procedure]{algorithm}이라고 쓰면 이후 모든 캡션이 “Procedure 1”, “Procedure 2”가 됩니다.

Command \algorithm already defined.가 나왔을 때

이 오류의 뜻은 하나뿐입니다. algorithmalgorithm2e를 함께 읽고 있습니다. 둘 다 algorithm이라는 이름의 환경을 만들려 하므로, 나중에 온 쪽이 먼저 온 쪽과 부딪칩니다. 프리앰블에서 하나를 지우면 해결됩니다. 같은 이유로 읽는 순서를 바꾸면 문구도 달라져서, algorithm2e를 먼저 쓰고 algorithm을 나중에 쓰면 ! LaTeX Error: Command \listofalgorithms already defined.가 나옵니다. 원인은 같습니다. 또 하나 흔한 것이 ! LaTeX Error: Command \algorithmic already defined.인데, 옛 algorithmic과 새 algpseudocode를 동시에 읽었을 때 나오는 증상입니다. algorithmicx 계열이 algorithmic의 후계이므로 둘 다 필요하지 않습니다.

preamble
% Each of these three preambles stops the compile.
\usepackage{algorithm}\usepackage{algorithm2e}
%   ! LaTeX Error: Command \algorithm already defined.

\usepackage{algorithm2e}\usepackage{algorithm}
%   ! LaTeX Error: Command \listofalgorithms already defined.

\usepackage{algorithmic}\usepackage{algpseudocode}
%   ! LaTeX Error: Command \algorithmic already defined.

% If a class or a co-author really forces algorithm2e into a document that
% already owns the algorithm float, this option renames the environment to
% algorithm2e and \listofalgorithms to \listofalgorithmes, so nothing collides.
\usepackage[algo2e]{algorithm2e}

그런데 가장 성가신 조합은 오류를 내지 않습니다. algorithm2ealgpseudocode를 나란히 읽어도 문서는 아무 말 없이 컴파일됩니다. algorithmicx\For\If 같은 사용자용 이름을 정의할 때 그 이름이 이미 있는지 먼저 확인하고, 있으면 말없이 정의를 포기하기 때문입니다. 그래서 algorithm2e의 뜻이 살아남고, algorithmic 환경의 본문을 쓰는 순간에야 비로소 \EndWhile 행에서 ! Missing number, treated as zero. 같은 전혀 무관해 보이는 오류가 튀어나옵니다. 원인에서 가장 먼 곳에서 무너지는 셈이라, 깔끔한 오류보다 오히려 다루기 어렵습니다. algorithm2ealgo2e 옵션을 갖춘 것은 바로 이런 충돌을 피하기 위해서이며, 이 옵션을 쓰면 \listofalgorithms는 철자가 다른 \listofalgorithmes로 바뀝니다. 이 패키지가 프랑스어권에서 만들어져 온 흔적입니다.

\begin{algorithm}은 플로트입니다: \caption, \label, [H]

algorithm 환경은 figuretable과 똑같은 의미에서 진짜 플로트입니다. 기본 배치 지정은 htbp(여기, 페이지 위, 아래, 독립 페이지 순으로 시도)이고, 목록 파일 확장자는 .loa입니다. 따라서 그림과 표에서 익힌 작법이 그대로 통합니다. \caption{…}이 “Algorithm 1”이라는 번호 붙은 제목을 만들고, 그 바로 뒤에 \label{alg:…}를 두면 본문에서 \ref{alg:power}로 번호를, \pageref{alg:power}로 페이지 번호를 가져올 수 있습니다. \label\caption보다 앞에 쓰면 번호가 하나 어긋나거나 절 번호를 물어 오므로 순서를 지켜야 합니다. 문서 앞부분에 \listofalgorithms라고 쓰면——\listoffigures, \listoftables와 형제뻘입니다——“List of Algorithms”가 생겨 번호와 \caption 문구가 나란히 놓입니다. 늘 그렇듯 번호와 참조와 목록이 확정되려면 두 번 컴파일해야 합니다. 반대로 떠다니게 하고 싶지 않다면 \begin{algorithm}[H]라고 쓰면 그 자리에 고정됩니다(algorithmfloat 패키지를 읽으므로 [H]는 별도 설정 없이 쓸 수 있습니다).

본문 쓰기: \State, \If, \For, \While, \Function

안쪽 환경의 이름은 algorithmic입니다. 이름 짓기의 마지막 함정으로, 패키지는 algpseudocode인데 환경 이름은 다릅니다. 명령은 모두 첫 글자만 대문자이며, \State가 한 행짜리 문장을 열고, 블록은 \If\EndIf, \For\EndFor처럼 명시적으로 닫습니다. 시작 부분의 선택 인수는 행 번호 간격을 정합니다. [1]은 모든 행에, [5]는 다섯 행마다 번호를 붙이고, 생략하면 번호가 없습니다. 다음 예는 이진 탐색으로, 조건 분기와 반복과 함수와 전제 조건과 행 끝 주석을 두루 담고 있습니다.

document.tex
\documentclass{article}
\usepackage{algorithm}
\usepackage{algpseudocode}
\begin{document}
\listofalgorithms

\begin{algorithm}
  \caption{Binary search}\label{alg:bsearch}
  \begin{algorithmic}[1]
    \Require sorted array $a[1..n]$, key $k$
    \Ensure index of $k$, or $0$
    \Function{Search}{$a,k$}
      \State $lo \gets 1$;\ $hi \gets n$ \label{ln:init}
      \While{$lo \le hi$}
        \State $m \gets \lfloor (lo+hi)/2 \rfloor$ \Comment{midpoint}
        \If{$a[m] = k$}
          \State \Return $m$
        \ElsIf{$a[m] < k$}
          \State $lo \gets m+1$
        \Else
          \State $hi \gets m-1$
        \EndIf
      \EndWhile
      \State \Return $0$
    \EndFunction
  \end{algorithmic}
\end{algorithm}

Algorithm~\ref{alg:bsearch} halves the interval; the bounds are set on
line~\ref{ln:init}, page~\pageref{alg:bsearch}.

\end{document}

출력은 이렇습니다. 위아래에 가로줄이 있는 상자가 페이지 위로 떠오르고, 맨 위에 “Algorithm 1 Binary search”가 놓입니다. 상자 안에서 \Require\Ensure는 굵은 Require: / Ensure: 라벨을 만들며, 이들은 번호 바깥에 놓입니다. 번호는 \Function 행에서 시작해 왼쪽 가장자리를 따라 1, 2, 3 … 이어지고, \Comment{midpoint}는 행 끝에 작은 삼각형을 붙여 ▷ midpoint로 조판됩니다. \Function{Search}{$a,k$}의 이름은 작은 대문자로 조판되며, 다른 행에서 호출할 때는 \Call{Search}{$a,k$}라고 씁니다. 라벨을 바꾸고 싶어 하는 사람이 많으니 덧붙이면, Require:Ensure: 라는 문자열은 \algorithmicrequire\algorithmicensure에 들어 있습니다. \renewcommand{\algorithmicrequire}{\textbf{Input:}}라고 쓰면 Input: 으로 바뀝니다.

명령조판 결과메모
\State번호가 붙은 한 행문장마다 하나씩. 빠뜨리면 앞 행에 이어 붙습니다
\Require / \EnsureRequire: / Ensure:전제 조건과 사후 조건. 행 번호 바깥에 놓입니다
\If ... \ElsIf ... \Else ... \EndIfifthen / else if / else / end if명령의 철자는 \ElsIf로 “else”보다 e가 하나 적습니다. 자주 틀리는 부분입니다
\For ... \EndForfordoend for\ForAll{…}for all …이 됩니다. 조건은 중괄호로 넘깁니다
\While ... \EndWhilewhiledoend while같은 모양의 \Repeat\Until{…}도 마련되어 있습니다
\Function ... \EndFunctionfunction Name(args) … end function이름은 작은 대문자로 조판되며, 호출은 \Call{Name}{args}. 짝이 되는 것은 \Procedure
\Returnreturn\State \Return $y$처럼 \State와 함께 씁니다
\Comment▷ 주석 문구행 끝에 둡니다. 삼각형은 \algorithmiccomment에 들어 있습니다

이 계열에서 가장 흔한 사고는 \State를 빠뜨리는 것이며, 오류는 나지 않습니다. \State $x \gets 1$ 다음에 \State 없이 $y \gets 2$라고 쓰면 출력은 “1: x ← 1 y ← 2”가 되어 두 문장이 같은 행에 밀려 들어가고 번호도 하나 줄어듭니다. 행 수가 맞지 않으면 여기부터 의심하세요. 그리고 오래된 학회 템플릿에서 \STATE, \WHILE, \ENDWHILE 같은 대문자 명령을 보았다면, 그것은 1994년에서 유래한 옛 algorithmic 방식입니다. 그런 원고를 그대로 쓰고 싶다면 algpseudocode 대신 \usepackage{algcompatible}을 읽으면 됩니다. 대문자 명령이 algorithmicx라는 토대 위에서 그대로 동작합니다.

행 번호를 붙이고 “3행”을 참조하기

행 번호는 \ref로 참조할 수 있습니다. algorithmic 환경을 [1]로 열고, 참조하려는 행의 \label{ln:init}을 두면 본문의 \ref{ln:init}이 그 행의 번호로 펼쳐집니다. 위 예에서는 \Function 행이 1이므로 \ref{ln:init}은 2가 됩니다. 이는 생각보다 큰 효과가 있습니다. “3행의 조건”이라고 손으로 적어 두면, 나중에 한 줄만 추가해도 그 문장이 거짓이 되기 때문입니다. algorithm2e에서도 방식은 같아서, linesnumbered를 켜고 행을 끝내는 \; 뒤에 \label{…}을 두면 \ref가 통합니다. 한 가지 주의할 점은 번호가 없는 행에 라벨을 달아도 의미가 없다는 것입니다. [1]linesnumbered도 지정하지 않았다면 가리킬 번호 자체가 없습니다.

algorithm2e의 작성법: \End… 대신 중괄호, 그리고 잊으면 안 되는 \;

다른 계열은 블록의 끝을 \EndFor 같은 명령으로 표시하지 않습니다. 본문을 중괄호 인수로 넘깁니다. \For{조건}{본문}, \While{조건}{본문}이며, if–then–else는 \eIf{cond}{then}{else}입니다(else가 없으면 \uIf, 한 행에 담으려면 \lIf). 입출력에는 전용 명령 \KwIn{…}\KwOut{…}이 있고, 굵은 Input: / Output: 으로 조판됩니다(Data: / Result: 라는 말이 낫다면 \KwData{…}\KwResult{…}). 가장 큰 함정은 모든 문장을 \;로 닫아야 한다는 점입니다. 잊어도 오류는 나지 않고, 다음 문장이 그대로 같은 행에 이어집니다. 인쇄물에 “;”를 보이고 싶지 않다면 \DontPrintSemicolon으로 숨길 수 있습니다(\; 자체는 여전히 필요합니다). 겉모습은 모두 로드 옵션으로 정해집니다. ruled(위아래 가로줄), boxed(전체를 상자로), vlinedlined(블록에 세로줄을 긋는 두 방식), plain(기본), 그리고 행 번호는 linesnumbered입니다. 이 세 가지 선 옵션은 문서 안에서 \SetAlgoVlined, \SetAlgoLined, \SetAlgoNoLine이라고 쓰는 것과 완전히 같습니다. TeX Live 2024에는 2017년 7월 18일자 v5.2가 실려 있습니다.

document.tex
\documentclass{article}
\usepackage[ruled,vlined,linesnumbered]{algorithm2e}
\begin{document}
\listofalgorithms

\begin{algorithm}
  \DontPrintSemicolon
  \KwIn{an array $a[1..n]$}
  \KwOut{the sum $s$ of its positive entries}
  $s \gets 0$\;\label{ln:zero}
  \For{$i \gets 1$ \KwTo $n$}{
    \eIf{$a[i] > 0$}{
      $s \gets s + a[i]$\;
    }{
      \tcp{negative or zero: skip}
    }
  }
  \Return $s$\;
  \caption{Sum of positive entries}\label{alg:sum}
\end{algorithm}

Algorithm~\ref{alg:sum} accumulates into $s$, initialised on line~\ref{ln:zero}.

\end{document}

출력의 제목은 “Algorithm 1: Sum of positive entries”입니다. 번호 뒤에 콜론이 붙는 것이 algorithm 플로트와 눈에 띄는 차이입니다. 본문 위에는 Input:Output: 이 놓이고, 본문은 for i ← 1 to n do, ifthenelse 로 조판되며, vlined를 지정했으므로 블록 왼쪽에 세로줄이 그어집니다. \tcp{…}는 행 안의 주석으로 // negative or zero: skip처럼 나옵니다. \caption을 환경의 에 쓴 점에 주목하세요. algorithm2e에서는 이것이 관례이며, ruled를 지정했다면 끝에 써도 제목은 상자 위쪽에 나옵니다. 같은 문구가 \listofalgorithms에도 실립니다. 참고로 algorithm2ealgorithm 환경도 기본적으로 플로트이므로, 그 자리에 두고 싶을 때는 \begin{algorithm}[H]를 쓸 수 있습니다.

키워드를 영어 이외의 언어로: french, germanonelanguage

algorithm2e는 의사코드의 키워드 자체를 번역할 수 있습니다. 이 기능은 이쪽 계열에만 있고 algpseudocode에는 대응물이 없습니다. 옵션으로 french, german, ngerman, spanish, italiano, portuguese, czech, slovak, croatian, english가 마련되어 있습니다. 다만 동작에 단차가 있습니다. \usepackage[french]{algorithm2e}만 쓰면 플로트의 이름은 “Algorithme 1 :”으로 바뀌지만 \For\eIf는 그대로 영어로 나옵니다. 프랑스어로 조판하려면 프랑스어 명령 이름(\Pour{…}{…}, \Si{…}{…}, \KwA)을 써야 합니다. 기존 원고의 명령 이름을 고치고 싶지 않다면 onelanguage를 더하세요. \usepackage[french,onelanguage]{algorithm2e}라고 쓰면 똑같은 \For\eIfpour i ← 1 à n faire, sialorssinonfin으로 출력됩니다. 독일어는 [german,onelanguage]für i ← 1 bis n tue, wenndannsonstEnde가 되고 제목은 “Algorithmus 1:”이 됩니다.

preamble
% Keep the English command names, print French keywords.
\usepackage[french,onelanguage,ruled,linesnumbered]{algorithm2e}
%   \For{...}{...}  ->  pour i <- 1 a n faire ... fin

% Without onelanguage, only the float name is translated; use the
% French command names for a French body.
\usepackage[french,ruled]{algorithm2e}
%   \Pour{...}{...}, \Si{...}{...}, \KwA

결국 무엇을 고를까: algpseudocodealgorithm2e 비교

투고할 곳의 템플릿이 하나를 지정하고 있다면 그에 따르는 것이 정답입니다. 많은 학회 클래스가 특정 선택을 전제로 여백과 서체를 맞춰 두었기 때문입니다. 지정이 없다면 의사코드를 수학적인 글로 읽히게 하고 싶으면 algpseudocode, 입출력 선언과 블록 세로줄, 키워드 번역이 필요하면 algorithm2e 입니다. 나머지는 아래 표로 정리합니다.

항목algorithm + algpseudocodealgorithm2e
packages둘: 입구와 내용하나로 완결
block syntax명시적으로 닫음: \If{…}\EndIf본문을 중괄호로: \eIf{…}{…}{…}
input / output\Require / \Ensure — 전제 조건과 사후 조건\KwIn / \KwOut — 명시적 입력과 출력
end of line필요 없음. \State가 행을 엽니다\; 필수. 빠뜨리면 다음 문장이 같은 행에 이어집니다
line numbers\begin{algorithmic}[1]의 선택 인수로 간격 지정linesnumbered 옵션(rightnl로 오른쪽 배치)
keyword language영어만 제공. \algorithmicwhile 등을 하나씩 재정의해야 합니다french, german 같은 옵션. onelanguage를 쓰면 명령 이름은 영어 그대로

어느 쪽을 고르든 게재물로서의 처리는 똑같습니다. \caption으로 번호 붙은 제목을 주고, \label\ref로 본문에서 가리키고, \listofalgorithms로 목록을 만들고, 번호를 확정하기 위해 두 번 컴파일합니다. 그리고 이 페이지의 요점이므로 다시 한 번 말합니다. 문서마다 내용 패키지는 정확히 하나만 쓰세요. algorithm2e를 쓴다면 algorithm은 읽지 않습니다. algpseudocode를 쓴다면 algorithmic은 읽지 않습니다. 이 한 줄의 규율이 서두에서 본 네 개의 헷갈리는 이름을 둘러싼 사고 대부분을 미리 막아 줍니다. 의사코드가 아니라 구문 강조와 행 번호가 있는 실제 소스 코드를 싣고 싶다면, listingsminted를 다루는 자매 페이지가 목적지입니다.