82

How can I iterate, loop, process, or otherwise do something, with each element from a comma separated list of things?

This question has been asked perhaps many many times, and the are already lots of very good answers everywhere. However I think it would also be valuable to have a single reference point where all such alternative techniques are explained in detail, with their advantages and disadvantages discussed.

All of plain-TeX, LaTeX, LaTeX3, LuaTeX, Context, etoolbox, pgffor, etc. answers are invited. Please consider explaining, for example:

  • How does the command/method deal with whitespace around items?
  • How does the command/method deal with empty/missing items? (e.g. a,b,,c)
  • How does the command/method deal with a trailing comma?
  • Can the command/method work of a list stored on a macro?
  • Is each iteration evaluated globally or on a local group? (i.e., if a command is defined during an iteration, does the command survive the loop?)
  • Do items get expanded or somehow mangled before being processed?
  • What about lists where items are key=value or some other kind of pairs?
  • Does the method work for lists separated with something other than a comma?
  • Any other thing I should know before deciding to use this method?
1
  • 1
    In other words, the same question How to loop an array in LaTeX? I think array should somehow expressed here too because I did not find this thread when searching about the topic. May 26, 2017 at 17:48

14 Answers 14

41

The main loop for comma separated lists in LaTeX3 is

\clist_map_inline:nn

