将新命令定义为现有 Latex 命令的循环

将新命令定义为现有 Latex 命令的循环

我想定义一个新命令,它是 \csvsimple 包中已定义的命令的组合。因此我编写了以下代码,它正确地满足了我的目的:

\newcommand{\myfirstcommand}{\csvcoli & \csvcolii & \csvcoliii & \csvcoliv & \csvcolv} % the \csvcoli command refers to the 1st column of a csv table, \csvcolii to the 2nd column, and so on.
\newcommand{\mysecondcommand}{\myfirstcommand}

但是,我想将其写成\myfirstcommandfor 循环,而不必手动写下所有 5 列的 \csvcol 命令。因此,我尝试了以下方法:

\newcommand{\myfirstcommand}{
    \foreach \i in {1,...,5}{
    \csname csvcol\romannumeral\i\endcsname
        \ifnum\i < 5
                \& 
        \fi
    }
}

\newcommand{\mysecondcommand}{\myfirstcommand}

但是,后一个代码的结果实际上并不等同于前一个代码。我该如何解决这个问题?也许我必须使用\noexpand\expandafter..?

答案1

您可以在 TeX 原始级别使用简单的宏来完成此操作:

\def\addto#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
\newcount\num

\def\myfirstcommand{}\num=0
\loop
   \advance\num by1
   \ifnum\num>1 \addto\myfirstcommand{&}\fi
   \expandafter\addto\expandafter\myfirstcommand\csname csvcol\romannumeral\num\endcsname
   \ifnum\num<5 \repeat

%% Test:
\message{\meaning\myfirstcommand}

如果要设置为由给定“列”数限制的\mycommand列表等,则可以在表前和表内使用以下宏。\csvcoli & \csvcolii\setmycommand{number}\mycommand

\def\addto#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}}
\newcount\num

\def\setmycommand#1{%
   \def\mycommand{}\num=0
   \loop
      \advance\num by1
      \ifnum\num>1 \addto\myfirstcommand{&}\fi
      \expandafter\addto\expandafter\mycommand\csname csvcol\romannumeral\num\endcsname
   \ifnum\num<#1 \repeat
}

%% Test:
... before the table: \setmycommand{7}
... inside the table use \mycommand.

答案2

