2018年12月14日

Ackermann関数を計算過程を込めて出力するやつのTeX版を展開できるようにしたやつ.$\mathrm{Ack}$をAckermann関数とする.

  • \Ack{n}{m}を展開すると$\mathrm{Ack}(n,m)$の計算過程が出てくる.各々の段階は\AckOutputでくるまれる.例えば$\mathrm{Ack}(1,0) = \mathrm{Ack}(0,1) = 2$に対応して,\Ack{1}{0}を展開すると\AckOutput{A(1,0)}\AckOutput{A(0,1)}\AckOutput{2}となる.
  • e-TeX拡張は使う.といっても使うのは\numexprのみである.+1と-1はこれを使わないと面倒…….
\documentclass[a4paper]{article}
\makeatletter
% メイン.内部表現に変換し,\ac@nextでループに移行.
\def\Ack#1#2{\ac@next{\ac@func{\ac@num{#1}}{\ac@num{#2}}}}
% 自然数nは\ac@num{n}で表す
\def\ac@num{\ac@num}
% Ack(X,Y)は\ac@func{X}{Y}で表す
\def\ac@func{\ac@func}
% \ac@num{n} -> n
\def\ac@numtonum#1{\expandafter\@firstofone\@gobble#1}
% \ac@ifnumeq{\ac@num{n}}{m}{true code}{false code}
% n = mならばtrue code,そうでなければfalse codeに展開される.
\def\ac@ifnumeq#1#2{%
  \ifnum\ac@numtonum{#1}=#2 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
% uniq token
\def\ac@endmark{\ac@endmark}
\def\ac@eattoendmark#1\ac@endmark{}
% \ac@ifnum{#1}{true code}{false code}
% #1 = \ac@num{n}ならばtrue code,そうでなければfalse codeに展開される.
\def\ac@ifnum#1{%
  \expandafter\ac@eattoendmark\ifx\ac@num#1\ac@endmark\expandafter\@firstoftwo\else\ac@endmark\expandafter\@secondoftwo\fi
}
% ループを進める.
\def\ac@next#1{%
  \ac@output{#1}% 出力
  % 自然数なら終わり,そうでなければ#1の計算を一段進める.#1=\ac@func{X}{Y}の形
  \ac@ifnum{#1}{}{\ac@eval@once#1\ac@endmark}%
}
% 関数の計算を一段進める.#1=\ac@func, #2, #3は引数
% Ackermann関数の定義から#2は常に自然数である.
\def\ac@eval@once#1#2#3{%
  \ac@ifnum{#3}{%
    \ac@ifnumeq{#2}{0}{% A(0,#3) = #3 + 1
      \expandafter\ac@eval@end\expandafter{\expandafter\ac@num\expandafter{\the\numexpr\ac@numtonum{#3} + 1\relax}}%
    }{%
      \ac@ifnumeq{#3}{0}{% A(#2,0) = A(#2 - 1,1)
        \expandafter\ac@eval@end\expandafter{\expandafter\ac@func\expandafter{\expandafter\ac@num\expandafter{\the\numexpr\ac@numtonum{#2} - 1\relax}}{\ac@num{1}}}%
      }{%
        \ac@eval@othercase{#2}{#3}% その他
      }%
    }%
  }{%
    % 引数の順番を入れ替える.最終的に
    % A(X,A(Y,A(....A(Z,A(V,W))..)
    % が
    % \ac@evel@end{A(V,W)の計算結果}{Z}...{Y}{X}
    % に置き換わる.
    \ac@eval@once#3{#2}%
  }%
}
% {\ac@num{m}}{\ac@num{n}} -> @i{m - 1}{\ac@num{m}}{\ac@num{n}}
\def\ac@eval@othercase#1#2{%
  \expandafter\ac@eval@othercase@i\expandafter{\the\numexpr\ac@numtonum{#1} - 1\relax}{#1}{#2}%
}
% {m - 1}{\ac@num{m}}{\ac@num{n}} -> @ii{n - 1}}{m - 1}{\ac@num{m}}
\def\ac@eval@othercase@i#1#2#3{%
  \expandafter\ac@eval@othercase@ii\expandafter{\the\numexpr\ac@numtonum{#3} - 1\relax}{#1}{#2}%
}
% {n - 1}{m - 1}{\ac@num{m}}
\def\ac@eval@othercase@ii#1#2#3{%
  \ac@eval@end{\ac@func{\ac@num{#2}}{\ac@func{#3}{\ac@num{#1}}}}%
}
% {A(V,W)の計算結果}{Z}...{Y}{X}\ac@endmarkをA(X,A(Y,A(....A(Z,A(V,W)の計算結果)..)に戻し,\ac@nextに回す
\def\ac@eval@end#1#2{%
  \ifx#2\ac@endmark\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi{%
    \ac@next{#1}%
  }{%
    \ac@eval@end{\ac@func{#2}{#1}}%
  }%
}
% 出力ルーチン.\ac@func{#1}{#2}をA(#1,#2)に,\ac@num{#1}を#1にして,最後に\AckOutputでくるむ
\def\ac@output#1{%
  \expandafter\AckOutput\expandafter{\romannumeral-`0\ac@output@i{#1}}%
}
% #1内をすべて変換する.
\def\ac@output@i#1{%
  % 自然数ならばそのまま出力
  \ac@ifnum{#1}{\the\numexpr\ac@numtonum{#1}\relax}{\ac@output@ii#1}%
}
% 第二引数を\ac@output@iで変換
\def\ac@output@ii#1#2#3{%
  \expandafter\ac@output@iii\expandafter{\romannumeral-`0\ac@output@i{#3}}{#2}%
}
% 第一引数を\ac@output@iで変換
\def\ac@output@iii#1#2{%
  \expandafter\ac@output@iv\expandafter{\romannumeral-`0\ac@output@i{#2}}{#1}%
}
\def\ac@output@iv#1#2{A(#1,#2)}
\makeatother

\begin{document}
\def\AckOutput{\noexpand\AckOutput}
% \AckOutput{A(1,1)}\AckOutput{A(0,A(1,0))}\AckOutput{A(0,A(0,1))}\AckOutput{A(0,2)}\AckOutput{3}
\message{^^J\Ack{1}{1}^^J}
% 出力のための\AckOutputの定義
\newbox\AckBox
\newif\ifAckFirst
\AckFirsttrue
\def\AckOutput#1{%
  \ifvoid\AckBox
    \setbox\AckBox=\hbox{$#1$}%
    \copy\AckBox
  \else
    \ifAckFirst
      \AckFirstfalse
      ${}=#1$%
    \else
      \par\noindent
      \hskip\wd\AckBox
      ${}=#1$%
    \fi
  \fi
}
\noindent
\Ack{3}{1}
\end{document}

0 件のコメント:

コメントを投稿

コメントの追加にはサードパーティーCookieの許可が必要です