3

I am using my solution from How can I determine the size of an array? to count the number of entries. However, the \foreach/\IfStrEq in \SetArrayLength seems to have a problem when an entry is macro (\Ellipsis in the MWE) with an optional parameter. I naively thought that would be simple to fix by just redefining the macro locally:

\renewcommand{\Ellipsis}[1][]{z}%

but this still does not let me count the number of entries of \ListB nor \ListC. The resultant error message is

Undefined control sequence.
\@xs@IfStrEq@@ ...\@xs@arg@ii {#2}\edef \@xs@call 
                                              {\noexpand \@xs@IfStrEq {\...

Notes:

  • This is a greatly simplified version of what I have so much of the usefulness is lost here.

Code:

\documentclass{article}
\usepackage{tikz}
\usepackage{xstring}
\usepackage{xparse}
\usepackage{xcolor}

%%% https://tex.stackexchange.com/questions/100542/how-to-extract-the-name-of-a-macro
\ExplSyntaxOn
    \newcommand{\CsToStr}[1]{\cs_to_str:N #1}
\ExplSyntaxOff

\makeatletter

\newcounter{@LengthOfArray}%
\newcommand{\GetArrayLength}{\arabic{@LengthOfArray}}%
\newcommand*{\SetArrayLength}[1]{%
    % https://tex.stackexchange.com/questions/66121/how-can-i-determine-the-size-of-an-array
    %
    % Counts the number of array members. This ignore any empty
    % array members created by extraneous commas (such as those
    % as  result of a double comma, or a trailing comma.
    %
    \setcounter{@LengthOfArray}{0}%
    \begingroup
        \renewcommand{\Ellipsis}[1][]{z}% <-- Redefine locally for counting
        \foreach \x in #1 {%
            \IfStrEq{\x}{}{}{%% only increment when non-empty
                \stepcounter{@LengthOfArray}%
            }%
        }%
    \endgroup
}%

\newcommand*{\Ellipsis}[1][\dots]{% 
    \,#1\ltx@ifnextchar{,}{\,}{}%
}%
\makeatother

\newcommand*{\ListA}{1,2,3,,,,4,}
\newcommand*{\ListB}{1,2,3,,,,4,\Ellipsis}
\newcommand*{\ListC}{1,2,3,,,,4,\Ellipsis,5}

\newcommand*{\CheckLength}[2]{%
    % #1 = string macro 
    % #2 = expected length
    \SetArrayLength{#1}%
    Length of \CsToStr{#1} is \GetArrayLength%
    \ifnum\GetArrayLength=#2\relax
        ~as expected.
    \else
        , \textcolor{red}{but should have been #2!!}
    \fi
}%

\begin{document}

%Test \verb|\Ellipsis:| 
%$1, \Ellipsis.$   \quad 
%$1, \Ellipsis[\cdots],  2$
%\bigskip

\CheckLength{\ListA}{4}\par %% This is ok, but following two have issues.
\CheckLength{\ListB}{5}\par
\CheckLength{\ListC}{6}

\end{document}
Peter Grill
  • 223,288
  • Why the tikz tag? – Alenanno Apr 30 '16 at 22:23
  • @Alenanno \foreach and PGF maths stuff ? – cfr Apr 30 '16 at 22:24
  • @cfr The \foreach can be done with pgffor as well no? And I didn't see any \pgfmath command in the code above, so that's why I thought tikz was weird there. :D – Alenanno Apr 30 '16 at 22:28
  • @Alenanno I'm guessing it is a feature of the larger document rather than the minimal example since tikz is loaded rather than pgffor. And pgffor is, after all, part of PGF/TikZ, so the tikz-pgf tag is not inaccurate. – cfr Apr 30 '16 at 22:57

1 Answers1

3

The usual expansion problems with xstring, I'd say. But you're complicating things. The \clist_set:Nn function of expl3 purges empty items, so it's a straightforward application.

\documentclass{article}
\usepackage{xparse}
\usepackage{xcolor}

\ExplSyntaxOn
\NewDocumentCommand{\SetArrayLength}{sm}
 {
  \IfBooleanTF{#1}
   {% star form: expect a control sequence
    \grill_array_length:V #2
   }
   {
    \grill_array_length:n { #2 }
   }
 }
\DeclareExpandableDocumentCommand{\GetArrayLength}{}
 {
  \clist_count:N \l_grill_purged_array_clist
 }
\clist_new:N \l_grill_purged_array_clist
\cs_new_protected:Nn \grill_array_length:n
 {
  \clist_set:Nn \l_grill_purged_array_clist { #1 }
 }

\cs_generate_variant:Nn \grill_array_length:n { V }
\ExplSyntaxOff

\makeatletter
\newcommand*{\Ellipsis}[1][\dots]{% 
    \,#1\ltx@ifnextchar{,}{\,}{}%
}
\makeatother

\newcommand*{\ListA}{1,2,3,,,,4,}
\newcommand*{\ListB}{1,2,3,,,,4,\Ellipsis}
\newcommand*{\ListC}{1,2,3,,,,4,\Ellipsis,5}

\newcommand*{\CheckLength}[2]{%
    % #1 = string macro 
    % #2 = expected length
    \SetArrayLength*{#1}%
    Length of \texttt{\string#1} is \GetArrayLength
    \ifnum\GetArrayLength=#2\relax
        ~as expected.
    \else
        , \textcolor{red}{but should have been #2!!}
    \fi
}%

\begin{document}

%Test \verb|\Ellipsis:| 
%$1, \Ellipsis.$   \quad 
%$1, \Ellipsis[\cdots],  2$
%\bigskip

\CheckLength{\ListA}{4}\par %% This is ok, but following two have issues.
\CheckLength{\ListB}{5}\par
\CheckLength{\ListC}{6}

\end{document}

enter image description here

You could do

\let\Ellipsis\relax

instead of \renewcommand{\Ellipsis}[1][]{z}, but with the above strategy you don't care about what items you have in the list.

The correct strategy with xstring is \noexpandarg:

\newcommand*{\SetArrayLength}[1]{%
    % http://tex.stackexchange.com/questions/66121/how-can-i-determine-the-size-of-an-array
    %
    % Counts the number of array members. This ignore any empty
    % array members created by extraneous commas (such as those
    % as  result of a double comma, or a trailing comma.
    %
    \setcounter{@LengthOfArray}{0}%
    \begingroup
        \noexpandarg
        \foreach \x in #1 {%
            \expandafter\IfStrEq\expandafter{\x}{}{}{%% only increment when non-empty
                \stepcounter{@LengthOfArray}%
            }%
        }%
    \endgroup
}
egreg
  • 1,121,712