8

I would like to be able to check if a number is in a list of comma separated numbers. The code here works if the list is hardcoded into the command: Membership check on comma separated list

But, I would like the list of comma separated numbers to be a variable. Once I implement this (with \newcommand used to define the variable) the command breaks down. Why?

MWE

\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}

The output:

True
False

How can I change the command to make this work? I've tried all kinds of \expandafter to no avail.

(PS: another trivial coding idea taking an hour to implement in Latex...)

9

True, \foo is not expanded and therefore doesn't seem like a CSV within \ifmember. We can expand both arguments to ensure you're working with a full list:

True
True
False
True
True

\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}
7

No need to reinvent the wheel. ;-)

\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> has the comma separated list in the first argument, the item to look for in the second one.

To be honest, I'd better split the check into two variants: one for a “naked” argument, one for a control sequence:

\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}

enter image description here

Anyway, here's a solution for your code: reverse the arguments, so you can easily reach the second with \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

As you mentioned that you tried with \expandafter, I provide an example exhibiting how first-level-expansion of \foo could be accomplished using \expandafter.

Nonetheless I definitely prefer Werner's answer and egreg's answer to what I am about to provide.

\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}

The gist of the trick is having TeX flip around macro arguments in order to temporarily have the second argument available as first argument and thus knowing exactly about the amount of \expandafter needed for "reaching" it.

Of course this trick can only be applied successfully in those special cases where first-level-expansion of \ifmember's second argument's first token yields fully expanded the entire list and not something like \barA,\barB which would be the case with:

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

In the latter case first-level-expanding \foo yields: \barA,\barB and not 3,4 while Werner's \edef-approach does not just first-level-expansion but does expand completely until there are no more expandable tokens and thus even in the latter case (and the like situations) yields 3,4.

That's why I prefer Werner's answer and why my answer should not be taken for something suitable for all-day-usage but should be taken for a moot point showing how the trick could be done using \expandafter in your special case of first-level-expanding \foo already yielding the fully expanded list.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.