32

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):

  1. whether or not they're expandable,
  2. how they handle empty items,
  3. how they handle extraneous leading and trailing spaces
  4. 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}

enter image description here

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 an f-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:

  1. 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.
  2. Responses which introduce other methods for iterating over a list of items along with their known weaknesses and strengths.
  3. Responses which can illustrate realistic examples for which one would want to use such a loop in an expandable context
  4. Reponses which illustrate how one can save information gathered from within the group for later use.
13
  • There is also forloop.
    – marczellm
    Oct 27, 2013 at 7:50
  • @marczellm forloop is for looping a counter: much more like expl3's \int_step_function:nnnN or similar.
    – Joseph Wright
    Oct 27, 2013 at 8:12
  • Serendipity. The way you passed that list to be directly used by \foreach saved me asking a question. I'm on a steep learning curve with tikz and pgfplots and as yet just getting started on loops and your question is right up my alley. Can't wait to see the answers. Oct 27, 2013 at 9:28
  • Why \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.
    – egreg
    Oct 27, 2013 at 10:02
  • @egreg I used \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?
    – A.Ellett
    Oct 27, 2013 at 14:50

2 Answers 2

11

I propose a different definition of \current@item

\def\current@item#1{%
  \stepcounter{item@count}
  \item $|$#1$|$\ $|$\texttt{\detokenize{#1}}$|$%
}

so the output also shows what's really passed as its argument. Also I changed the complicated management of \my@item@count with a simple counter. I don't comment about \@tfor, which is a different tool not designed for comma separated lists.

  1. etoolbox: \forcsvlist doesn't remove trailing spaces; your definition is too complicated, because

    %% `etoolbox`
    \def\etoolbox@myloop#1{%%
      \forcsvlist\current@item{#1}}
    

    suffices. Items are passed explicitly.

  2. \@for is the basic tool defined in the LaTeX kernel. Leading and trailing spaces are not removed. Items are passed as \x, the control sequence used after \@for.

  3. \foreach doesn't remove trailing spaces. Items are passed as \x, the control sequence used after \foreach.

  4. ae is basically like \@for, although it works by expansion. Leading and trailing spaces are not removed. Items are passed explicitly.

  5. expl removes leading and trailing spaces, but also empty items; items are passed explicitly.

\documentclass{article}
\usepackage[margin=0.5in,paperheight=15in]{geometry}
\usepackage{xparse}
\usepackage{etoolbox}
\usepackage{pgffor}
\usepackage{enumitem}
\makeatletter

\newcounter{item@count}
%% \rules to emphasize how spaces are seen and treated!
\def\current@item#1{%
  \stepcounter{item@count}
  \item $|$#1$|$\ $|$\texttt{\detokenize{#1}}$|$%
}

%% `etoolbox`
\def\etoolbox@myloop#1{%%
  \forcsvlist\current@item{#1}}

%% \@for
\def\@for@myloop#1{%%
  \@for \x:=#1\do{\current@item \x}}

%% pgffor
\def\pgffor@myloop#1{%%
  \foreach \x in {#1} {\typeout{pgf:\x}\current@item \x}}

%% version a la `ae`
\def\@ae@myloop#1,#2\@nil{%%
  \current@item{#1}%%
  \if\relax\detokenize{#2}\relax\else
    \@ae@myloop#2\@nil
   \fi
}
\def\ae@myloop#1{%%
  \@ae@myloop#1,\@nil
}

\def\listoffruit#1#2{%%
  \setcounter{item@count}{0}%%
  \noindent
  List of type \texttt{#1}
  \begin{itemize}[topsep=4pt,itemsep=2pt]
    \csname#1@myloop\endcsname{#2}%%
  \end{itemize}
  Total number of bulleted items: \arabic{item@count}%
  \par \vspace{2ex}\hrule\par \vspace{2ex}
  }

\ExplSyntaxOn
\NewDocumentCommand{\expl@myloop}{ m }
{
  \clist_map_inline:nn{#1}{\current@item {##1}}
}
\ExplSyntaxOff

\makeatother

\begin{document}

\listoffruit{etoolbox}{apples,, oranges, bananas ,cherries}

\listoffruit{@for}{apples,, oranges, bananas ,cherries}

\listoffruit{pgffor}{apples,, oranges, bananas ,cherries}

\listoffruit{ae}{apples,, oranges, bananas ,cherries}

\listoffruit{expl}{apples,, oranges, bananas ,cherries}

\end{document}

enter image description here

I'd add another expl3 loop, that also considers empty items:

\ExplSyntaxOn
\NewDocumentCommand{\seq@myloop}{ m }
 {
  \seq_set_split:Nnn \l_tmpa_seq { , } { #1 }
  \seq_map_inline:Nn \l_tmpa_seq { \current@item {##1} }
 }
\ExplSyntaxOff

(of course, using \l_tmpa_seq is discouraged, better allocate a new variable).

enter image description here

The comparison should leave no doubt. The only place where \foreach is superior is in its treatment of “incomplete lists”, such as 1,2,...,20.

6
  • The only reason my \forcsvlist is as complicated as presented is because I frequently pass my comma separated lists via macros. This is not necessarily an issue with pgffor's \foreach. It would be an issue with my homebrew example.
    – A.Ellett
    Oct 27, 2013 at 16:40
  • @A.Ellett In my opinion, the macros for dealing with explicit and implicit (given via a macro) lists should be different: you can get wrong result if an explicit list starts with a macro which will be expanded too early.
    – egreg
    Oct 27, 2013 at 16:43
  • I am aware of that issue. Do you have suggestions for a better approach? Or are you suggesting that's an entirely different question? Actually, this who question arose because I had a list saved in a macro and I wanted to use etoolbox's \forlistloop. But apparently the <listmacro> expected needs to be built internally which didn't work for me because of how I was generating the list.
    – A.Ellett
    Oct 27, 2013 at 16:48
  • Incidentally, I like your idea for illustrating what was passed via \detokenize.
    – A.Ellett
    Oct 27, 2013 at 16:49
  • @A.Ellett For expandable loops, if your list can be gathered before in a clist variable, you have \clist_map_function:NN which is expandable. I'm sure there's code around for expandably removing leading and trailing space, but I don't know how robust they are (if Bruno's not able to do it, …)
    – egreg
    Oct 27, 2013 at 17:26
1

LuaLaTeX:

\documentclass{standalone}
\usepackage{tikz}
\usepackage{filecontents}
\begin{document}
    \newcommand{\drawCircle}[1]{\tikz \draw (0,0) circle (#1);}
    \begin{filecontents*}{testlua.lua}
        for i=1,10 do
            tex.print("\\drawCircle{",0.1*i,"}")
        end
    \end{filecontents*}
    \directlua{dofile('testlua.lua')}
\end{document}
2
  • 2
    Seems like this is rather an answer but another method worth to be mentioned in the question ;-)
    – Tobi
    Oct 27, 2013 at 16:19
  • 1
    To make this an "Answer" you should add discussion of how it compares with the other methods. Oct 27, 2013 at 18:59

You must log in to answer this question.

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