LaTeX 中的递归列表类型定义

LaTeX 中的递归列表类型定义

我正在尝试排版一个类似 OCaml 的列表,并想mylist为其定义一个命令。预期结果如下所示:

\mylist{}          -->     []
\mylist{aa}        -->     [aa]
\mylist{aa,bb}     -->     [aa;bb]
\mylist{aa,bb,cc}  -->     [aa;bb;cc]

我希望它,始终是类型化的分隔符(在 LaTeX 中),但在 pdf 文档中显示为另一个符号(这里是;)(在定义中对显示的符号进行硬编码是可以接受的mylist)。

搜索了一段时间后我发现这个答案并做了一些改变。

\newcounter{listcount}\newcounter{totalcount}%
\newcommand{\mylist}[1]{%
  \setcounter{totalcount}{0}% Reset total count
  \renewcommand*{\do}[1]{\stepcounter{totalcount}}% Reconfigure count
  \docsvlist{#1}% Count number of items
  \setcounter{listcount}{0}% Reset current item count
  \renewcommand*{\do}[1]{% Reconfigure item \do
    \stepcounter{listcount}% Next item
    ##1;\ifnum\value{listcount}<\value{totalcount}\,\fi% Print item
  }
  \left[\docsvlist{#1}\right]% Process list
}

问题是,当列表包含 >1 个元素时,会出现尾随;

\mylist{aa,bb} --> [aa;bb;]

直观上,需要测试元素是否是最后一个。但是我似乎对宏定义listcount和逗号的数量感到困惑。

我还发现使用forcsvlist但问题是相似的。似乎一个办法通过使用其他包,但有点过度,所以我倾向于不使用它。

答案1

;必须移动角色才能将其放置在\ifnum\value....\fi测试内部。

我还添加了非常非常多expl3之前的较短版本\begin{document},尽管expl3语法对于 L3 新手来说可能非常奇怪......

我两年前(2015 年)一直使用\docsvlist和宏,但后来转向了功能更强大的语法(虽然不容易学习),尤其是对于中的大量列表,例如或或列表,在我看来,它们提供了更多功能。 \forcsvlistexpl3expl3seqclistprop

但首先要进行\ifnum...测试:

 \ifnum\value{listcount} < \value{totalcount}
 ;\,
 \fi

只是测试计数器的当前值是否listcount小于totalcount计数器的值并打印;\,,否则不打印。

expl3版本位于最后,使用起来更加简单,不需要测试(事实上,它们隐藏在后面\seq_use)。

首先使用expl3- 语法样式并将\ExplSyntaxOn参数的逗号分隔列表值存储到临时序列变量中\l_tmpa_seq。稍后使用\seq_use:Nn \l_tmpa_seq {;\,}- 显示此列表,该命令会处理列表的末尾。

为了防止空间占用,包装器命令\displaylist定义在外面,另请参阅 l3prop 中的空格有关带有 的空间的主题expl3


\documentclass{article}

\usepackage{etoolbox}

\newcounter{listcount}
\newcounter{totalcount}%
\newcommand{\mylist}[1]{%
  \setcounter{totalcount}{0}% Reset total count
  \renewcommand*{\do}[1]{\stepcounter{totalcount}}% Reconfigure count
  \docsvlist{#1}% Count number of items
  \setcounter{listcount}{0}% Reset current item count
  \renewcommand*{\do}[1]{% Reconfigure item \do
    \stepcounter{listcount}% Next item
    ##1\ifnum\value{listcount}<\value{totalcount};\,\fi% Print item
  }
  \left[\docsvlist{#1}\right]% Process list
}


\begin{document}
$\mylist{aa,bb,cc}$

$\mylist{aa}$


\end{document}

当然$\mylist{}$会屈服[]

在此处输入图片描述

\documentclass{article}
\usepackage{expl3}

\ExplSyntaxOn
\newcommand{\myotherlist}[1]{%
  \seq_set_from_clist:Nn \l_tmpa_seq  {#1}
  \displaylist{\seq_use:Nn \l_tmpa_seq {;\,}}
}
\ExplSyntaxOff
\newcommand{\displaylist}[1]{%
  \left[#1\right]%
}

\begin{document}

$\myotherlist{}$

$\myotherlist{aa,bb,cc}$

$\myotherlist{aa}$

\end{document}

答案2

以下是一个替代解决方案,它不需要迭代列表两次。在第一个元素之后,\do被替换为\mydo

\documentclass{article}

\usepackage{etoolbox}

\newcommand*{\mydo}[1]{;\,#1}

\newcommand{\mylist}[1]{%
  \renewcommand*{\do}[1]{##1\let\do\mydo}%
  \left[\docsvlist{#1}\right]% Process list
}

\begin{document}

$\mylist{aa,bb,cc}$

$\mylist{aa}$

\end{document}

答案3

完全可扩展的版本:

\documentclass{article}

\makeatletter
\newcommand{\ocamllist}[1]{\left[\ocaml@list#1,,\@nil}
\def\ocaml@list#1,#2,#3\@nil{%
  \if\relax\detokenize{#2}\relax
    \expandafter\@firstoftwo % no comma
  \else
    \expandafter\@secondoftwo
  \fi
  {\if\relax\detokenize{#1}\relax\,\else#1\fi\right]}
  {#1;\ocaml@list#2,#3,\@nil}
}
\makeatother

\begin{document}

$\ocamllist{}$

$\ocamllist{aa}$

$\ocamllist{aa,bb}$

$\ocamllist{aa,bb,cc}$

\end{document}

在此处输入图片描述

不可扩展,但更紧凑:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\ocamllist}{m}
 {
  \left[
  \clist_set:Nn \l_hongxu_ocamllist_clist {#1}
  \clist_if_empty:NTF \l_hongxu_ocamllist_clist
   { \, }
   { \clist_use:Nn \l_hongxu_ocamllist_clist {;} }
  \right]
 }
\clist_new:N \l_hongxu_ocamllist_clist
\ExplSyntaxOff

\begin{document}

$\ocamllist{}$

$\ocamllist{aa}$

$\ocamllist{aa,bb}$

$\ocamllist{aa,bb,cc}$

\end{document}

答案4

这是一个仅使用 TeX 原语的可扩展解决方案:

\def\ocamlist#1{\ocamlistA #1,,}
\def\ocamlistA#1,{[#1\ocamlistB}
\def\ocamlistB#1,{\ifx,#1,#1]\else;#1\expandafter\ocamlistB\fi}

$\ocamlist{aa,bb}$

\end

相关内容