0

I am trying to understand how to define a PGF function with a variable number of arguments. Here is a minimal working example of what I am trying to do:

\documentclass{minimal}

\usepackage{pgfmath}

\pgfmathdeclarefunction{theFnIDef}{...}{%
\begingroup
   \pgfmatharray{#1}{0}%
   % \pgfmatharray{#1}{1}%   <---  This line always fails
   \pgfmathsmuggle\pgfmathresult
\endgroup
}

\begin{document}

theFnIDef(2,3): \pgfmathparse{theFnIDef(2,3)}\pgfmathresult

theFnIDef(2,3,5,8): \pgfmathparse{theFnIDef(2,3,5,8)}\pgfmathresult

\end{document}

This always outputs:

theFnIDef(2,3): 3

theFnIDef(2,3,5,8): 8

which is basically the last element. I have tried other syntax (\pgfmathparse{array(#1,1)}) and calling the function with additional curly braces (\pgfmathparse{theFnIDef({2,3})}), but nothing works.

In the documentation for version 3.1.9a, on page 1050, it says,

...although it is a fairly simple task...

as can be seen in this screen shot I took of the documentation:

screenshot of pgf documentation 3.1.9a, page 1050

but I can't seem to figure out how to do it. If anybody could help, that would be great.

3
  • 1
    The function \pgfmatharray expects as its first argument a comma separated list of values. The ... argument specification doesn't return the arguments in commas but in braced groups.
    – Skillmon
    Sep 23, 2021 at 18:33
  • @Skillmon I figured that out using your explations in another answer and finding out about \tl_log:n. I still can't figure out how to use {2}{3}{5}{8} in the rest of the function definition. Any pointers to where I could find more specific info on that? :-(
    – ghlecl
    Sep 23, 2021 at 18:36
  • I think I have finally found a way: latex \makeatletter \ExplSyntaxOn \seq_new:N \myseq \clist_new:N \myclist \cs_set_eq:NN \@seqsetsplit \seq_set_split:Nnn \cs_set_eq:NN \@clistfromseq \clist_set_from_seq:NN \cs_set_eq:NN \@seqcount \seq_count:N \ExplSyntaxOff \pgfmathdeclarefunction{theFnIDef}{...}{% \begingroup \@seqsetsplit \myseq {} {#1} \@clistfromseq \myclist \myseq \edef\bla{\@seqcount \myseq} % \edef\bla{4} \pgfmatharray{\myclist}{1}% \pgfmathsmuggle\pgfmathresult \endgroup } \makeatother seems to work.
    – ghlecl
    Sep 23, 2021 at 19:00

1 Answer 1

1

You need to use the internal defintion of the array function which does expect its first argument to be a list of braced elements, i.e.

{2}{3}

and

{2}{3}{5}{8}

in your case, which is exactly what the internal defintion of your theFnIDef functions has to deal with (because that's what you always do with \pgfmathdeclarefunction{<name>}, you actually define \pgfmath<name>@).

So

\pgfmatharray@{#1}{<num>}%

will give you the <num>th element of your parameters by storing it in \pgfmathresult.

The array@ function itself just loops through all given {…} until it has reached the one you've asked for.


In the code below, I'm getting the size of the #1 array by using the dim@ function (same rules apply → using the internal one) which also just loops through all elements incrementing a counter everytime it encounters an element.

This is a very inefficient (O(n²)?) way to access all parameters since it involves multiple loops that do nothing with the actual elements.

But for a more efficient (or easier?) way to use all your elements the actual definition of the function is needed.


This code does … nothing but returns the number of parameters (basically just the first call to dim@) and writes the elements to the log.

It would be better to also do something with the parameters while we're iterating through them anyway. In another answer of mine the last eight of ten parameters get simply forwarded to other functions (that do expect eight arguments). This is done via the \pgfmath@util@… functions (and a bit expansion control).


There's also a \pgfmathloop\repeatpgfmathloop with its own self-incrementing counter \pgfmathcounter (not a TeX count!) but that starts at 1, not at 0, so we would need

\pgfmatharray@{#1}{\numexpr\pgfmathcounter-1\relax}%

in the loop (and \the\numexpr\pgfmathcounter-1\relax in the \typeout) to get the PGFmath numbering of the array's index.

I've opted instead for the \pgfutil@loop (which should be equal to the LaTeX \@loop but is defined across all platforms that can run PGFmath.

Code

\documentclass{article}
\usepackage{pgfmath}
\makeatletter
\pgfmathdeclarefunction{theFnIDef}{...}{%
  \begingroup
    \pgfmathdim@{#1}%
    \let\pgfmatharraysize\pgfmathresult
    \typeout{Array #1 has \pgfmatharraysize\space elements.}%
    \c@pgf@countb=0
    \pgfutil@loop
      \pgfmatharray@{#1}{\c@pgf@countb}%
      \typeout{Argument No \the\c@pgf@countb\space is \pgfmathresult.}%
      \advance\c@pgf@countb by 1
      \ifnum\c@pgf@countb<\pgfmatharraysize\relax
    \pgfutil@repeat
    \let\pgfmathresult\pgfmatharraysize
    \pgfmathsmuggle\pgfmathresult % will just return the number of elements
  \endgroup
}
\makeatother

\begin{document}
theFnIDef(2,3): \pgfmathprint{theFnIDef(2,3)}

theFnIDef(2,3,5,8): \pgfmathprint{theFnIDef(2,3,5,8)}
\end{document}

Output (Log)

Array {2}{3} has 2 elements.
Argument No 0 is 2.
Argument No 1 is 3.
Array {2}{3}{5}{8} has 4 elements.
Argument No 0 is 2.
Argument No 1 is 3.
Argument No 2 is 5.
Argument No 3 is 8.

You must log in to answer this question.

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