27

Is there a way for handling the last item in a \foreach-list in a special way. An example might illustrate an application for that:

\documentclass{minimal}
\usepackage{pgffor}
\begin{document}
\verb|\foreach \n in {1,...,5}{\n --}| leads to
\foreach \n in {1,...,5}{\n --} better would be
1--2--3--4--5 without the last --

A bad workaround: \foreach \n/\l in {1/--,2/--,3/--,4/--,5/{}}{\n\l}
or \foreach \n in {1--,2--,3--,4--,5}{\n}

% unfortunatly
% \foreach \n/\l in {1/--,...,4/--,5/{}}{\n\l}
% or \foreach \n in {1--,...,4--,5}{\n}
% doesn't work
\end{document}
3
  • 1
    It is often easier to test for the first element (and print -- in front of the others). Use the count option to see where in the list you are.
    – Caramdir
    Commented Apr 20, 2011 at 3:27
  • @Caramdir: Your suggestion promises to be more general. Commented Apr 20, 2011 at 3:51
  • @Cardamdir: Good idea but it doesn’t seem to work: Could you try \foreach \n [count=\ni] in {a,...,z} {$\n_\ni+$} which causes an error in my test. Even the pgfmanual-example doesn’t work. I’m using TL2010 and i didn’t find updates for PGF today (\listfiles: pgffor.sty 2010/03/23 v2.10 (rcs-revision 1.18) and pgfrcs.sty 2010/10/25 v2.10 (rcs-revision 1.24)). PS: Maybe you can post your comment as an answer, so I can vote for you ;-)
    – Tobi
    Commented Apr 20, 2011 at 10:06

4 Answers 4

19

Using the count option of \foreach, one can get a counter that stores the position in the list. Since the number of elements in the list might be unknown, it is usually easier to check for the first element instead of the last. Here is an example. (Note that in v2.10, tho count option does not work when only including pgffor, as it is missing \pgfmathparse. So you need to include pgfmath as well (which then complained about missing pgfkeys, so I just included everything in the example).)

\documentclass{minimal}
\usepackage{pgf,pgffor}

\begin{document}
\foreach \n [count=\ni] in {a,...,e} {% Comment signs at end of line to avoid
    \ifnum\ni=1%                        unwanted whitespace.
        \n%
    \else%
        --\n%
    \fi%
}
\end{document}

Alternatively, you could simply write

a%
\foreach \n in {b,...,e} {--\n}
1
13

Subsection The dots notation of Section 56. Repeating Things: The Foreach Statement of pgfmanual v2.10 (pp.504--505) has a vast variety of examples. Among them one that allows you to do the following:

\documentclass{minimal}
\usepackage{pgffor}
\begin{document}
\foreach \n in {1--, ...--, 4--, 5}{\n}
\end{document}
2
  • 1
    or 1\foreach \n in {--2,--...,--5}{\n} Commented Jun 14, 2012 at 13:37
  • @AlainMatthes, using your approach we get first element being torn appart from the whole chain what I consider to be much less readable. I think it's better to put 1 inside curly braces. Once this is done you get almost the same solution as I have suggested. Commented Dec 16, 2015 at 11:49
8

Although the other answers here provide solutions to accomplish the task at hand, they don't really address the requirements in the title of the question. I came across this question when working a solution to another question, but was disappointment that there was no solution here showing how to do special processing for the last member of a list. Hence am including it here in case someone else really needs it. And perhaps someone with a more elegant solution will be encouraged to post one.

I have to admit it is not very elegant, but just brute force approach to count the number of members at the beginning of the list processing, and keep a count to see which list member we are at. This allows one to apply different formatting for the first, middle, and last list member. So with \ColorListMembers{first,second,third,fourth} I can obtain:

enter image description here

Notes:

  • I am using the xstring package for the numerical comparison as I prefer it's syntax, but this could easily be adapted to not require that package.

References:

Code:

\documentclass[border=2pt]{standalone}
\usepackage{xcolor}
\usepackage{pgffor}
\usepackage{xstring}


