7
\documentclass[a4paper]{article}
\usepackage{tikz,etoolbox}
\usepackage{geometry}
\geometry{showframe}
\geometry{left=1cm,right=1cm,top=1cm,bottom=1cm}
\parindent0pt


\begin{document}
Method 1: %by \docsvlist from etoolbox
% This does not work. How make it work?
\def\lst{}
\appto\lst{1in,1pt,}
\appto\lst{2in,2pt,}
\appto\lst{3in,3pt,}
% length with unit in is for rule width, length with unit pt is for rule height
\renewcommand{\do}[2]{\rule{#1}{#2}\\}
\docsvlist\lst
%----------------------------------------

\def\aaa{1,2,3,}
\foreach \x in \aaa{\x}
% The above \foreach works well thought there is an extra comma in the list of \aaa.

Method 2:\\ % by \foreach from tikz
\def\lst{}
\appto\lst{1in/1pt,}
\appto\lst{2in/2pt,}
\appto\lst{3in/3pt,}
\lst
% The following \foreach gives error. I think this is due to the last comma in \lst.
% So, how to solve this problem?
\foreach \x/\y in \lst{\rule{\x}{\y}}

\end{document}

6 Answers 6

5

You can use etoolboxs \listadd with a slightly different input syntax:

\documentclass{article}
\usepackage{etoolbox}

\begin{document}

\newcommand\lst{}
\listadd\lst{{1in}{1pt}}
\listadd\lst{{2in}{2pt}}
\listadd\lst{{3in}{3pt}}

\renewcommand\do[1]{\rule#1\par}
\dolistloop{\lst}

\newcommand\handler[2]{\hspace{#1} \rule{1in}{#2}\par}
\renewcommand\do[1]{\handler#1\par}
\dolistloop{\lst}

\end{document}
7
  • Thank you @cgnieder! In this way(\listadd), how to see the content of \lst?
    – lyl
    Feb 26 at 9:38
  • @lyl you can do \show\lst or \texttt{\meaning\lst} if you want to inspect the list
    – cgnieder
    Feb 26 at 9:41
  • In my example, both arguments of \rule are next to each other, so \listadd\lst{{1in}{1pt}} put them together. However, in many cases, both arguments(or both loop variables) are seperated. For example \hspace{#1} \rule{1in}{#2}. How to achieve this by etoolbox?
    – lyl
    Feb 26 at 9:55
  • @lyl see my edited answer
    – cgnieder
    Feb 26 at 9:58
  • \listeadd\lst{{\dimexpr 2in-1in}{.5in}} \meaning\lst, why does the length given by\dimexpr not expanded by listeadd?
    – lyl
    Feb 26 at 10:47
7

Indeed the issue is the extra comma at the end of the last list item. You can see that also in the simple loop when you print a character after each list item, which will print an extra character for the final, empty list item. This will fail \foreach because the last empty item cannot be parsed properly.

The solution is easy: remove the final comma, or not add it in the first place. It would be useful to determine during list construction that you are at the last item and in that case add just the item without the comma. If this is difficult then as a workaround you can remove the comma using \StrGobbleRight from the xstring package.

\documentclass[a4paper]{article}
\usepackage{tikz,etoolbox}
\usepackage{geometry}
\usepackage{xstring}
\geometry{showframe}
\geometry{left=1cm,right=1cm,top=1cm,bottom=1cm}
\parindent0pt


\begin{document}
\def\aaa{1,2,3,}
\foreach \x in \aaa{\x a}
% The above \foreach works well thought there is an extra comma in the list of \aaa.

Method 2:\\ % by \foreach from tikz
\def\lst{}
\appto\lst{1in/1pt,}
\appto\lst{2in/2pt,}
\appto\lst{3in/3pt,}
% remove 1 character from the right and store the result back in `\lst`
\StrGobbleRight{\lst}{1}[\lst]
\lst\\
% The following \foreach gives error. I think this is due to the last comma in \lst.
% So, how to solve this problem?
\foreach \x/\y in \lst{\rule{\x}{\y}\\}

\end{document}

Result:

enter image description here

3
  • The \lst is constructed by \appto automatically, so a comma has to be added each time. Then, in programming, how to remove the last comma of \lst?
    – lyl
    Feb 26 at 8:48
  • @lyl see edit to remove the comma automatically after constructing the list.
    – Marijn
    Feb 26 at 8:57
  • Many thanks for the method from xstring!
    – lyl
    Feb 26 at 10:15
5

You don't want to use the “naked” \appto, but to build your own list constructor, so you don't need to bother with commas.

\newcommand{\addtolist}[2]{%
  \ifdefvoid{#1}{\appto{#1}{#2}}{\appto{#1}{,#2}}%
}

Example:

\documentclass[a4paper]{article}
\usepackage{tikz,etoolbox}

\newcommand{\addtolist}[2]{%
  \ifdefvoid{#1}{\appto{#1}{#2}}{\appto{#1}{,#2}}%
}

\begin{document}

\addtolist\lst{1in/1pt}
\addtolist\lst{2in/2pt}
\addtolist\lst{3in/3pt}

\foreach \x/\y in \lst{\rule{\x}{\y}\par}

\end{document}

enter image description here

You may want an implementation independent on tikz and etoolbox.

The \lforeach command takes as first argument the name of a list (no backslash) and as second mandatory argument the code to apply to each list element. In the middle an optional argument sets a pattern to which the list elements must comply. If this argument is missing, the generic #1 is used.

Note that this is not executed in groups. It would be possible to allow nesting, but it becomes complicated for a proof of concept.

\documentclass[a4paper]{article}

\ExplSyntaxOn

\NewDocumentCommand{\lforeach}{mo+m}
 {
  \IfNoValueTF{#2}
   {
    \cs_set:Npn \__lyl_lforeach_do:w ##1 \q_stop { #3 }
   }
   {
    \cs_set:Npn \__lyl_lforeach_do:w #2 \q_stop { #3 }
   }
   \__lyl_lforeach_main:n { #1 }
 }

\NewDocumentCommand{\newlist}{m}
 {
  \clist_clear_new:c { l__lyl_lforeach_#1_clist }
 }

\NewDocumentCommand{\addtolist}{mm}
 {
  \clist_map_inline:nn { #2 }
   {
    \clist_put_right:cn { l__lyl_lforeach_#1_clist } { ##1 }
   }
 }

\cs_new_protected:Nn \__lyl_lforeach_main:n
 {
  \clist_map_function:cN { l__lyl_lforeach_#1_clist } \__lyl_lforeach_do:n
 }

\cs_new:Nn \__lyl_lforeach_do:n { \__lyl_lforeach_do:w #1 \q_stop }
\cs_new:Npn \__lyl_lforeach_do:w {}

\ExplSyntaxOff


\begin{document}

\newlist{lst}
\addtolist{lst}{1in/1pt}
\addtolist{lst}{2in/2pt, 3in/3pt}

\newlist{lsta}
\addtolist{lsta}{1in}
\addtolist{lsta}{2in, 3in}

\newlist{lstb}
\addtolist{lstb}{(1in;1pt)}
\addtolist{lstb}{(2in;2pt), (3in;3pt)}

\lforeach{lst}[#1/#2]{\rule{#1}{#2}\par}

\lforeach{lsta}{\rule{#1}{0.4pt}\par}

\lforeach{lstb}[(#1;#2)]{\rule{#1}{#2}\par}

\end{document}

enter image description here

You can also make your own commands for common structures; for example when the list items are of the form <a>/<b>, you can define

\newcommand{\lforeachslash}[2]{\lforeach{#1}[##1/##2]{#2}}

and

\lforeachslash{lst}{\rule{#1}{#2}\par}

would produce the same as \lforeach{lst}[#1/#2]{\rule{#1}{#2}\par}.

4
  • the "naked" \appto, what's the meaning, thank you@egreg.
    – lyl
    Feb 26 at 9:10
  • @lyl The “basic” \appto.
    – egreg
    Feb 26 at 9:44
  • Many thanks for this solution and the idea \addtolist!!
    – lyl
    Feb 26 at 10:14
  • @lyl I also provided a solution with no need of \x and \y.
    – egreg
    Feb 26 at 14:09
3

When OpTeX is used then \foreach (with different syntax than from tikz) is ready to use:

\useOpTeX
\def\lst{}
\addto\lst{1in/1pt,}
\addto\lst{2in/2pt,}
\addto\lst{3in/3pt,}

\ea\foreach \lst \do#1/#2,{\hrule width#1 height#2 \medskip}

\end
 
2

Here is an alternative solution with functional package:

\documentclass{article}
\usepackage{functional}
\begin{document}

\clistNew \lMyClist
\clistPutRight \lMyClist {{1in}{1pt}}
\clistPutRight \lMyClist {{2in}{2pt}}
\clistPutRight \lMyClist {{3in}{3pt}}

\NewDocumentCommand \DoMyClist {m} {%
  \clistVarMapInline #1 {%
    \rule ##1\par
  }%
}

\DoMyClist \lMyClist

\end{document}

enter image description here

1

Package multido, as many loop parameters as needed:

\documentclass{article}
\usepackage{multido}

\begin{document}
  \noindent\multido{\dimW=1in+1in,\dimH=1pt+1pt}{3}{%
    \rule{\dimW}{\dimH}\\
  }
\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.