如何将宏输出转换为字符串

如何将宏输出转换为字符串

我希望能够检查某个数字是否在逗号分隔的数字列表中。如果列表被硬编码到命令中,则此处的代码有效:使用逗号分隔列表检查成员资格

但是,我希望逗号分隔的数字列表是一个变量。一旦我实现这一点(使用 \newcommand 定义变量),命令就会崩溃。为什么?

平均能量损失

\documentclass[11pt,letterpaper]{article}

\makeatletter
\newcommand\ifmember[2]{%
\in@{,#1,}{,#2,}%
\ifin@
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\makeatother

\begin{document}

\ifmember{3}{3,4}{True}{False}
\newcommand{\foo}{3,4}
\ifmember{3}{\foo}{True}{False}

\end{document}

输出:

True
False

我该如何更改命令以使其工作?我尝试了各种 \expandafter,但无济于事。

(PS:另一个简单的编码想法需要一个小时才能在 Latex 中实现......)

答案1

确实,\foo没有展开,因此看起来不像是 中的 CSV \ifmember。我们可以展开两个参数以确保您使用的是完整列表:





\documentclass{article}

\makeatletter
\newcommand\ifmember[2]{%
  \begingroup
  \edef\x{\endgroup\noexpand\in@{,#1,}{,#2,}}\x%
  \ifin@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\makeatother

\begin{document}

\ifmember{3}{3,4}{True}{False}

\newcommand{\foo}{3,4}%
\ifmember{3}{\foo}{True}{False}

\ifmember{44}{\foo}{True}{False}

\ifmember{\foo}{\foo}{True}{False}

\newcommand{\baz}{4}%
\ifmember{\baz}{\foo}{True}{False}

\end{document}

答案2

没必要重新发明轮子。;-)

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\ifmember}{mmmm}
 {
  \clist_if_in:onTF { #2 } { #1 } { #3 } { #4 }
 }
\cs_generate_variant:Nn \clist_if_in:nnTF { o }
\ExplSyntaxOff

\begin{document}

\ifmember{3}{3,4}{True}{False} (expected: True)

\ifmember{3}{ 3 ,4}{True}{False} (expected: True)

\newcommand{\foo}{3,4}
\ifmember{3}{\foo}{True}{False} (expected: True)

\renewcommand{\foo}{3, 4}

\ifmember{4}{\foo}{True}{False} (expected: True)

\ifmember{foo}{\foo}{True}{False} (expected: False)

\end{document}

\clist_if_in:<args>第一个参数中有逗号分隔的列表,第二个参数中有要查找的项目。

老实说,我最好将检查分成两个变量:一个用于“裸”参数,一个用于控制序列:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\ifmember}{smmmm}
 {
  \IfBooleanTF{#1}
   {% explicit argument
    \clist_if_in:nnTF { #3 } { #2 } { #4 } { #5 }
   }
   {% implicit argument
    \clist_if_in:VnTF #3 { #2 } { #4 } { #5 }
   }
 }
\cs_generate_variant:Nn \clist_if_in:nnTF { V }
\ExplSyntaxOff

\begin{document}

\ifmember*{3}{3,4}{True}{False} (expected: True)

\ifmember*{3}{ 3 ,4}{True}{False} (expected: True)

\newcommand{\foo}{3,4}
\ifmember{3}{\foo}{True}{False} (expected: True)

\renewcommand{\foo}{3, 4}

\ifmember{4}{\foo}{True}{False} (expected: True)

\ifmember{foo}{\foo}{True}{False} (expected: False)

\end{document}

在此处输入图片描述

无论如何,这里有一个针对您的代码的解决方案:反转参数,这样您可以轻松地用达到第二个\expandafter

\documentclass[11pt,letterpaper]{article}

\makeatletter
\newcommand\ifmember[2]{%
  \expandafter\ifmember@aux\expandafter{#2}{#1}%
}
\newcommand\ifmember@aux[2]{%
  \in@{,#2,}{,#1,}%
  \ifin@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\makeatother

\begin{document}

\ifmember{3}{3,4}{True}{False}
\newcommand{\foo}{3,4}
\ifmember{3}{\foo}{True}{False}

\end{document}

答案3

正如您提到您尝试过的,我提供了一个示例,展示如何使用完成的\expandafter一级扩展 。\foo\expandafter

尽管如此,我还是更喜欢沃纳的回答egreg 的回答我将要提供的内容。

\documentclass[11pt,letterpaper]{article}

\makeatletter
\newcommand\ifmember[2]{%
  \in@{,#1,}{,#2,}%
  \ifin@
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}%
\makeatother

\newcommand\ExpandSecondArgumentOnceFirst[2]{%
  \expandafter\PassFirstArgumentToSecondArgument\expandafter{#2}{#1}%
}%
\newcommand\PassFirstArgumentToSecondArgument[2]{#2{#1}}%


\begin{document}

\ifmember{3}{3,4}{True}{False}

\newcommand\foo{3,4}

\ExpandSecondArgumentOnceFirst{\ifmember{3}}{\foo}{True}{False}

% or:

\expandafter\PassFirstArgumentToSecondArgument\expandafter{\foo}{\ifmember{3}}{True}{False}

\end{document}

这个技巧的要点是让 TeX 翻转宏参数,以便暂时将第二个参数用作第一个参数,从而准确知道\expandafter“达到”它所需的数量。

当然,这个技巧只能在特殊情况下成功应用,即\ifmember第二个参数的第一个标记的一级扩展产生完全扩展的整个列表,而不是像以下情况那样\barA,\barB

\def\barA{3}
\def\barB{4}
\def\foo{\barA,\barB}

在后一种情况下,第一级扩展\foo会产生:\barA,\barB而不是3,4,而 Werner 的\edef方法不仅会进行第一级扩展,而且会完全扩展,直到没有更多可扩展的标记,因此即使在后一种情况下(以及类似情况)也会产生3,4

这就是为什么我更喜欢沃纳的答案,以及为什么我的答案不应被视为适合全天使用的东西,而应被视为一个没有实际意义的观点,展示如何\expandafter在第一级扩展的特殊情况下使用这个技巧来\foo已经产生完全扩展的列表。

相关内容