The first argument is an explicit list, the second argument tells LaTeX what to do with each item. For instance, we want to print an enumerate environment from the items:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\NewDocumentCommand{\makeenumerate}{ m }
 {
  \begin{enumerate}
  \clist_map_inline:nn { #1 } { \item \fbox{##1} }
  \end{enumerate}
 }

\ExplSyntaxOff

\begin{document}

\makeenumerate{a, b ,c d, ,e }

\end{document}

image

I used \fbox in order to illustrate some of the features:

  1. spaces are stripped on either side of the items;

  2. empty items are ignored (empty means only spaces between commas);

  3. no expansion is performed on the item.

Note that the current item is denoted by #1 and it's literally available, which is not the case with the usual \@for, where the current item is hidden in a macro. In the code above we have to use double ##1 because we're inside a macro definition.

A similar function is \clist_map_function:nN, which has the advantage of being fully expandable (but only in x full expansion, not f). The above example would be

\NewDocumentCommand{\makeenumerate}{ m }
 {
  \begin{enumerate}
  \clist_map_function:nN { #1 } \xyz_make_item:n
  \end{enumerate}
 }

\cs_new_protected:Npn \xyz_make_item:n #1
 {
  \item \fbox { #1 }
 }

In this case the current item is passed as an argument to the indicated function.

List mappings can be broken; let's say, in the above example, we want to stop processing if an item is \stop:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\NewDocumentCommand{\makeenumerate}{ m }
 {
  \begin{enumerate}
  \xyz_make_items:n { #1 }
  \end{enumerate}
 }
\cs_new_protected:Npn \xyz_make_items:n #1
 {
  \clist_map_inline:nn { #1 }
   {
    \tl_if_eq:nnTF { ##1 } { \stop }
     {
      \clist_map_break:
     }
     {
      \item \fbox { ##1 }
     }
   }
 }
\ExplSyntaxOff

\begin{document}

\makeenumerate{a, b ,c d, \stop ,e }

\end{document}

enter image description here

Note that complex code shouldn't be used in \NewDocumentCommand, so I defined an auxiliary function for this purpose.

One can also use

\clist_map_break:n

and the argument given to this function will be executed before breaking the mapping.

The same features apply when using

\keys_set:nn { <module> } { <comma list of key-value pairs> }

for evaluating a set of key-value pairs: leading and trailing spaces are ignored as are empty (blank) items.

If the comma separated list is stored in a macro, one can use

\clist_map_inline:Nn
\clist_map_function:NN

with the same ideas. In my opinion, it's bad programming style allowing both inputs and a variant should be defined.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\NewDocumentCommand{\makeenumerate}{ sm }
 {
  \begin{enumerate}
  \IfBooleanTF{#1}
   {
    \clist_set:NV \l_xyz_input_clist #2
    \xyz_make_items:V \l_xyz_input_clist
   }
   {
    \xyz_make_items:n { #2 }
   }
  \end{enumerate}
 }

\clist_new:N \l_xyz_input_clist
\cs_new_protected:Npn \xyz_make_items:n #1
 {
  \clist_map_inline:nn { #1 }
   {
    \tl_if_eq:nnTF { ##1 } { \stop }
     {
      \clist_map_break:
     }
     {
      \item \fbox { ##1 }
     }
   }
 }
\cs_generate_variant:Nn \xyz_make_items:n { V }
\ExplSyntaxOff

\begin{document}

\newcommand{\mylist}{A, B ,C,}

\makeenumerate{a, b ,c d, \stop ,e }

\makeenumerate*{\mylist}

\end{document}

Setting a variable with the contents of the macro holding the comma separated list is done because this process “normalizes” the comma separated list for better usage in \clist_map_inline:Nn.


If other delimiters are desired, the better method is to go to sequences; use \seq_map_inline:Nn or \seq_map_function:NN after splitting the input into components with

\seq_set_split:Nnn \l_xyz_input_seq { ; } { #1 }

Full example:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\NewDocumentCommand{\makeenumerate}{ O{,} m }
 {
  \begin{enumerate}
  \xyz_make_items:nn { #1 } { #2 }
  \end{enumerate}
 }

\seq_new:N \l_xyz_input_seq
\cs_new_protected:Npn \xyz_make_items:nn #1 #2
 {
  \seq_set_split:Nnn \l_xyz_input_seq { #1 } { #2 }
  \seq_map_inline:Nn \l_xyz_input_seq
   {
    \tl_if_eq:nnTF { ##1 } { \stop }
     {
      \seq_map_break:
     }
     {
      \item \fbox { ##1 }
     }
   }
 }
\ExplSyntaxOff

\begin{document}

\makeenumerate[;]{a; b ;c, d; \stop ;e }

\end{document}

enter image description here

Also here the splitting ignores leading and trailing spaces and also empty items.

8
  • Nice! Although for key=value items I would mention \keyval_parse:NNn instead, as this is the actual function doing just the parsing (instead of the more high-level option/key handling). Feb 8, 2014 at 12:05
  • 1
    @JuanA.Navarro That is in the “Low level interface” section, so it should be used only for extending the key processing to other situations.
    – egreg
    Feb 8, 2014 at 12:11
  • @egreg How can I create a loop with specific start and end indexes e.g. for \itemStep from 2 to \clist_count; do \clist_item:Nn\arglist{}. It seems that the example you demonstrated are automated interations without much control over the individual steps/iterations. May 7, 2015 at 9:32
  • 1
    @macmadness86 \int_step_inline:nnnn {2}{1}{\clist_count:N \arglist}{...\clist_item:Nn \arglist {#1}} (with #1 becoming ##1 if this is used in the body of a definition).
    – egreg
    May 7, 2015 at 9:35
  • @egreg Thanks. Did you write LaTeX3? Why are you so tremendously skilled at this? To deal with separate formatting on the last item (like removing a comma), I tried using: \str_if_eq:nnTF { ##1 } { \clist_count:N \arglist }{\clist_item:Nn \arglist {##1}}{\clist_item:Nn \arglist {##1},~}. Is that the right approach? (##1 is not expanded correctly in parameters: T and F) Another option is to just make the steps go to \clist_count-1 (whatever the correct syntax for that is). May 7, 2015 at 10:27
18

The etoolbox package provides some list processing tools. For comma-separated lists there are

  • \docsvlist{<list>} where each item is put in the input stream with \do{<item>}. This means a suitable definition of \do has to be provided.
  • \forcsvlist{<handler>}{<list>} where each item is put in the input stream as argument to <handler> which can be arbitrary code provided its last token has a mandatory argument.

The following example shows that empty items are stripped from the list. For each item leading spaces are removed, trailing spaces aren't!

\documentclass{article}
\usepackage{etoolbox}

\makeatletter
\newcommand\makeenumerate[1]{%
  \begin{enumerate}
    \forcsvlist{\makeenumerate@item}{#1}
  \end{enumerate}
}
\newcommand\makeenumerate@item[1]{\item \fbox{#1}}
\makeatother

\begin{document}

\makeenumerate{a, b ,c d, ,e }

\end{document}

enter image description here

For lists with other separators parsers can be defined with

\DeclareListParser*{<macro>}{<separator>}

both above mentioned parsers are defined with it:

\DeclareListParser{\docsvlist}{,}
\DeclareListParser*{\forcsvlist}{,}

An example as variation of the above example:

\documentclass{article}
\usepackage{etoolbox}

\makeatletter
\DeclareListParser*\formylist{;}
\newcommand\makeenumerate[1]{%
  \begin{enumerate}
    \formylist{\makeenumerate@item}{#1}
  \end{enumerate}
}
\newcommand\makeenumerate@item[1]{\item \fbox{#1}}
\makeatother

\begin{document}

\makeenumerate{a; b ;c d; ;e }

\end{document}

For list processing not dealing with user input directly etoolbox provides quite a number of different macros. All of them deal with macros that hold the list. The next variation of the above example shows \listadd{<list>}{<item>} and \forlistloop{<handler>}{<list>} where both times <list> means a macro holding the etoolbox list:

\documentclass{article}
\usepackage{etoolbox}

\makeatletter
\newcommand*\mylist{}
\newcommand\makeenumerate[1]{%
  \forcsvlist{\listadd\mylist}{#1}%
  \begin{enumerate}
    \forlistloop{\makeenumerate@item}{\mylist}
  \end{enumerate}
}
\newcommand\makeenumerate@item[1]{\item \fbox{#1}}
\makeatother

\begin{document}

\makeenumerate{a, b ,c d, ,e }

\end{document}

There are quite a number of additional macros (e.g. \listeadd for adding expanded items, or conditionals like \ifinlist ...) which are all explained in the manual.


In all of etoolbox's list parsers \listbreak can be used to break the loop:

\documentclass{article}
\usepackage{etoolbox}

\makeatletter
\newcommand*\mylist{}
\newcommand\makeenumerate[1]{%
  \forcsvlist{\listadd\mylist}{#1}%
  \begin{enumerate}
    \forlistloop{\makeenumerate@item}{\mylist}
  \end{enumerate}
}
\newcommand\makeenumerate@item[1]{%
  \ifstrequal{#1}{\stop}
    {\listbreak}
    {\item \fbox{#1}}%
}
\makeatother

\begin{document}

\makeenumerate{a, b ,c d, \stop ,e }

\end{document}

enter image description here

16

Using egreg's template for straight LaTeX

\documentclass{article}

\makeatletter
\newcommand{\makeenumerate}[2][,]{\begin{enumerate}%
  \def\dolist##1{\expandafter\@dolist##1#1\@eol}%
  \def\@dolist##1#1##2\@eol{%
    \begingroup\setbox0=\hbox{##1\unskip}\ifdim\wd0=0pt\endgroup\else\endgroup\item \fbox{\ignorespaces##1\unskip}\fi%
    \ifx\@eol##2\@eol\else\@dolist##2\@eol\fi}%
  \dolist{#2}%
\end{enumerate}}
\makeatother

\begin{document}

\makeenumerate{a, b ,c d, ,e }

\makeenumerate[;]{a; b ;c d; ;e }
\end{document}

enter image description here

  • How does the command/method deal with whitespace around items: It strips leading and trailing spaces
  • How does the command/method deal with empty/missing items: It ignores them
  • How does the command/method deal with a trailing comma: It ignores it
  • Can the command/method work of a list stored on a macro: No
  • Is each iteration evaluated globally or on a local group: Locally
  • Do items get expanded or somehow mangled before being processed: Yes, they get expanded into box0
  • What about lists where items are key=value or some other kind of pairs: Shouldn't matter
  • Does the method work for lists separated with something other than a comma: Yes
  • Any other thing I should know before deciding to use this method: If you are not trying to ignore empty items, you don't run into the box0 issues
2
  • It would be nice to get some more difficult examples, etc how to insert text there with your iterations at the same time. May 26, 2017 at 17:55
  • 1
    @LéoLéopoldHertz준영 ##1 is the item of the array and you can replace \fbox with essentially whatever you want while being careful about spaces. For example \fbox{\ignorespaces AAA ##1\unskip BBB}.
    – StrongBad
    May 26, 2017 at 19:54
15

There is a package lambda-lists by Alan Jeffrey for "Lists in TeX's mouth". Here's an example in plain TeX:

\catcode`@=11 \input lambda.sty \catcode`@=12
\tracingmacros=1 % check out the log file afterwards!

\def\mylist{\Listize[1,  2 ,3,,4=foo,5,]}
\def\myfun#1{do something with #1\def\foo{}}
\def\fooDefined?{\ifdefined\foo foo's defined\else foo's undefined\fi}

\Unlistize\mylist\par

{\Unlistize{\Map\myfun\mylist}\par}
% or, \def\myfun#1{{do something with #1\def\foo{}}}
\fooDefined?

\Unlistize{\Map\myfun\mylist}\par
\fooDefined?

\def\mylist{\Listize[1,2,3,4,5]} % would error with non-numeric entries w/ below
\Unlistize{\Map\myfun{\Filter{\Lessthan3}\mylist}}

\bye

enter image description here

2
  • Do you do \usepackage{lambda}? It would be nice to get complete example. May 26, 2017 at 17:51
  • @LéoLéopoldHertz준영 this is a plain TeX solution while \usepackage is a LaTeX macro. the lamba-lists package does work in LaTeX, so it would be possible to make a LaTeX solution, but if you are using LaTeX, the other solutions might be better.
    – StrongBad
    May 26, 2017 at 19:46
12

Package kvsetkeys

  • Package kvsetkeys provides parsers for key value lists and comma separated lists.
  • Many formats are supported: LaTeX, plain TeX, it works even with iniTeX.
  • Syntax:

    \comma@parse{⟨comma separated list⟩}{⟨code/processor with one argument⟩}
    

Example

The example is given for iniTeX to show the minimal requirements of the package.

% Setup for iniTeX
\catcode`\{=1
\catcode`\}=2
\catcode`\#=6
\catcode`\@=11

% Package is loaded; LaTeX: \usepackage{kvsetkeys}
\input kvsetkeys.sty

% -------------------------------------------------------------------
% Call with entry processor that takes the entry as argument
% -------------------------------------------------------------------
\def\ProcessEntry#1{\message{<#1>}}
\comma@parse{a, b,, c ,d,}\ProcessEntry

% Result: <a> <b> <c> <d>

% -------------------------------------------------------------------
% Funny entries
% -------------------------------------------------------------------
\long\def\@gobble#1{}% from LaTeX
\def\@empty{}% from LaTeX
\comma@parse{
  a, #1, \fi, \iffalse, {b}, {{c}}, d e f, {}, \empty, { }, {g,h}
}{%
  \immediate\write16{[\meaning\comma@entry]}%
  \@gobble
}

% Result:
%   [macro:->a]
%   [macro:->##1]
%   [macro:->\fi ]
%   [macro:->\iffalse ]
%   [macro:->b]
%   [macro:->{c}]
%   [macro:->d e f]
%   [macro:->\empty ]
%   [macro:-> ]
%   [macro:->g,h]

% -------------------------------------------------------------------
% \comma@break allows the premature ending of the list processing
% -------------------------------------------------------------------
\comma@parse{
  2,3,5,7,0,1,4
}{%
  \ifnum\comma@entry=0 %
    \comma@break
  \else
    \message{<\comma@entry>}%
  \fi
  \@gobble
}

% Result: <2> <3> <5> <7>

% -------------------------------------------------------------------
% Active commas are supported as separators
% -------------------------------------------------------------------
\catcode`\,=13 % active
\def,{;}
\comma@parse{a,b,c}{\message{(\comma@entry)}\@gobble}

% Result: (a) (b) (c)

% End of job
\csname @@end\endcsname\end

Answer to the questions

  • How does the command/method deal with whitespace around items?

Spaces (also more than one) are removed around items.

  • How does the command/method deal with empty/missing items? (e.g. a,b,,c)

Empty items are ignored. An empty item can be specified using a pair of curly braces: a, {}, b

  • How does the command/method deal with a trailing comma?

This is interpreted as an empty last item, which is handled as other empty items by ignoring, see previous answer.

  • Can the command/method work of a list stored on a macro?

Since the list is the first argument of \comma@parse, a list macro can easily unpacked with \expandafter:

\def\list{a,b,c}
\expandafter\comma@parse\expandafter{\list}{...}
  • Is each iteration evaluated globally or on a local group? (i.e., if a command is defined during an iteration, does the command survive the loop?)

The loop is not put into a local group, the current group level is not changed. Thus assignments including definitions survive the loop.

  • Do items get expanded or somehow mangled before being processed?

No. The entry is directly available, if a processor with argument is used for \comma@parse. Otherwise the entry is stored and available in macro \comma@entry.

If the value is wrapped into curly braces, then one level of curly braces is removed exactly. This way, by putting the item in a pair of curly braces, the item can be empty, can contain spaces or commas and further properly nested curly braces are not touched.

  • What about lists where items are key=value or some other kind of pairs?

Package kvsetkeys also supports lists with key value pairs.

  • Does the method work for lists separated with something other than a comma?

Active commas (and active equal signs for key value processing) are supported. But other symbols cannot be used as separators.

  • Any other thing I should know before deciding to use this method?
  • \comma@break implements a shortcut for ending the list parsing, if the following entries are not needed any more.
  • Currently entries with \par break \comma@parse. This will be supported in the next version.
2
  • Can you please compare your proposal to the proposal in the answer tex.stackexchange.com/a/326801/13173 When do you use it and when the other one? May 26, 2017 at 17:52
  • @LéoLéopoldHertz준영 I have no idea, what you want to know specifically, since the other package pgffor is also presented here, see the answer of Henri Menke. Thus, you have already a good start for a comparison of both packages. May 26, 2017 at 19:17
11

The old standard LaTeX \@for. I don't claim expertise on it, it's just what I came across as a solution for my limited applications.

Example from my thesis class:

\documentclass{article}

\makeatletter
\newcommand{\committeemembers}[1]{\providecommand\ttu@committeemembers{}\renewcommand{\ttu@committeemembers}{#1}}
\committeemembers{Member1=Name,Member2=Name,Member3=Name}
\makeatother

\begin{document}

\makeatletter
\newcounter{mycounter}
%\@for\themember:={Member 1 Name,Member 2 Name,Member 3 Name}\do{\stepcounter{mycounter} \themember{} \themycounter }
\@for\themember:=\ttu@committeemembers\do{\fbox{\themember}}
\makeatother

\end{document}

Q&A:

  1. How does the command/method deal with whitespace around items? Whitespace is preserved.
  2. How does the command/method deal with empty/missing items (e.g. a,b,,c)? Missing items are iterated over.
  3. How does the command/method deal with a trailing comma? Trailing commas are treated like missing items.
  4. Can the command/method work of a list stored on a macro? Yes.
  5. Is each iteration evaluated globally or on a local group? (i.e., if a command is defined during an iteration, does the command survive the loop?) Yes.
  6. Do items get expanded or somehow mangled before being processed? Don't think so on mangling, not sure on expansion.
  7. What about lists where items are key=value or some other kind of pairs? No difference, any items can be separated by commas.
  8. Does the method work for lists separated with something other than a comma? Only commas are considered separators.
  9. Any other thing I should know before deciding to use this method? No idea. I assume every other option is more full-featured, and this one is the least common denominator.
2
11

Presenting the powerful pgffor!

How to use it

pgffor is part of the pgf-bundle and may be familiar to people who already used TikZ. The package defines the \foreach macro, which allows you to iterate over comma separated lists. It is available to nearly all flavours of TeX.

\usepackage{pgffor} % LaTeX
\input pgffor.tex   % plain TeX
\usemodule[pgffor]  % ConTeXt

Usage example

The usage and the several options to \foreach are described in section 56 on page 504 of the TikZ 2.10 manual. Here is one example from the manual, which outputs the elements of the list one by one in square brackets.

\foreach \x in {1,2,3,0} {[\x]}

Answering the questions

  • How does the command/method deal with whitespace around items?

Whitespace is ignored, unless you wrap your item in curly braces. Thus

\foreach \i in { a , b } != \foreach \i in {{ a },{ b }}
  • How does the command/method deal with empty/missing items? (e.g. a,b,,c)

For missing items the loop body is executed, but the loop variable is empty (see MWE)

  • How does the command/method deal with a trailing comma?

A trailing comma is handled like a missing element.

  • Can the command/method work of a list stored on a macro?

Yes and no. You can use a macro instead of a list of elements, but you cannot use a macro inside a list of elements.

While this works

\def\mylist{1,2,3}
\foreach \i in \mylist {...}

this in contrast, will handle the macro as a single element (i.e., it is not expanded while reading this list)

\def\mylist{1,2,3}
\foreach \i in {0,\mylist,4} {...}
  • Is each iteration evaluated globally or on a local group? (i.e., if a command is defined during an iteration, does the command survive the loop?)

The loop body is grouped. You can of course still make macros defined inside \global.

  • Do items get expanded or somehow mangled before being processed?

If an item is a macro, it gets expanded. If an item is a mathematical statement, you can use the evaluate option (see manual) to process the expression.

  • What about lists where items are key=value or some other kind of pairs?

You can define arbitrarily long tuples by separating the items with /.

\foreach \x/\y/\z in {0/0/0, 1/0/0} {...}
  • Does the method work for lists separated with something other than a comma?

No. Comma is the one and only separator.

A longer example

Use any plain TeX engine, e.g. pdftex.

\nopagenumbers% for cropping
\input pgffor.tex
\def\mylist{f,g,h}
Lorem ipsum
% Showcase for preserving whitespace, missing elements, trailing commas and a macro as an item
\foreach \i in { { a } , b , {c,d} ,, e , \mylist ,} {
    \item{$\bullet$} \i
}\par
dolor sit amet
% iterating over a macro and defining a macro inside
\foreach \i in \mylist {
    \def\lastitem{\i}
    \expandafter\gdef\expandafter\glastitem\expandafter{\i}
    \item{$\bullet$} \i
}\par
consectetur, adipisci velit

{\tt\string\lastitem=\meaning\lastitem}

{\tt\string\glastitem=\meaning\glastitem}
% tuples
\foreach \i/\j in {a/1, b/2, c/3} {
    \item{\i} \j
}
\bye

enter image description here

10

This is using the under-development tabstackengine package, first introduced here at Writing a table with equally spaced columns, based on the widest column (source code available at Measuring align).

The package extends the stackengine package by adding tabbing capability. This answer, Can I tab inside of align environment?, gives some of the syntax of the package. I apologize that I have been lax in completing the package and getting it out the door, so the only way to see it in action is by searching this site for tabstackengine.


One of the nice bonuses of tabstackengine is that it gives you access to its parser. By default, the tabbing delimiter is a &. However, it can be reset to a , for the purposes of this question. It provides \readTABrow{ID}{tab-separated-string} for digesting a list and assigning it an ID. It also provides \TABcells{ID} to tell you how many items are in the list named ID, and \TABcell{ID}{column number} to provide the element for the column in question from the ID'ed row.

Iterating over it, as the OP asks, is merely a question of using the etoolbox \whileboolexpr facility to process the information.

Method Highlights:

  1. Leading/trailing spaces are stripped, when using the high-level macro \TABcell{}{}; however...

  2. spaces can be preserved by using a lower level syntax, such as \csname TABX\ident X\romannumeral 3\endcsname for item 3 of dataset \ident.

  3. Trailing comma creates a new blank element for the last data item.

  4. Null cells are kept and given null value.

  5. Tabbing delimiter is user definable, including a space token.

  6. data is not expanded and can be accessed in its raw form; however, a processed form of the unexpanded data is also available in which leading/trailing spaces are removed and optionally placed in an \ensuremath wrapper (the latter occurs when \TABstackMath is invoked in advance).

  7. Because the method stores the parsed data in unique identifiers for later recall, it conveniently allows multiple datasets to be processed and combined after the fact (this is a major strength of the approach). This is, to the core, what tabstackengine was designed to do: it reads and digests arrayed text, row by row, but outputs it by column by column, in the form of adjacent stacks.

\documentclass[12pt]{article}
\usepackage[T1]{fontenc}
\usepackage{tabstackengine}
\usepackage{etoolbox}
\parskip 1ex
\parindent 0ex
\begin{document}
\TABstackMath
\def\x{1,2,   3, 4, 5,x,,8,\textbf{9},}
\def\ident{mydata}
Here is the data: \fbox{\x} identified with the tag \ident

\setstackTAB{,}
\def\readdata{\readTABrow{\ident}}
\expandafter\readdata\expandafter{\x}
\TABcells{\ident} cells in \ident

For dataset \ident, the cell data for cells 3, 4, and 9 are
:\TABcell{\ident}{3}:\TABcell{\ident}{4}:\TABcell{\ident}{9}:

Spaces can be preserved by calling on low-level macro.  
Item 3 is :\csname TABX\ident X\romannumeral 3\endcsname:

Here is a loop through \ident, showing raw and processed items:\\
\newcounter{index}
\setcounter{index}{0}
\whileboolexpr{test {\ifnumless{\theindex}{\TABcells{\ident}}}}{%
  \stepcounter{index}%
  :\csname TABX\ident X\romannumeral\theindex\endcsname%
  :\TABcell{\ident}{\theindex}:\\%
}

Here is \textbackslash TABcell\{\textbackslash ident\}\{9\} detokenized:\\
\detokenize\expandafter{\TABcell{\ident}{9}}

Here is the lowlevel macro \textbackslash TABXmydataXix detokenized:\\
\detokenize\expandafter{\TABXmydataXix}
\end{document}  

enter image description here

0
8

The xinttools package provides various loops, and among them there is \xintFor which iterates over a comma separated lists of items (possibly represented as macro). The package can be used either with Plain Tex:

\input xinttools.sty

or with LaTeX

\usepackage{xinttools}

Loading and usage is possible without the Plain format loaded as long as:

  1. for loading of the package: the % is of category code comment character, the \ is of category code escape character, digits have category code other, letters have category code letter,

  2. for usage of \xintFor: {, }, , and # all have their standard category code, and the \empty and \space macros their standard meanings.

The syntax is:

\xintFor #1 in {\mylist} \do {stuff with #1}

The #1 (or #2, ..., #9: this is to allow nesting with the iterated text using itself a further \xintFor) is treated as in the replacement text of a macro with parameter, thus if \list expands to, or is, abc, def, ghk then #1 will be abc then def then ghk not a macro expanding to these things.

When \xintFor is used inside a macro definition, the # must be doubled as usual: ##1. There will be no conflict if this macro once defined is used elsewhere, for example inside another \xintFor loop defined itself with a #1 or ##1.

The \xintFor is not expandable, that means one can not insert it inside an \edef. However it has been especially designed to be usable inside alignments such as a tabular.

\documentclass{article}
\usepackage{xinttools}

\begin{document}    
\begin{tabular}{ccc}
\hline
  \xintFor #1 in {A, B, C, D, E}\do {#1&\lowercase{#1}&\textbf{#1}\\}
\hline
\end{tabular}

\end{document}

tabular xinttools example

Although the loop survives the closing of groups possibly done by its replacement text (as is the case in the example above), it does not make by itself any global assignment.

  • How does the command/method deal with whitespace around items?

It removes them, both starting and trailing spaces (the case of multiple space tokens, of standard character code 32, is also handled). Additionally if the item was ...,<spaces>{item}<spaces>,..., one level of brace pair is removed (but spaces inside the braces are left untouched). In the special case ...,{stuff},.. of a braced item with no spaces to separate both commas from braces, stuff will also be stripped of starting and trailing spaces.

  • How does the command/method deal with empty/missing items? (e.g. a,b,,c)

A missing item is treated as empty item. That is #1 is empty. The iteration is not skipped.

  • How does the command/method deal with a trailing comma?

A trailing comma means a trailing empty item. One can use \xintifForLast to abort the loop if one knows the list has such an empty ending item!

  • Can the command/method work of a list stored on a macro?

Yes. \xintFor #1 in \list \do, \xintFor #1 in {\list} \do, \xintFor #1 in {a,b,c} \do are equivalent (if the macro \list expanding to a,b,c has arguments, braces around it and its arguments must be used). And \list is expanded exactly once. With in {\x,a,b,c} as input, \x will be expanded, and may for example transform the list into {d,e,f,a,b,c}. Use {{\x},a,b,c} or { \x,a,b,c} to prevent this expansion of \x (in the latter form, the initial space is gobbled and serves only to prevent expansion of \x). For all other items except the first, there will never be any expansion.

  • Is each iteration evaluated globally or on a local group? (i.e., if a command is defined during an iteration, does the command survive the loop?)

No global assignments is made. No local group is created. Yes, if a command is defined during the iteration it will survive the loop: assuming the iteration text does not itself close groups!

  • Do items get expanded or somehow mangled before being processed?

Spaces at the starts and ends are stripped, and exactly one level of braces is removed. The first item will get expanded if it is not protected by braces or an intial space and if the list is not given as macro but directly.

  • What about lists where items are key=value or some other kind of pairs?

\xintFor only looks at commas, thus the #1 will be key=value.

  • Does the method work for lists separated with something other than a comma?

No, \xintFor is only for comma separated lists (with a standard category code comma). \xintFor converts the comma separated list to a sequence of braced stripped items using \xintCSVtoList and then treats this sequence with \xintFor*.

  • Any other thing I should know before deciding to use this method?

The comma separated items may contain \par or empty lines.

\xintFor has been designed to:

  1. be usable within alignments,
  2. allow nesting (up to nine levels),
  3. not use macros to encapsulate the items but manipulate them as usual macro parameters,
  4. not create groups.

The xint documentation provides some more information.

The package does not provide facilities to manipulate comma separated lists per se, only to use them.

Expandable manipulations are possible via first converting with \xintCSVtoList the comma separated list to a sequence of braced items ({abc}{123}...) and then using \xintApply or \xintApplyUnbraced which apply a given one-argument macro in turn to each item of the sequence. Also, \xintNthElt accesses the element of given index (either from the start or end, if negative), but the package is lacking currently facilities to cut and glue such sequences of braced items, perhaps this will be added.

The iteration done by \xintFor can be prematurely interrupted using \xintBreakFor and \xintBreakForAndDo inside the replacement text, for example in the YES or NO branch of some LaTeX-like test, the package own tests, or the etoolbox tests, or some ifthen test (non-expandability not being an issue).

\xintifForFirst {stuff if first iteration}{stuff if not first}, \xintifForLast {stuff if last iteration}{stuff is not last iteration}, allow special treatment of the first or last iterations; but to count iterations the user is invited to define a counter or \count and update it as part of the loop (if used in an alignment, presumably the \count assignments will need to be made global).

1
  • How is this answer principally different from this answer? tex.stackexchange.com/a/326801/13173 It would be nice to get a summary of main commands what it proposes for the basic task. There are so much pieces of information that it is difficult for me to get the big picture. Maybe, some sort of table or list for the summary. May 26, 2017 at 17:50
8

listofitems provides a pgffor-like interface for iterating over a list:

enter image description here

\documentclass{article}

\usepackage{listofitems}

\begin{document}

\setsepchar{,}% Parse-character = ','
\readlist\mylist{This,is,a,list,of,items}
\begin{enumerate}
  \foreachitem\word\in\mylist{\item \word}
\end{enumerate}

\end{document}

Specifically,

  1. How does the command/method deal with whitespace around items?

    \readlist keeps the surrounding white spaces while \readlist* (the starred version) removes them.

  2. How does the command/method deal with empty/missing items? (e.g. a,b,,c) How does the command/method deal with a trailing comma?

    The "option" \ignoreemptyitems disregards empty items, including trailing commas, while \reademptyitems takes them into account.

  3. Can the command/method work of a list stored on a macro?

    Yes. It works well with a list stored in a macro.

  4. Is each iteration evaluated globally or on a local group? (i.e., if a command is defined during an iteration, does the command survive the loop?)

    Macros defined in the processing are local, unless they're defined via \gdef. the parsed list can be made global with \greadlist. The elements themselves can be made global by assigning them to a macro with the command \itemtomacro.

  5. Do items get expanded or somehow mangled before being processed?

    Items are fully expanded.

  6. What about lists where items are <key>=<value> or some other kind of pairs?

    They are not really handled, even though = can be uses as a separator.

  7. Does the method work for lists separated with something other than a comma?

    listofitems can use a huge variety of separators, including white spaces, other symbols (excluded are catcodes 6, 14 and 15), words and even commands. It also accept OR-ed separators like \setsepchar{,||;}.

  8. Any other thing I should know before deciding to use this method?

    It handles nested lists with distinct separators very easily.

3

Since this question was lacking a luatex solution, I've always wanted to give it luatex a proper try and this seemed to be manageable problem, here is my lualatex solution.

\documentclass{article}

\usepackage{luacode}
\newcommand\makeenumerate[1]{
\begin{enumerate}
\luaexec{
    function split(s, delimiter)
        result = {};
        for match in (s..delimiter):gmatch("(.-)"..delimiter) do
            table.insert(result, match);
        end
        return result;
    end
    list = "#1"
    table = split(list, ",")
    for k,v in pairs(table) do
        tex.print("\\item \\fbox{" .. v .. "}")
    end
}
\end{enumerate}
}
\begin{document}
\makeenumerate{a, b, c, d, e}
\end{document}
3

There still seems to be missing a fully expandable plain TeX version of list iteration. So here is a functional style \map macro similar to the lazy-lists version provided by morbusg.

The syntax is simply \map\func{...} where {...} is the comma-separated list of items. \func is a worker macro that expects exactly one argument and is called for each item in the list with item text passed as argument to that macro in braces. \map\func{a,b,c} expands to \func{a}\func{b}\func{c}, for example.

Here's the definition with some examples:

\catcode`@=11
\def\map#1#2{%
    \ifx\omit#2\omit\else
        \map@#1\map@#2,\endmap@
    \fi
}
\def\map@#1\map@#2,#3\endmap@{%
    #1{#2}%
    \ifx\omit#3\omit\else
        \map@#1\map@#3\endmap@
    \fi
}
\catcode`@=12

\def\parens#1{(#1)}
\def\test#1{[#1]: \map\parens{#1}\par}

\tt
\test{}
\test{a}
\test{a,b}
\test{, a,b , c ,,     ,{,},}
\def\list{a,b,c}
\test\list
\test{a,\map\parens{b1,b2,b3},c}
\expandafter\string\csname\map\parens{a,b,c}\endcsname

\bye

enter image description here

How does the command/method deal with whitespace around items?
All leading and trailing spaces are preserved.

How does the command/method deal with empty/missing items?
Empty items are allowed and have an empty expansion.

How does the command/method deal with a trailing comma?
A trailing comma is considered an empty last item.

Can the command/method work of a list stored on a macro?
If a list is stored in a macro and that macro is passed to the function like \map\func\mylist (or as an item), it is not expanded but treated as a single item list.

Is each iteration evaluated globally or on a local group?
There is no grouping involved, so all definitions or assignments are local to their respective level the \map macro is called in.

Do items get expanded or somehow mangled before being processed?
No extra expansion is done for the items.

What about lists where items are key=value or some other kind of pairs?
Key-value pairs are not provided.

Does the method work for lists separated with something other than a comma?
The macro could be easily modified to other kinds of separators that are allowed as macro parameter delimiters.

Any other thing I should know before deciding to use this method?
The macro is pretty simple but should work quite well in most situations. Most notably, it works in all expansion-only contexts. You can even nest calls of \map in list items. If the separator should be used in the item text, it has to be put in braces.

2

My solution is similar to siracusa's but the whole list is not read again in each loop cycle. This is pure TeX solution based o TeX primitives and it is expandable.

We create expandable \apply macro which can be used as \apply\macro{comma,list}. and it expands to \macro{comma}\macro{list}.

\def\apply#1#2{\applyA#1#2,\applyA,}
\def\applyA#1#2,{\ifx\applyA#2\empty\else #1{#2}\afterfi{\applyA#1}\fi}
\def\afterfi#1#2\fi{\fi#1}

%test:
\def\p#1{(#1)}
\apply\p{1,2,3}  % expands to (1)(2)(3)

Unlike siracusa's solution \apply\macro{} expands to \macro{} which is more conceptual because ,, in the list expands to \macro{} too.

Using \foreach from OpTeX, we can define expandable \apply macro in more compact form:

\useOpTeX
\def\apply#1#2{\foreach#2,\do ##1,{#1{##1}}}
1

For completeness, I threw in an xstring based solution. Note that xstring does not ignore the spaces after the commas.

\documentclass{article}
\usepackage{xcolor}
\usepackage{xstring}

\def\thecolors{black, blue, brown, cyan, darkgray, gray, green, lightgray, 
 lime, magenta, olive, orange, pink, purple, red, teal, violet, white, yellow}

\newcounter{mycount}
\begin{document}
\StrCount{\thecolors}{,}[\total]% number of entries-1
\setcounter{mycount}{0}%
\loop\ifnum\value{mycount}>\total\relax
\else
  \ifnum\value{mycount}=0\relax
    \StrBefore{\thecolors}{,}[\result]%
  \else
    \ifnum\value{mycount}=\total\relax 
      \StrBehind[\total]{\thecolors}{, }[\result]%
    \else 
      \StrBetween[\themycount,\the\numexpr\value{mycount}+1]{\thecolors}{, }{,}[\result]%
    \fi
  \fi
  \textcolor{\result}{Some text including the string \result.}\par
  \stepcounter{mycount}%
\repeat

\end{document}

You must log in to answer this question.

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