14

I want to be able to have a macro access a specific element of a list. The following code works, however requires me to have two separate macros: one that accepts a list, and the other that accepts the name of a macro that contains the list.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{xstring}

\begin{document}

\newcommand*{\GetListMemberA}[2]{%
    \foreach \a [count=\i] in {#1} {%
        \IfEq{\i}{#2}{\a\breakforeach}{}%
    }%
    \par%
}%

% This is same as above except does not have {} around #1
\newcommand*{\GetListMemberB}[2]{%
    \foreach \a [count=\i] in #1 {%
        \IfEq{\i}{#2}{\a\breakforeach}{}%
    }%
    \par%
}%


\newcommand*{\MyList}{a1,b2,c3,d4,e5}%

% These work if the foreach uses {#1} (ie, with the curly braces)
This should print "b": \GetListMemberA{a,b,c,d,e}{2}%
This should be blank: \GetListMemberA{a,b,c,d,e}{6}%

% These work if the foreach uses #1 (ie, without the curly braces}
This should be "a1": \GetListMemberB{\MyList}{1}%
This should be "c3": \GetListMemberB{\MyList}{3}%
This should be blank: \GetListMemberB{\MyList}{6}%

\end{document}

This question on TikZ \foreach loop with macro-defined list suggest to remove the braces around the macro when using a foreach, and that is how I was able to come up with this solution.

So, how can I change this so I don't have to have two macros?

I don't think this issue is related to Using Macro Defined Lists in TikZ/PGFplots as that was specific to lists used to label tick marks on axis.

1

4 Answers 4

11

To make the macro work both with inline lists and with lists in macros, you can use \edef to assemble the loop command, with \noexpand before every macro except for the arguments:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{xstring}

\begin{document}

% This works both with inline lists and with macros containing lists
\newcommand*{\GetListMember}[2]{%
    \edef\dotheloop{%
    \noexpand\foreach \noexpand\a [count=\noexpand\i] in {#1} {%
        \noexpand\IfEq{\noexpand\i}{#2}{\noexpand\a\noexpand\breakforeach}{}%
    }}%
    \dotheloop
    \par%
}%


\newcommand*{\MyList}{a1,b2,c3,d4,e5}%

This should print "b": \GetListMember{a,b,c,d,e}{2}%
This should be blank: \GetListMember{a,b,c,d,e}{6}%

This should be "a1": \GetListMember{\MyList}{1}%
This should be "c3": \GetListMember{\MyList}{3}%
This should be blank: \GetListMember{\MyList}{6}%

\end{document}
1
  • That works. One minor change: Need to add a %1 after the the }}`. Otherwise end up with extra spaces. Commented Jun 25, 2011 at 4:10
5

You can use \StrBetween and your code does not require calc or pgf:

\documentclass{article}
\usepackage{xstring}
\begin{document}

% This works both with inline lists and with macros containing lists
\newcommand*\GetListMember[2]{\StrBetween[#2,\number\numexpr#2+1]{,#1,},,\par}%

\newcommand*\MyList{a1,b2,c3,d4,e5}

This should print "b": \GetListMember{a,b,c,d,e}{2}
This should be blank: \GetListMember{a,b,c,d,e}{6}

This should be "a1": \GetListMember{\MyList}{1}
This should be "c3": \GetListMember{\MyList}{3}
This should be blank: \GetListMember{\MyList}{6}
\end{document}
5

The macro \mypkg_expand:Nw expands or not the argument of a function depending on whether it is unbraced, or braced. The \GetListMember is just defined through that macro, and \GetListMember_aux:nn recieves the list (expanded once) as a first argument and the index as a second. Now \clist_item:nn starts counting at 1. Negative arguments start from the right, but require a recent version of the expl3 package.

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_protected_nopar:Npn \mypkg_expand:Nw #1
  {
    \peek_catcode_ignore_spaces:NTF \c_group_begin_token
      { #1 } { \exp_args:NV #1 }
  }
\cs_new_nopar:Npn \GetListMember
  { \mypkg_expand:Nw \GetListMember_aux:nn }
\cs_new_nopar:Npn \GetListMember_aux:nn #1 #2
  {
    \clist_item:nn {#1} {#2}
    \par
  }
\ExplSyntaxOff

\begin{document}
\newcommand*{\MyList}{a1,b2,c3,d4,e5}

This should print ``b'': \GetListMember{a,b,c,d,e}{2}%
This should be blank:  \GetListMember{a,b,c,d,e}{6}%
This should be ``a1'':   \GetListMember\MyList{1}%
This should be ``c3'':   \GetListMember\MyList{3}%
This should be blank:  \GetListMember\MyList{6}%

\smallskip

This should print ``b'': \GetListMember{a,b,c,d,e}{-4}%
This should be blank:  \GetListMember{a,b,c,d,e}{-6}%
This should be ``a1'':   \GetListMember\MyList{-5}%
This should be ``c3'':   \GetListMember\MyList{-3}%
This should be blank:  \GetListMember\MyList{-6}%

\end{document}
0
1

This doesn't cope with the case when you try it with one element list that happens to be a control sequence. It would be possible to make the difference according to whether a brace follows or not, but I don't find this very robust.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\GetListMember}{mm}
 {
  \bool_lazy_and:nnTF { \tl_if_single_p:n { #1 } } { \token_if_cs_p:N #1 }
   {% it's a control sequence
    \clist_item:Nn #1 { #2 }
   }
   {
    \clist_item:nn { #1 } { #2 }
   }
 }
\ExplSyntaxOff

\begin{document}

\newcommand*{\MyList}{a1,b2,c3,d4,e5}%

This should print ``b'': \GetListMember{a,b,c,d,e}{2}

This should be blank: \GetListMember{a,b,c,d,e}{6}

This should be ``a1'': \GetListMember{\MyList}{1}

This should be ``c3'': \GetListMember{\MyList}{3}

This should be blank: \GetListMember{\MyList}{6}

This should be ``a1'': \GetListMember\MyList{1}

This should be ``c3'': \GetListMember\MyList{3}

This should be blank: \GetListMember\MyList{6}

\end{document}

enter image description here

In general, I'd prefer using * in the case we want a control sequence (or conversely, according to your preferences).

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\GetListMember}{smm}
 {
  \IfBooleanTF{#1}
   {% * means control sequence
    \clist_item:Nn #2 { #3 }
   }
   {
    \clist_item:nn { #2 } { #3 }
   }
 }
\ExplSyntaxOff

\begin{document}

\newcommand*{\MyList}{a1,b2,c3,d4,e5}%

This should print ``b'': \GetListMember{a,b,c,d,e}{2}

This should be blank: \GetListMember{a,b,c,d,e}{6}

This should be ``a1'': \GetListMember*{\MyList}{1}

This should be ``c3'': \GetListMember*{\MyList}{3}

This should be blank: \GetListMember*{\MyList}{6}

\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .