# How to find the length of a PGF array even if it's in a macro?

One of the answers to a question about how to calculate the length of a PGF array provided a function that increments a counter for each step of the iteration on the array provided as its argument.

But if I store an array in a variable with \def\myarray{{1,2,3,4,5,6}} and then use \arrayLength{\myarray}, the number 1 is output, instead of 6. I believe it's got something to do with how \myarray is expanded, but can't understand what. \expandafter\arrayLength{\myarray} also outputs 1.

How can a function \len correctly output the number of elements in an array, no matter if it is \len{{1,2,3}} or \len{\myarray}?

A MWE:

\documentclass[tikz]{standalone}

\newcounter{arraycard}
\def\arrayLength#1{%
\setcounter{arraycard}{0}%
\foreach \x in #1{%
\stepcounter{arraycard}%
}%
\the\value{arraycard}%
}

\begin{document}
The length of $\{1,2,3\}$ is \arrayLength{{1,2,3}}.

The length of $\{1,2,\{3,4\},5\}$ is \arrayLength{{1,2,{3,4},5}}.

\def\myarray{{1,2,3,4,5,6}}
The length of \verb|\myarray| is \arrayLength{\myarray}.
\end{document}

• If you use \def\myarray{1,2,3,4,5,6} instead it works as expected. The reason is that it just counts the array as one item. – user121799 Jun 13 '19 at 18:09
• \expandafter\arrayLength{\myarray} expands { but that isn't expandable, you want \expandafter\arrayLength\expandafter{\myarray} – David Carlisle Jun 13 '19 at 18:09
• @marmot it does, but PGF/Tikz documentation explains that arrays should be declared with double brackets. – Daniel Diniz Jun 13 '19 at 18:10
• @DanielDiniz This is correct but you do not write the macro that computes the length in this way. – user121799 Jun 13 '19 at 18:11
• @DanielDiniz not, not really. What \expandafter\arrayLength\expandafter{\myarray} does is it expands \myarray before \arrayLength reads its argument, so what \arrayLength sees is in either case \arrayLength{{1,2,3,4,5,6}}. – Skillmon Jun 13 '19 at 18:22

You want to expand the macro before counting so

\documentclass[tikz]{standalone}

\newcounter{arraycard}
\def\arrayLength#1{\expandafter\xarraylength\expandafter{#1}}%
\def\xarraylength#1{%
\setcounter{arraycard}{0}%
\foreach \x in #1{%
\stepcounter{arraycard}%
}%
\the\value{arraycard}%
}

\begin{document}
The length of is \arrayLength{{\newcounter,\begin,\tikz}}.

\def\myarray{{\newcounter,\begin,\tikz}}
The length of \verb|\myarray| is \arrayLength\myarray.
\end{document}

• +1 but this solves a different problem from the one mentioned in my above comment. What if one of the macros expands to 1,2,3? – user121799 Jun 13 '19 at 19:17
• @marmot according to the OPs comment the array should always be double braced so that would be user error. – David Carlisle Jun 13 '19 at 19:20
• @marmot even if one of the arguments would, if the array gets used in a \foreach that one won't expand the macro containing that valid clist. So I guess the behaviour in David's answer is the desired one. – Skillmon Jun 13 '19 at 19:43
• @Skillmon This is not necessarily true. – user121799 Jun 13 '19 at 20:31

A version of \arrayLength that checks whether the first token in the argument is a second opening brace, if so it is checked that nothing follows the inner group and if that is true the elements are counted. If the first token is not an opening brace the first token is expanded once, it is again tested whether the first token is an opening brace and nothing follows the inner group, if so it counts the elements. If the argument fails these tests an error is thrown and 0 returned.

The macro is fully expandable.

\documentclass[border=3.14]{standalone}

\usepackage{xparse}
\ExplSyntaxOn
\group_begin:
\char_set_catcode_letter:n { 32 }% spaces are letters until the next \group_end:
\group_end:%
\NewExpandableDocumentCommand \xarrayLength { +m }
{
{ \__xarrayLength_count_first_group:nw #1 \q_stop }
{ \__xarrayLength_aux:o { #1 } }
}
\cs_new:Npx \__xarrayLength_aux:n #1
{
{ \exp_not:N \__xarrayLength_count_first_group:nw #1 \exp_not:N \q_stop }
{ \exp_after:wN \exp_not:N \__xarrayLength_bad_argument: 0 }
}
\cs_generate_variant:Nn \__xarrayLength_aux:n { o }
\cs_new:Npx \__xarrayLength_count_first_group:nw #1 #2 \q_stop
{
\exp_not:N \tl_if_empty:nTF { #2 }
{ \exp_not:N \clist_count:n { #1 } }
{ \exp_after:wN \exp_not:N \__xarrayLength_bad_argument: 0 }
}
\ExplSyntaxOff

\newcommand\myarray{{1,2,3,4,5,6}}

\newcounter{elements}

\begin{document}
% \setcounter to prove it is expandable
\setcounter{elements}{\xarrayLength{\myarray}}\arabic{elements}
\xarrayLength{{1,2,3,4,5,6}}
\renewcommand\myarray{1,2,3,4,5,6}%
% \setcounter to prove it is expandable
\setcounter{elements}{\xarrayLength{\myarray}}\arabic{elements}
\xarrayLength{1,2,3,4,5,6}
\end{document}


Since you're using PGF arrays, that are always expressed with “double braces”, an expl3 one liner suffices:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\arrayLength}{m}
{% #1 = explicit or implicit array
\diniz_array_length:o { #1 }
}
\cs_new:Nn \diniz_array_length:n
{
\clist_count:n #1
}
\cs_generate_variant:Nn \diniz_array_length:n { o }
\ExplSyntaxOff

\begin{document}

The length of $\{1,2,3\}$ is \arrayLength{{1,2,3}}.

The length of $\{1,2,\{3,4\},5\}$ is \arrayLength{{1,2,{3,4},5}}.

\def\myarray{{1,2,3,4,5,6}}

The length of \verb|\myarray| is \arrayLength{\myarray}.

\end{document}


If you want the position of the last item, under PGF conventions it's the number of items minus one, because indexing starts from 0.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\arrayLength}{m}
{% #1 = explicit or implicit array
\diniz_array_length:o { #1 }
}
\cs_new:Nn \diniz_array_length:n
{
\int_eval:n { \clist_count:n #1 - 1 }
}
\cs_generate_variant:Nn \diniz_array_length:n { o }
\ExplSyntaxOff

\begin{document}

The length of $\{1,2,3\}$ is \arrayLength{{1,2,3}}.

The length of $\{1,2,\{3,4\},5\}$ is \arrayLength{{1,2,{3,4},5}}.

\def\myarray{{1,2,3,4,5,6}}

The length of \verb|\myarray| is \arrayLength{\myarray}.

\end{document}


The answers here will be 2, 3 and 5.

The trick is that if you call \arrayLength{{1,2,3}}, the o variant will try to expand {, which obviously does nothing; in the case of \arrayLength{\myarray} the macro is expanded once, as desired. Notice that {1,2,3} will be the argument to \clist_count:n which will so remove the braces.

Note also that this is fully expandable (can go in \edef).

The way I sometimes solve this in my codes is

\documentclass[tikz]{standalone}

\newcounter{arraycard}
\def\arrayLength#1{%
\setcounter{arraycard}{0}%
\edef\temp{\noexpand\foreach \noexpand\x in #1{%
\noexpand\stepcounter{arraycard}%
}}%
\temp
\the\value{arraycard}%
}

\begin{document}
The length of $\{1,2,3\}$ is \arrayLength{{1,2,3}}.

The length of $\{1,2,\{3,4\},5\}$ is \arrayLength{{1,2,{3,4},5}}.

\def\myarray{{1,2,3,4,5,6}}
The length of \verb|\myarray| is \arrayLength\myarray.
\end{document}


Most likely there will be an \expandafter equivalent but I do not like \expandafter personally too much.

• \expandafter is easier to control, in that \edef here is safe as you know you just has a comma list of digits if list had had commands you don't want to expand, things would have been harder – David Carlisle Jun 13 '19 at 18:27
• @DavidCarlisle \foreach does not parse its arguments unless you say explicitly parse=true. But even then the above works. Anyway, you can just write an answer that does the same with several \expandafters, I will be delighted to learn from it. – user121799 Jun 13 '19 at 18:31
• irresepective of foreach, the \edef would expand anything in #1 (not an issue here as you know it can only expand once). – David Carlisle Jun 13 '19 at 18:42
• @DavidCarlisle Yes, that's the purpose of this \edef. Of course if the OP places macros in the arrays that expand to something containing commas this will be different but in this case one needs to specify what the desired result is. The purpose of this is to provide something that works both for explicit lists and lists stored in arrays in the formal the OP wants them. It achieves this. I personally would not store the array in the format the OP does but would add the braces when I read out its elements, as mentioned in the comment above. – user121799 Jun 13 '19 at 18:48
• commas in the argument are not the main problem, try the list I used in my answer with some \edef unfriendly entries.... – David Carlisle Jun 13 '19 at 19:00