About
Shop
LaTeX
Software
Books
Gallery
News
Contact
Blog
Settings
Account
Latest news 2024-10-15: New blog post: Tales for Our Times Book Launch.


2.7.5 Iteration Tips and Tricks

This section describes some advanced techniques that you may or may not need to know, so feel free to skip it.

Recall from §2.7.1 Iterating Through a Database I had:

\DTLforeach*{people}{\Surname=surname}{\Surname. }

which, when using the sample people.csv file, produced:

Parrot. Canary. Zebra. Arara. Duck. Canary.

Suppose instead I wanted to produce:

Parrot; Canary; Zebra; Arara; Duck; Canary.

If I try:

\DTLforeach*{people}{\Surname=surname}{\Surname; }.

I get:

Parrot; Canary; Zebra; Arara; Duck; Canary; .

(there's an unwanted semi-colon and space before the terminating full stop) and if I try:

\DTLforeach*{people}{\Surname=surname}{; \Surname}.

I get:

; Parrot; Canary; Zebra; Arara; Duck; Canary.

(The unwanted semi-colon and space are now at the start.)

Neither or these are quite right. Here's a way of achieving the desired output:

\newcommand{\surnamesep}{%
  \renewcommand{\surnamesep}{; }%
}%
\DTLforeach*{people}{\Surname=surname}{\surnamesep\Surname}.

This may look a bit weird at first sight, but here's how it works:

Be careful if the contents of ⟨body⟩ are localised (for example, if it's in a tabular environment, as in Example 6) since \renewcommand only has a local effect. Instead you can use TeX's \gdef command described in §2.1.1 Macro Definitions:

\newcommand{\surnamesep}{%
  \gdef\surnamesep{; }%
}%
\DTLforeach*{people}{\Surname=surname}{\surnamesep\Surname}.

Alternatively you can do a global \let after you've redefined the command:

\newcommand{\surnamesep}{%
  \renewcommand\surnamesep{; }%
  \global\let\surnamesep\surnamesep
}%
\DTLforeach*{people}{\Surname=surname}{\surnamesep\Surname}.

The datatool package defines the command:

\DTLiffirstrow{true}{false}

designed for use within the ⟨body⟩ of \DTLforeach so I could just do:

\DTLforeach*
 {people}% database
 {\Surname=surname}% assignment list
 {\DTLiffirstrow{}{; }\Surname}.

However, the technique described above can be used in more general situations. For example, suppose I want to use etoolbox's \docsvlist, described in §2.7.2 Iterating Over a Comma-Separated List, I could do:

\newcommand{\surnamesep}{%
  \renewcommand{\surnamesep}{; }%
}%
\renewcommand\do[1]{\surnamesep#1}%
\docsvlist{Parrot,Canary,Zebra,Arara,Duck}.

which produces:

Parrot; Canary; Zebra; Arara; Duck.

The datatool package also defines the command:

\DTLiflastrow{true}{false}

As with \DTLiffirstrow, this command is designed for use within the ⟨body⟩ of \DTLforeach (or \DTLforeach*). For example:

\DTLforeach*
 {people}% database
 {\Surname=surname}% assignment list
 {%
  \DTLiffirstrow{}{\DTLiflastrow{ and }{, }}%
  \Surname
 }.

produces:

Parrot, Canary, Zebra, Arara, Duck and Canary.

So how can we do the equivalent for a general comma-separated list rather than using \DTLforeach? One possible method is described in the example below.

Example 7. List of Names

This is slightly more complicated but it uses a similar technique to earlier:

\newcommand*{\surnamesep}{}%
\newcommand*{\lastsurname}{}%
\newcommand*{\prelastsurname}{}%
\renewcommand*{\do}[1]{%
  \surnamesep
  \lastsurname
  \renewcommand{\lastsurname}{%
    \renewcommand{\surnamesep}{, }%
    \renewcommand{\prelastsurname}{ and }%
    #1%
  }}%
\docsvlist{Parrot,Canary,Zebra,Arara,Duck}%
\prelastsurname \lastsurname

This produces:

Parrot, Canary, Zebra, Arara and Duck.

Here's how it works:

  1. On the first iteration (\do{Parrot}) \surnamesep and \lastsurname do nothing. Then \lastsurname is redefined via:
    \renewcommand{\lastsurname}{%
      \renewcommand{\surnamesep}{, }%
      \renewcommand{\prelastsurname}{ and }%
      Parrot%
    }

    (The #1 has been replaced with the argument passed to \do.) So far nothing has been displayed.

    If this was the only item in the list, the loop would end and then:

    • \prelastsurname would be done, but this is currently nothing.
    • \lastsurname would be done, which is now set to redefine a couple of commands that are no longer needed (\surnamesep and \prelastsurname) and then displays “Parrot”.
    Therefore, if the list only has one element, it just displays that element. However, since the list has more than one element, we have nothing displayed and we move on to the next item in the list.

  2. On the second iteration (\do{Canary}) \surnamesep still does nothing but \lastsurname now does:
      \renewcommand{\surnamesep}{, }%
      \renewcommand{\prelastsurname}{ and }%
      Parrot%
    So \surnamesep gets redefined to do , (that is a comma followed by a space) and \prelastsurname gets redefined to do and. After these redefinitions, the word “Parrot” is then displayed. Next \lastsurname is redefined via:
    \renewcommand{\lastsurname}{%
      \renewcommand{\surnamesep}{, }%
      \renewcommand{\prelastsurname}{ and }%
      Canary%
    }

    Therefore, by the end of the second iteration, the only text displayed is “Parrot”. If this happened to be the last item in the list, the loop would end and then:
    • \prelastsurname would be done, which now displays “ and ”.
    • \lastsurname would be done, which redefines some commands we no longer need (\surnamesep and \prelastsurname) and then displays “Canary”.
    Therefore, if the list only contained Parrot,Canary then just the text “Parrot and Canary” would be displayed. However, as there are still more items left, the only text displayed so far is “Parrot”.

  3. On the third iteration (\do{Zebra}) \surnamesep now displays a comma followed by a space and \lastsurname does:
      \renewcommand{\surnamesep}{, }%
      \renewcommand{\prelastsurname}{ and }%
      Canary%
    Then \lastsurname is redefined via:
    \renewcommand{\lastsurname}{%
      \renewcommand{\surnamesep}{, }%
      \renewcommand{\prelastsurname}{ and }%
      Zebra%
    }

    So we have thus far produced the text “Parrot, Canary”. If this happened to be the last item in the list, the loop would end and then:
    • \prelastsurname would display “ and ”
    • \lastsurname would redefine some commands that we no longer need (\surnamesep and \prelastsurname) and then display “Zebra”.
    The remaining iterations follow the same pattern as this third iteration.

If you plan to use this method more than once, you might prefer to define a new command. For example:

% set up defaults so we don't get an error
% when we try to redefine these commands
\newcommand*{\surnamesep}{}%
\newcommand*{\lastsurname}{}%
\newcommand*{\prelastsurname}{}%
% define the new command to process a list of names:
\newcommand*{\displaynames}[1]{%
  % initialise:
  \renewcommand*{\surnamesep}{}%
  \renewcommand*{\lastsurname}{}%
  \renewcommand*{\prelastsurname}{}%
  % set up list handler:
  \renewcommand*{\do}[1]{%
    \surnamesep
    \lastsurname
    \renewcommand{\lastsurname}{%
      \renewcommand{\surnamesep}{, }%
      \renewcommand{\prelastsurname}{ and }%
      ##1%
    }}%
  \docsvlist{#1}%
  \prelastsurname \lastsurname
}

Since we have nested definitions of commands that take a parameter we need to be careful how we reference the parameter. In the above #1 refers to the argument of \displaynames (the outer command) and ##1 refers to the argument of \do (the inner command).

In this case, it's better to define your own handler macro and use \forlistloop instead of \docsvlist:

% set up defaults so we don't get an error
% when we try to redefine these commands
\newcommand*{\surnamesep}{}%
\newcommand*{\lastsurname}{}%
\newcommand*{\prelastsurname}{}%
% define the handler macro:
\newcommand*{\dodisplayname}[1]{%
  \surnamesep
  \lastsurname
  \renewcommand{\lastsurname}{%
    \renewcommand{\surnamesep}{, }%
    \renewcommand{\prelastsurname}{ and }%
    #1%
  }}%
% define the new command to process a list of names:
\newcommand*{\displaynames}[1]{%
  % initialise:
  \renewcommand*{\surnamesep}{}%
  \renewcommand*{\lastsurname}{}%
  \renewcommand*{\prelastsurname}{}%
  % iterate through list:
  \forcsvlist{\dodisplayname}{#1}%
  % finish off:
  \prelastsurname \lastsurname
}

This removes one of the nested redefinitions and the need to use ##1 instead of #1.

You can download or view a complete document.

Exercise 5. Oxford Comma

Some people always use the Oxford comma, some people never use it, and some people only use it where its lack would cause ambiguity. The purpose of this exercise is not to engage in a heated debate over whether or not it should be used. It's simply an exercise in iteration techniques. For those who've never heard of the Oxford comma, it's a comma that's placed after the penultimate item in a list of three or more items before the “and”. For example: Parrot, Canary, and Zebra.

For this exercise, see if you can adapt the definition of \displaynames from the end of the previous example so that it uses the Oxford comma. To test it:

\displaynames{Parrot,Canary,Zebra,Arara,Duck}

\displaynames{Parrot,Canary,Zebra,Arara}

\displaynames{Parrot,Canary,Zebra}

\displaynames{Parrot,Canary}

\displaynames{Parrot}

should produce:

Parrot, Canary, Zebra, Arara, and Duck

Parrot, Canary, Zebra, and Arara

Parrot, Canary, and Zebra

Parrot and Canary

Parrot End of Image.


You can download or view the solution to this exercise.


This book is also available as A4 PDF or 12.8cm x 9.6cm PDF or paperback (ISBN 978-1-909440-07-4).

© 2015 Dickimaw Books. "Dickimaw", "Dickimaw Books" and the Dickimaw parrot logo are trademarks. The Dickimaw parrot was painted by Magdalene Pritchett.

Terms of Use Privacy Policy Cookies Site Map FAQs