I've been wondering about the various methods available for looping over a comma separated list and the like. In particular, I was wondering about their various strengths and weaknesses. In other words, I was wondering about such things as the following (but not solely limited to this list):
- whether or not they're expandable,
- how they handle empty items,
- how they handle extraneous leading and trailing spaces
- whether you can use
\def
/\edef
or need to use\gdef
/\xdef
to save information from within the loop for later use.
In no particular order, here's a list of looping methods I'm familiar with. In the following list \current@item
represents a macro, taking one argument, for formatting the current item in iteration.
Using commands from the 2ekernal
:
%% \@for
\def\@for@myloop#1{%%
\@for \x:=#1 \do{\current@item \x}}
%% \@tfor (not a comma separated list--probably shouldn't be here)
\def\@tfor@myloop#1{%%
\@tfor \x:=#1 \do{\if,\x\relax\else\current@item \x\fi}}
Using etoolbox
package
%% `etoolbox`: need to be careful whether passed a macro:
%% in that case expansion may be necessary to that the
%% delimiters are visible to `\forcsvlist`
\def\etoolbox@myloop#1{%%
\expandafter\forcsvlist
\expandafter\current@item
\expandafter{#1}}
Using pgffor
package
%% pgffor
\def\pgffor@myloop#1{%%
\foreach \x in {#1} {\current@item \x}}
A homebrew method:
%% version a la `ae`
\def\@ae@myloop#1,#2\@nil{%%
\current@item{#1}%%
\expandafter\ifx\expandafter\relax\detokenize{#2}\relax\else
\@ae@myloop#2\@nil
\fi
}
\def\ae@myloop#1{%%
\@ae@myloop#1,\@nil
}
Then there are also the various expl3
methods such as (there are quite a few, so this is hardly exhaustive):
\tl_map_inline:nn
\tl_map_function:Nn
\clist_map_inline:Nn
\seq_map_inline:Nn
Here is a MWE illustrating the results of each of these (including one LaTeX3 version):
\documentclass{article}
\usepackage[margin=0.5in,paperheight=15in]{geometry}
\usepackage{xparse}
\usepackage{etoolbox}
\usepackage{pgffor}
\usepackage{enumitem}
\makeatletter
\def\my@item@count{0}
\def\step@my@counter{\xdef\my@item@count{\number\numexpr\my@item@count+1\relax}}
%% \rules to emphasize how spaces are seen and treated!
\def\current@item#1{\step@my@counter\item \rule{0.4pt}{2ex}#1\rule{0.4pt}{2ex}}
%% `etoolbox`
\def\etoolbox@myloop#1{%%
\forcsvlist \current@item {#1}}
%% but if passed a macro, then it first needs to be expanded so the delimiters are visible to `\forcsvlist`. You'll need to write
%% \expandafter\forcsvlist \expandafter\current@item \expandafter{#1}}
%% \@for
\def\@for@myloop#1{%%
\@for \x:=#1 \do{\current@item \x}}
%% \@tfor
\def\@tfor@myloop#1{%%
\@tfor \x:=#1 \do{\current@item \x}}
%% pgffor
\def\pgffor@myloop#1{%%
\foreach \x in {#1} {\current@item \x}}
%% version a la `ae`
\def\@ae@myloop#1,#2\@nil{%%
\current@item{#1}%%
\expandafter\ifx\expandafter\relax\detokenize{#2}\relax\else
\@ae@myloop#2\@nil
\fi
}
\def\ae@myloop#1{%%
\@ae@myloop#1,\@nil
}
\def\listoffruit#1#2#3{%%
\def\my@item@count{0}%%
\noindent
List of type \texttt{#1}: \parbox[t]{3in}{\raggedright#3}
\begin{itemize}[topsep=4pt,itemsep=2pt]
\csname#1@myloop\endcsname{#2}%%
\end{itemize}
Total number of bulleted items: \my@item@count
\par \vspace{2ex}\hrule\par \vspace{2ex}
}
\ExplSyntaxOn
\NewDocumentCommand{\expl@myloop}{ m }
{
\clist_map_inline:nn{#1}{\current@item {##1}}
}
\ExplSyntaxOff
\makeatother
\def\apples{apples}
\def\bananas{bananas}
\def\cherries{cherries}
\begin{document}
\listoffruit{etoolbox}{apples,, oranges, bananas ,cherries}{Ignores leading spaces and empty items. Trailing spaces not ignored.}
\listoffruit{@for}{apples,, oranges, bananas ,cherries}{Empty lines and trailing or leading spaces not ignored. Something goes on with last item in list.}
\listoffruit{@tfor}{\apples,{} {oranges}\bananas\cherries}{Spaces ignored, all other token respected, bracketed tokens treated as one.}
\listoffruit{pgffor}{apples,, oranges, bananas ,cherries}{Ignores leading spaces, empty items and trailing spaces not ignored.}
\listoffruit{ae}{apples,, oranges, bananas ,cherries}{Leading or trailing spaces not ignored. Empty items not ignored.}
\listoffruit{expl}{apples,, oranges, bananas ,cherries}{Trailing or leading spaces ignored. Empty items ignored.}
\end{document}
Issues I'm aware of:
- I believe
\@for
is not expandable, \cslist_map_inline:nn
is expandable but with limitations: i.e., it is not expandable in anf
-type argument.pgffor
's\foreach
loop in executed within a group. So you need to use\gdef
or\xdeg
to save information from within the group for later use. I've not fully explored which others of the loops presented here have a similar short-coming (not sure that's the right choice of word). I have no idea whether\foreach
is expandable or not.- Some methods handle their lists rather nicely whether passed explicitly or implicitly via a macro. For example,
pgffor
's\foreach
knows what do to with a list passed via a macro.etoolbox
's\forcsvlist
needs that macro to first be expanded: hence the reason my first illustration of a\forcslist
is as complicated as it is.
So what I'm interested in here is:
- Responses which address the strengths and weaknesses of the different approaches presented here as itemized at the beginning of this post, but not necessarily limited to those suggestions since I may be unaware of other important issues.. For example, I'm not really sure which are expandable.
- Responses which introduce other methods for iterating over a list of items along with their known weaknesses and strengths.
- Responses which can illustrate realistic examples for which one would want to use such a loop in an expandable context
- Reponses which illustrate how one can save information gathered from within the group for later use.
forloop
.forloop
is for looping a counter: much more likeexpl3
's\int_step_function:nnnN
or similar.\foreach
saved me asking a question. I'm on a steep learning curve withtikz
andpgfplots
and as yet just getting started on loops and your question is right up my alley. Can't wait to see the answers.\expandafter\ifx\relax\detokenize{#1}\relax
? The shorter version\if\relax\detokenize{#1}\relax
is safe and reliable. About the bulk of the question: remove the braces around the items and try also with leading and trailing spaces.\ifx
because I understand it better than\fi
. I'm not really sure what you mean in your suggestion for improving the readability of the question. Could you post a link to another question that does something similar to what you're trying to suggestion?