2

I am trying to do specific actions based on the count of a certain character in a variable string.

When using the result of StrCount inside an ifnumgreater, I get an error despite the value being processed properly.

I think that the StrCount is not processed as an integer but it does output the correct value (after 2¿2 which appears to be an error).

Any idea of how to resolve this?

MWE

\documentclass[draft]{article}
\usepackage{mwe}
\usepackage{etoolbox}
\usepackage{xstring}
\newcommand{\foo}{\StrCount{test}{t}}
\newcommand{\res}{}
\ifnumgreater{\foo}{0}{
  \renewcommand{\res}{yes}
}{\renewcommand{\res}{no}}
\begin{document}
\res
\end{document}

Output

output

Error log

error-log

./test.tex:7: Missing number, treated as zero.
<to be read again> 
                   \let 
l.7 \ifnumgreater{\foo}{2}
                          {
A number should have been here; I inserted `0'.
(If you can't figure out why I needed to see a number,
look up `weird error' in the index to The TeXbook.)
3
  • \StrCount{test}{t}[\foo] instead of \newcommand – egreg Aug 31 '17 at 13:29
  • @egreg What is the difference between \newcommand{\foo} and [\foo]. It does work but I would like to understand why. – Akaizoku Sep 1 '17 at 9:46
  • You should look at the documentation for xstring. – egreg Sep 1 '17 at 10:00
1

It's the usual problem that \StrCount{test}{t} is not the number 1, but a set of complex instructions that ultimately (but not expandably) produce it.

The xstring package allows for a trailing optional argument which should contain a control sequence that's defined to expand to the result of the operation.

\documentclass{article}

\usepackage{etoolbox}
\usepackage{xstring}

\newcommand{\foo}{}% just for checking it's not defined
\StrCount{test}{t}[\foo]% this stores the number of matches in \foo

\newcommand{\res}{}
\ifnumgreater{\foo}{0}
  {\renewcommand{\res}{yes}}
  {\renewcommand{\res}{no}}

\begin{document}

\res

\end{document}

A variation using xparse, but the precise coding depends on information about how you plan to use the macros.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\florian}{mmmO{}}
 {% #1 = regex to test for
  % #2 = token list
  % #3 = cases
  % #4 = default
  \regex_count:nxN { #1 } { #2 } \l_florian_matches_int
  \int_case:nnF { \l_florian_matches_int } { #3 } { #4 }
 }
\int_new:N \l_florian_matches_int
\cs_generate_variant:Nn \regex_count:nnN { nx }
\ExplSyntaxOff

\newcommand{\testA}{test}
\newcommand{\testB}{tes}
\newcommand{\testC}{es}
\newcommand{\testD}{ttest}

\newcommand{\res}{} % initialize

\begin{document}

\florian{t}{\testA}{
 {0}{\renewcommand{\res}{no}}
 {1}{\renewcommand{\res}{yes, one}}
}[\renewcommand{\res}{yes, many}]

1. \res

\florian{t}{\testB}{
 {0}{\renewcommand{\res}{no}}
 {1}{\renewcommand{\res}{yes, one}}
}[\renewcommand{\res}{yes, many}]

2. \res

\florian{t}{\testC}{
 {0}{\renewcommand{\res}{no}}
 {1}{\renewcommand{\res}{yes, one}}
}[\renewcommand{\res}{yes, many}]

3. \res

\florian{t}{\testD}{
 {0}{\renewcommand{\res}{no}}
 {1}{\renewcommand{\res}{yes, one}}
 {3}{\renewcommand{\res}{Three!}}
}[\renewcommand{\res}{yes, many}]

4. \res

\end{document}

enter image description here

8
  • I would like to count the number of occurrences of a specified character in a string. How can I do that with expl3? I have not found anything on that in the package documentation. – Akaizoku Aug 31 '17 at 12:43
  • @Florian That wasn't specified and is quite a different problem – egreg Aug 31 '17 at 12:58
  • It is literally explained in the first sentence of my post. – Akaizoku Aug 31 '17 at 13:04
  • @Florian Sorry, I had overlooked the first paragraph. – egreg Sep 1 '17 at 10:03
  • Your first solution covers exactly what I needed, thank you. The second one, however, is much more interesting as it allows for various use cases. It is a bit more complex though. – Akaizoku Sep 1 '17 at 12:20
1

Tailor-made for the listofitems package, which provides fully expandable results. In the MWE below, the search "sequence" can be a single character, as shown, or a multi-token sequence, such as "is" which will likewise provide \thecharcount of 2.

Typically, listofitems is used to parse an input list for the fields between [possibly nested] separators. Here, if we use the desired search string as the field separator, then the number of instances of the found search string (aka field separators) will be the number of fields located (known as the list length) minus 1.

\documentclass{article}
\usepackage{listofitems}
\newcommand\countchars[2]{%
  \setsepchar{#1}%
  \readlist\mylist{#2}%
  \edef\thecharcount{\the\numexpr\listlen\mylist[]-1\relax}%
  \thecharcount
}
\begin{document}
\def\mydata{This is a test of the emergency broadcast system}
\countchars{e}{\mydata}

There are \thecharcount{} instances of the sequence ``e'' in ``\mydata''

\countchars{c}{\mydata}

There are \thecharcount{} instances of the sequence ``c'' in ``\mydata''
\end{document}

enter image description here


To give an example of how nested searches can be employed and utilized:

\documentclass{article}
\usepackage{listofitems}
\begin{document}
\def\mydata{This is a test of the emergency broadcast system}
\setsepchar{e/s}
\readlist\mylist{\mydata}

There are \the\numexpr\listlen\mylist[]-1\relax{} instances of the sequence 
  ``\mylistsep[1]'' in ``\mydata''

\foreachitem\i\in\mylist{%
  \def\tlev{\mylistsep[\icnt]}%
  \def\blev{\mylistsep[\icnt,1]}%
  There are \the\numexpr\listlen\mylist[\icnt]-1\relax{} instances of ``\blev'' between 
  \ifnum\icnt=1\relax the beginning \else \tlev$_{\the\numexpr\icnt-1\relax}$ \fi and 
  \ifnum\icnt=\listlen\mylist[]\relax the end\else e$_{\icnt}$\fi.\par
}
\end{document}

enter image description here

0

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.