% Specify the formatting of each of the list members
\newcommand*{\FormatFirstListMember}[1]{\textcolor{magenta}{\texttt{#1}}}%
\newcommand*{\FormatMiddleListMember}[1]{\textcolor{blue}{\texttt{#1}}}%
\newcommand*{\FormatLastListMember}[1]{\texttt{#1}}%


\newcounter{TotalNumberOfListMembers}%
\newcommand{\SetTotalNumberOfListMembers}[1]{%
    \setcounter{TotalNumberOfListMembers}{0}%
    \foreach \member in {#1} {%
        \stepcounter{TotalNumberOfListMembers}%
    }%
}%


\newcounter{CurrentListMemberCount}%
\newcommand*{\ColorListMembers}[1]{%
    \SetTotalNumberOfListMembers{#1}%
    \setcounter{CurrentListMemberCount}{0}%
    \foreach \member in {#1} {%
        \stepcounter{CurrentListMemberCount}%
        \IfEq{\the\value{CurrentListMemberCount}}{1}{%
            \FormatFirstListMember{\member}%
        }{%
            \IfEq{\the\value{CurrentListMemberCount}}{\the\value{TotalNumberOfListMembers}}{%
                --\FormatLastListMember{\member}%
            }{%
                --\FormatMiddleListMember{\member}%
            }%
        }%
    }%
}%

\begin{document}
    \ColorListMembers{first,second,third,fourth}
\end{document}
6

A mild variant on Caramdir's answer (more or less equivalent to the alternative):

\documentclass{article}
\usepackage{pgffor}
\newcommand*\minusminus[1]{--#1}
\newcommand*\initial[1]{#1\global\let\doit=\minusminus}
\let\doit=\initial
\begin{document}
 \foreach \n in {1,...,5} {%
  \doit{\n}%
 }
\end{document}

The \doit macro redefines itself after the first call and thereafter adds the en dash. This is sort of evil, but I had in mind something like calling a function pointer with a target that gets replaced, which is less evil.

Edit: And here is a solution that actually treats the last item specially.

\documentclass{article}
\usepackage{pgffor,etoolbox}
\newcommand*\last{}
\newcommand*\storage{}
\newcommand*\doit[1]{%
 \xdef\storage{\expandonce\storage\noexpand\transform{\last}}%
 \xdef\last{#1}%
}
\newcommand*\finalize{\storage\last}
\newcommand*\minusminus[1]{#1--}
\newcommand*\initial[1]{#1\global\let\transform=\minusminus}
\let\transform=\initial
\begin{document}
 \foreach \n in {1,...,5} {
  \doit{\n}
 }
 \finalize
\end{document}

What's happening is that each iteration of \doit saves its argument and then adds the previous argument (which was saved) to the \storage list, along with a directive to transform it. In the last iteration, the argument is saved but never added, which is done in \finalize, but without the transformation.

Note the appearance of the "Carlisle wand", as Ahmed Musa described it, even in this method. The first (that is, zeroth) \last should be ignored. You can even double this up (that is, have \transform redefine itself to something else that then redefines itself) to get the first item, or indeed any specific number of initial items, to behave differently. And of course, you can do something to the last item as well, rather than nothing, as I did.

As far as I can tell, it is mathematically necessary to have the \finalize step. You can't know you are at the end of the list until moving past it, so every item has to be processed late, and in particular the last item has to be processed "by hand". This is as automated as I can get it.

Edit: In response to Peter Grill's comment: there is actually a way to do \AtEndForeach, though it's not pretty. I don't know why there is no mechanism to set this, since it is a no-op in the code:

\makeatletter
 \let\pgffor@afterhook=\finalize
\makeatother

I suppose you could make this a macro:

\makeatletter
\newcommand\AtEndForeach{%
 \def\pgffor@afterhook
}
\makeatother

\AtEndForeach{\finalize}
\foreach \n in {1,...,5} {
 \doit{\n}
}

Note that there is a \pgffor@atendforeach, but that does have a purpose; it seems to finish up the operation of the remember key option. You want the after hook.

3
  • There is no evil in that trick: it's age-old, age-long Carlisle wand. Even now he's fond of it.
    – Ahmed Musa
    Commented Jun 17, 2012 at 8:01
  • Now if there just an \AtEndForeach{\finalize} hook, this would be perfect. Commented Jun 17, 2012 at 18:19
  • @Peter: Now there is :)
    – Ryan Reich
    Commented Jun 17, 2012 at 18:43

You must log in to answer this question.

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