您可以使用不同类型的循环来实现这一点。但不能使用\foreach,因为有几个原因与可扩展性和分组有关。

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\definecsvcommand}{mm}
 {% #1 = command to be defined
  % #2 = number of columns (should be ≥ 1) 
  % we build the appropriate token list
  \tl_set:Nn \l_tmpa_tl { \csvcoli }
  % the loop will step from 2 to #2 where we also add &
  \int_step_inline:nnn { 2 } { #2 }
   {
    \tl_put_right:Ne \l_tmpa_tl { & \exp_not:c { csvcol \int_to_roman:n { ##1 } } }
   }
  % build the temporary command
  \cs_set:NV \__mattiap_csv_command: \l_tmpa_tl
  % finish up
  \cs_new_eq:NN #1 \__mattiap_csv_command:
 }

\cs_generate_variant:Nn \cs_set:Nn { NV }
\cs_new:Nn \__mattiap_csv_command: {} % initialize

\ExplSyntaxOff

\definecsvcommand{\mycommand}{5}

\ShowCommand{\mycommand}

\stop

控制台将显示该命令按预期定义:

> \mycommand=\long macro:
->\csvcoli &\csvcolii &\csvcoliii &\csvcoliv &\csvcolv .

这个想法是建立适当的令牌列表:

\exp_not:c { csvcol \int_to_roman:n { ##1 } }

控制序列\csvcoli\csvcolii被创建,但不再在请求的扩展中进一步扩展e。之后,利用\cs_new:Nn(实际上是其变体) 定义临时命令,并使请求的命令等于临时命令。


如果只需要一个命令,代码非常简单:

\ExplSyntaxOn
\tl_set:Nn \l_tmpa_tl { \csvcoli }
\int_step_inline:nnn { 2 } { 5 }
 {
  \tl_put_right:Ne \l_tmpa_tl { & \exp_not:c { csvcol \int_to_roman:n { #1 } } }
 }
 \exp_args:NNV \cs_new:Npn \mycommand \l_tmpa_tl
\ExplSyntaxOff

哪个可能简化为

\ExplSyntaxOn
\tl_set:Nn \mycommand { \csvcoli }
\int_step_inline:nnn { 2 } { 5 }
 {
  \tl_put_right:Ne \mycommand { & \exp_not:c { csvcol \int_to_roman:n { #1 } } }
 }
\ExplSyntaxOff

并且可以与其他答案中的代码进行比较,就简洁性而言。为什么可能? 因为expl3最好保持标记列表变量和宏/函数之间的区别。

我们要定义的是执行某些操作的命令,因此它不是某些标记的容器。归根结底,两者都是 TeX 宏,但这并不重要。

进一步注意:使用扩展分配\tl_put_right:Ne避免执行两个步骤(首先添加&然后\csvcol<suffix>),而\exp_not:c清楚地执行“形成控制序列名称,但不要进一步扩展”。

答案3

您可以使用\romannumeral尾递归循环来驱动扩展。

如果要在表格环境/对齐内执行循环,则需要确保将用户提供内容的宏参数嵌套在花括号之间进行“屏蔽”,直到结果传递为止。

否则,诸如表示数字 38 的字母常量之类的东西`&可能会被错误地用作表格单元格分隔符,进而会破坏\romannumeral驱动的扩展链。

例如,查看错误消息

\documentclass{article}
\begin{document}
\def\foo#1{#1}%
\begin{tabular}{ll}
arabic&roman\\
text \number38 &text \romannumeral38\\
text \expandafter\foo\expandafter{\number`&}&text \expandafter\foo\expandafter{\romannumeral`&}%
%\\text \number`&&text \romannumeral`&
\end{tabular}
\end{document}

取消注释之前的行时\end{tabular}

以下是代码:

\makeatletter
% \romannumeral\commandloop{<lower bound>}%
%                          {<result gathered so far>}%
%                          {<upper bound>}%
%                          {<prefix>}{<postfix>}{<separator>}{<next separator>}
\@ifdefinable\stopromannumeral{\chardef\stopromannumeral=`\^^00 }%
\newcommand\exchange[2]{#2#1}%
\newcommand\commandloop[7]{%
  \expandafter\@firstofone\expandafter{%
    \ifnum\numexpr(#3)\relax<\numexpr(#1)\relax
    \expandafter\@firstoftwo\else
  }\expandafter\@secondoftwo\fi
  {\stopromannumeral#2}{%
    \expandafter\commandloop\expandafter{%
      \the\numexpr((#1)+1)\expandafter\relax\expandafter
    }\expandafter{%
      \romannumeral
      \expandafter\exchange\expandafter{%
        \csname#4\romannumeral\numexpr(#1)\relax#5\endcsname
      }{\stopromannumeral#2#6}%
    }{#3}{#4}{#5}{#7}{#7}%
  }%
}%
\makeatother
%--------------------------------------------------------------
\newcommand{\myfirstcommand}{%
  \romannumeral\commandloop{1}{}{5}{csvcol}{}{}{&}%
}%
%--------------------------------------------------------------
\newcommand{\mysecondcommand}{\myfirstcommand}
%--------------------------------------------------------------
\expandafter\newcommand\expandafter\mythirdcommand\expandafter{%
  \romannumeral\commandloop{1}{}{5}{csvcol}{}{}{&}%
}%
%--------------------------------------------------------------
\newcommand\PassFirstToSecond[2]{#2{#1}}%
\expandafter\PassFirstToSecond\expandafter{%
  \romannumeral\commandloop{1}{}{5}{csvcol}{}{}{&}%
}{\newcommand\myfourthcommand}%
%--------------------------------------------------------------
\show\myfirstcommand
\show\mysecondcommand
\show\mythirdcommand
\show\myfourthcommand
\stop

相关内容