5

I would like to use \foreach inside an \edef, for example to define a set of random points as follows:

\edef\mypoints{%
  \foreach \x in {0,.1,...,1}
           {\pgfmathparse{rand} (\x,\pgfmathresult)}}

Unfortunately, the above code causes an error about Undefined control sequence \pgffor@remember@once@code.

My questions are why this doesn't work, and what the simplest workaround is for saving the result of a \foreach loop.

6
  • I think we've got a lot questions on the \foreach expansion trap
    – user31729
    Commented Oct 6, 2015 at 20:24
  • 1
    @ChristianHupfer When I search for "foreach expansion trap," I get no results on stackexchange, so how do I find these? Commented Oct 6, 2015 at 20:26
  • Do you really need \edef? Commented Oct 6, 2015 at 20:26
  • @GonzaloMedina It's absolutely necessary. Otherwise, I get new random points each time, which doesn't work very well in, say, overlay slides. Commented Oct 6, 2015 at 20:27
  • @user3188445 You can set a seed: \pgfmathsetseed{1} Commented Oct 6, 2015 at 20:30

2 Answers 2

2

The other answer provides a solution which works but has the downside of using global definitions, which should be avoided unless really necessary. pgf comes with the /.list key handler which allows one to avoid the global definitions.

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}[add point/.code={\pgfmathparse{rand}%
    \edef\mypoints{\mypoints(#1,\pgfmathresult)}}]
    \def\mypoints{} % initialize
    \tikzset{add point/.list={0,0.2,...,5}}% loop without global definitions
    \draw plot coordinates {\mypoints};
\end{tikzpicture}    
\end{document}
4

You have to do the cycle outside the \edef:

\gdef\mypoints{} % initialize
\foreach\x in {0,0.1,...,1} {%
  \pgfmathparse{rand}%
  \xdef\mypoints{\mypoints(\x,\pgfmathresult)}%
}

so at each cycle you append the new point.


With the code above we might get something like

(0,-0.5593)(0.1,0.51411)(0.20001,0.55302)
(0.30002,0.51347)(0.40002,-0.54858)(0.50003,-0.64978)
(0.60004,0.61548)(0.70004,-0.32903(0.80005,0.2993)
(0.90005,-0.38226)

(line breaks for clarity). Well, not really what we expected.

We can avoid this and also global declarations.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\fpforeach}{mmmm}
 {% #1 = start, #2 = step, #3 = end, #4 = template
  \fp_step_inline:nnnn { #1 } { #2 } { #3 } { #4 }
 }

\NewDocumentCommand{\clearlist}{m}
 {
  \tl_clear_new:c { l_egreg_list_#1_tl }
 }
\NewDocumentCommand{\addtolist}{smm}
 {
  \IfBooleanTF{#1}{\tl_put_right:cx}{\tl_put_right:cn} { l_egreg_list_#2_tl } { #3 }
 }
\NewExpandableDocumentCommand{\uselist}{m}
 {
  \tl_use:c { l_egreg_list_#1_tl }
 }

\ExplSyntaxOff
  
\clearlist{points} % initialize
\fpforeach{0}{0.1}{1}{%
  \addtolist*{points}{(#1,\fpeval{round(2*rand()-1,4)}) }
}

\begin{document}

\raggedright
\uselist{points}

\end{document}

Since rand() only returns numbers between 0 and 1, but the pgfmath function rand returns numbers from −1 to 1, I use 2*rand()-1.

A possible output is

enter image description here

The trailing space is just for typesetting purposes. In the fourth argument of \fpforeach, #1 stands for the current value in the loop.

The \addtolist command simply adds, but with * it also does full expansion.

Note that the x-coordinates are indeed from 0 to 1, because l3fp uses floating point decimal numbers (not fixed precision binary converted into decimal like pgfmath).

3
  • That answers half of my question, thanks. But can you explain why it doesn't work? Commented Oct 6, 2015 at 20:41
  • @user3188445 No assignments are performed in an \edef, just macro expansion is done. There are several topics on the site that deal with the problem.
    – egreg
    Commented Oct 6, 2015 at 20:43
  • @user3188445 Try \edef\mypoints{\pgfmathparse{rand}}. Not everything is expandable.
    – percusse
    Commented Oct 6, 2015 at 21:20

You must log in to answer this question.

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