2

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. – marmot Jun 13 at 18:09
  • 2
    \expandafter\arrayLength{\myarray} expands { but that isn't expandable, you want \expandafter\arrayLength\expandafter{\myarray} – David Carlisle Jun 13 at 18:09
  • @marmot it does, but PGF/Tikz documentation explains that arrays should be declared with double brackets. – Daniel Diniz Jun 13 at 18:10
  • @DanielDiniz This is correct but you do not write the macro that computes the length in this way. – marmot Jun 13 at 18:11
  • 1
    @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 at 18:22
2

You want to expand the macro before counting so

enter image description here

\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? – marmot Jun 13 at 19:17
  • 1
    @marmot according to the OPs comment the array should always be double braced so that would be user error. – David Carlisle Jun 13 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 at 19:43
  • @Skillmon This is not necessarily true. – marmot Jun 13 at 20:31
1

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:
\cs_new:Npn\__xarrayLength_bad_argument:{\xarrayLength error: no TikZ array}%
\group_end:%
\NewExpandableDocumentCommand \xarrayLength { +m }
  {
    \tl_if_head_is_group:nTF { #1 }
      { \__xarrayLength_count_first_group:nw #1 \q_stop }
      { \__xarrayLength_aux:o { #1 } }
  }
\cs_new:Npx \__xarrayLength_aux:n #1
  {
    \exp_not:N \tl_if_head_is_group:nTF { #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}

enter image description here

0

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}

enter image description here

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 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. – marmot Jun 13 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 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. – marmot Jun 13 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 at 19:00
0

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}

enter image description here

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).

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.