如何定义一个不定参数(可能超过9个)的宏:先读入所有参数,并分析,再根据分析结果采取不同策略处理各个参数


问题比较抽象,先看一个简单例子:


需要定义一个宏 \qq,其参数数量不定,可以是 1-无穷个(超过9),期望效果:

\qq{P1}{P2} -> (P1/2)(P2/2)
\qq{P1}{P2}……{Pn} -> (P1/n)(P2/n)……(Pn/n)


实现:

% 用户接口:初始化\P,并调底层 \storeP 实现:加工并存储各个参数到 \P 中,直到最后一个参数,再输出 \P
\def\qq{\def\P{\relax}\count6120=0\relax\storeP} 
\def\qq{\let\P=\relax \count6120=0\relax\storeP}

% \storeP 的实现代码(失败的教训):
尝试方案 1  (失败)
\def\storeP#1{\advance\count6120 by 1\let\oldP=\P \def\PP{\oldP  (#1/\the\count6120)}\let\P=\PP\@ifnextchar\bgroup{\storeP}{\P}}

尝试方案  2  (失败)
\def\storeP#1{\advance\count6120 by 1\def\newP{\P  (#1/\the\count6120)}\let\P=\PP\@ifnextchar\bgroup{\storeP}{\P}}

瞎尝试方案  x-1  (失败)
\def\storeP#1{\advance\count6120 by 1\let\oldP=\P
\edef\P{\noexpand\oldP (#1/\the\count6120)} \@ifnextchar\bgroup{\storeP}{\P}}

瞎尝试失败方案  x
\def\storeP#1{\advance\count6120 by 1\let\oldP=\P
\def\P{\noexpand\oldP (#1/\the\count6120)} \@ifnextchar\bgroup{\storeP}{\P}}

盲目骚操作  x + 1
% \edef  \xdef \expanderafter ……
初步成功!
\def\storeP#1{\advance\count6120 by 1\apptocmd{\P}{(#1/\the\count6120)}{\relax}{\relax}\@ifnextchar\bgroup{\storeP}{\P}}

\qq{P1}{P2}{P3}{P4}      【期望的结果为(P1/4)(P2/4)(P3/4)(P4/4)】

这个例子看起来没什么实用性,

那好,大家再看下这个抽象问题背后的实际场景:选择题的自动排版

场景分析:

选项数量不确定,可能有多个(甚至超过9个,你懂的),

需要先读入所有选项,进行分析:找出最长选项,并结合选项数量决定如何排版(一行排几个)


选项数可选排版方案:每行可能排多少选项
11


212

31
3
412
4
5123
6123
712
4
81234
9123
1012
4
111234
121234
……




麻烦之处:

① 参数不定,还可能是无穷

② 需要先分析并做决策,再倒回去排版。


遇到一个参数分析一个,并将参数存储到一个内部宏中



目前选项可以自动支持 2-9 个参数,但被一个 Python 工程师吐嘈说支持不了 10 个参数,暴力写做不到无穷多个,还搞得模板还很臭


参考:

如果只是想遍历所有的参数,不需要先来一遍分析,再下决策然后倒回去排版,还是挺好实现的。例如:分分钟可以写出求和宏:

\def\addnext#1{\advance\count6120 by #1\@ifnextchar\bgroup{\addnext}{\the\count6120}}
\def\sumup{\count6120=0\addnext}

效果
\sumup{2}{3}{5}{5}{2}{3}{5}{5}{2}{3}{5}{5}{2}{3}{5}{5}{2}{3}{5}{5}{2}{3}{5}{5}  ……


3 回答3

2
<p></p><p>根据以下python的逻辑</p><pre class="brush:python;toolbar:false">S&nbsp;=&nbsp;0 def&nbsp;sum(n=None): &nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;n&nbsp;is&nbsp;None: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0 &nbsp;&nbsp;&nbsp;&nbsp;global&nbsp;S &nbsp;&nbsp;&nbsp;&nbsp;S&nbsp;=&nbsp;n &nbsp;&nbsp;&nbsp;&nbsp;def&nbsp;inner(m=None): &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;global&nbsp;S &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;m&nbsp;is&nbsp;None: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;S &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;S&nbsp;+=&nbsp;m &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;inner &nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;inner print(sum()) print(sum(1)()) print(sum(1)(2)()) print(sum(1)(2)(3)()) print(sum(1)(2)(3)(4)())</pre><p>用LaTeX3可写出相似的逻辑<br/></p><pre class="brush:plain;toolbar:false">\documentclass{ctexart} \ExplSyntaxOn \int_new:N&nbsp;\g_S_int \NewDocumentCommand&nbsp;{\sumup}&nbsp;{O{}}&nbsp;{ &nbsp;\tl_if_blank:nTF&nbsp;{#1}&nbsp;{ &nbsp;&nbsp;0 &nbsp;}{ &nbsp;&nbsp;\int_gset:Nn&nbsp;\g_S_int&nbsp;{&nbsp;#1&nbsp;} &nbsp;&nbsp;\inner &nbsp;} } \NewDocumentCommand&nbsp;{\inner}&nbsp;{O{}}&nbsp;{ &nbsp;\tl_if_blank:nTF&nbsp;{#1}&nbsp;{ &nbsp;&nbsp;\int_to_arabic:n&nbsp;{&nbsp;\g_S_int&nbsp;} &nbsp;}{ &nbsp;&nbsp;\int_gadd:Nn&nbsp;\g_S_int&nbsp;{&nbsp;#1&nbsp;} &nbsp;&nbsp;\inner &nbsp;} } \ExplSyntaxOff \begin{document} \sumup \sumup[1] \sumup[1][2] \sumup[1][2][3] \sumup[1][2][3][4] \end{document}</pre><p>效果如下图</p><p><img src="/data/ueditor/php/upload/image/20200509/1589017017494758.png" title="1589017017494758.png" alt="93f1d2a130575dd91028279647a9980.png"/></p><p>我并不想完全做出主问题,用你最后一个<span style="box-sizing: content-box; font-weight: 700; margin: 0px; padding: 0px; color: rgb(47, 47, 47); font-family: ">求和</span><span style="color: rgb(47, 47, 47); font-family: ">宏来举例,只是为了让你对LaTeX3的用法和能实现的东西有个初步了解。</span></p>
  • 谢谢前辈指点,<br> LaTeX3 编程接口确实是个很好的方向 – 余@光♡中 2020-05-11 16:42 回复
0
<p>终于试出简化问题的可行解:<br></p><p><br></p><pre class="brush:plain;toolbar:false">\def\storeP#1{\advance\count6120 by 1\apptocmd{\P}{(#1/\the\count6120)}{\relax}{\relax}\@ifnextchar\bgroup{\storeP}{\P}}</pre><p><br></p>
0
<pre class="brush:plain;toolbar:false">%\usepackage{tikz} %\usepackage{pgfplotstable}</pre><p>下面的代码需要上面两个宏包。</p><pre class="brush:cpp;toolbar:false">\makeatletter \newbox\mytestboxA% \newbox\mytestboxB% \newdimen\mytestdimen% \newdimen\mytestlinedimen% \newdimen\mytestlinewidth% \mytestlinewidth=3cm% \mytestlinedimen=\mytestlinewidth% \newcount\mytestcount% \def\testmyitembox#1{%     \setbox\mytestboxA=\hbox\bgroup#1\egroup%     \setbox\mytestboxB=\hbox\bgroup\box\mytestboxA\egroup%     \mytestdimen=\wd\mytestboxB% }% % \def\saveoneitem#1{%     \testmyitembox{#1}%     \expandafter\def\csname item@contents@\the\mytestcount\endcsname{#1}%     \expandafter\edef\csname item@width@\the\mytestcount\endcsname{\the\mytestdimen}%     \advance\mytestcount by 1\relax%     \myitemprocess% }% % \def\myitemprocess{%     \@ifnextchar\bgroup{\saveoneitem}{\whenmyitemstop}% }% % \def\whenmyitemstop{%         \advance\mytestcount by -1\relax%         \edef\itemtotalnumber{\the\mytestcount}%         \def\haveoneitem{0}%         \pgfplotsforeachungrouped\fortempvar in {0,...,\itemtotalnumber}%         {%             \mytestdimen=\csname item@width@\fortempvar\endcsname%             \ifnum\haveoneitem=0\relax%                 \csname item@contents@\fortempvar\endcsname%                 \advance\mytestlinedimen by -\mytestdimen%                 \def\haveoneitem{1}%             \else%                 \ifdim\mytestdimen<\mytestlinedimen%                     \csname item@contents@\fortempvar\endcsname%                     \advance\mytestlinedimen by -\mytestdimen%                 \else%                     \hfill\mbox{\tikz{\draw[->](0,0)--(0.2,0)arc[start angle=90,end angle=-90,radius=0.6mm]--++(-0.4,0);}}\break%                     \def\haveoneitem{0}%                     \mytestlinedimen=\mytestlinewidth%                     \csname item@contents@\fortempvar\endcsname%                     \advance\mytestlinedimen by -\mytestdimen%                     \def\haveoneitem{1}%                 \fi%             \fi%         }% }% \begingroup%用一个组限制处理过程 \noindent% \myitemprocess{aaa\rule{1cm}{1mm}}{bbb\rule{1cm}{1mm}}{ccc\rule{2cm}{1mm}}{\tikz{\draw(0,0)circle[radius=2cm];}}\relax% \\ \csname item@width@0\endcsname\\ \csname item@width@1\endcsname\\ \csname item@width@2\endcsname\\ \csname item@width@3\endcsname \endgroup \makeatother</pre><p><img src="/data/ueditor/php/upload/image/20200510/1589040075448038.png" title="1589040075448038.png" alt="2020-05-10 00-00-09屏幕截图.png" style="max-width:650px"></p><p><br></p><p>命令  \myitemprocess 依次处理花括号里的内容,内容保存在 \csname item@contents@\the\mytestcount\endcsname 中,宽度保存在 \csname item@width@\the\mytestcount\endcsname 中,如果需要利用保存的东西做某些事情,可以重定义命令 \whenmyitemstop</p>
  • 谢谢前辈指点, 感觉用 \csname item@contents@\the\mytestcount\endcsname 来存参挺巧妙的,可以用数字索引。 建议 \pgfplotsforeachung – 余@光♡中 2020-05-11 16:40 回复

你的回答

请登录后回答

你的回答将会帮助更多人,请务必认真回答问题。