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}
  • 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
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
}

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.