我希望能够检查某个数字是否在逗号分隔的数字列表中。如果列表被硬编码到命令中,则此处的代码有效:使用逗号分隔列表检查成员资格
但是,我希望逗号分隔的数字列表是一个变量。一旦我实现这一点(使用 \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
已经产生完全扩展的列表。