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.
A.Ellett
  • 50,533
  • There is also forloop. – marczellm Oct 27 '13 at 07:50
  • @marczellm forloop is for looping a counter: much more like expl3's \int_step_function:nnnN or similar. – Joseph Wright Oct 27 '13 at 08: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. – Geoff Pointer Oct 27 '13 at 09: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 '13 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 '13 at 14:50
  • @A.Ellett It was not about readability; some loop constructs do not remove trailing or leading spaces from items. – egreg Oct 27 '13 at 14:54
  • @egreg Then that's getting to the heart of what I'm after. It seems that there are various delicacies with some of the approaches that are not present for others. I was trying to be too clever at too late an hour by smuggling in the LaTeX3 answer, and so wrote it in a manner that would work for all the approaches listed: notably because of \@tfor. – A.Ellett Oct 27 '13 at 15:02
  • \@tfor is really out of place here, because it doesn't manage comma separated lists. – egreg Oct 27 '13 at 15:56
  • There is also http://www.ctan.org/pkg/loops – Xoff Oct 27 '13 at 16:28
  • Another method that you should probably considered is \pgfplotsforeachungrouped, which comes with the pgfplots package. As I understand it, it's intended to be identical to the \foreach command from pgffor except that it is not executed within a group. However, I'm not qualified to understand any more subtle differences. Also, short of (possibly) copying code from the source, I don't know of a way to get this method without using the entire pgfplots package, which drags TikZ in with it. – Charles Staats Oct 27 '13 at 17:15
  • @CharlesStaats I think you should nevertheless still post your suggestion as an answer. Others will come along a make suggestions and point out strengths and weaknesses. – A.Ellett Oct 27 '13 at 17:18
  • @A.Ellet: Since the author of pgfplots is active on TeX.SE, I'm going to wait a bit to see if he responds first. In the meantime, here's a potentially relevant answer he wrote http://tex.stackexchange.com/a/17817/484 as well as another answer http://tex.stackexchange.com/a/43355/484 by a different author demonstrating one use of \pgfplotsforeachungrouped. – Charles Staats Oct 27 '13 at 17:25
  • Where is the \multido? – kiss my armpit Nov 01 '13 at 18:37

2 Answers2

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.

egreg
  • 1,121,712
  • 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 '13 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 '13 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 '13 at 16:48
  • Incidentally, I like your idea for illustrating what was passed via \detokenize. – A.Ellett Oct 27 '13 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 '13 at 17:26
  • @egreg Inspired by the OP I tried the list passing idea which worked well, but I very quickly got into trouble with the list as macro scenario. Should this be asked as a separate question or is there already a detailed answer somewhere? I'm restricting myself to tikz/pgfplots based solutions at this point. – Geoff Pointer Oct 27 '13 at 22:25
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}