5

How to get the number of occurrences of characters in a string?

\documentclass[14pt]{extarticle}
\usepackage{xstring}
\usepackage{forloop}
\usepackage{ifthen}
\usepackage{pgfkeys}
\newcommand{\fios}{hellow world}
\begin{document}
%\StrCount{\fios}{e}
%\StrChar{\fios}{1}
\StrLen{\fios}[\varL]
\varL

\vspace{5ex}
\newcounter{loop}
\forloop{loop}{1}{\value{loop}< \varL}
{\StrChar{\fios}{\arabic{loop}}~--
\par
}
\end{document}

I need to get:

h - 1
e - 1
l - 3
o - 2
w - 2
r - 1
d - 1
0

5 Answers 5

6

This is a Lua snippet with some CJKV characters in the strings. I've selected an opentype font from TeX Live, but it doesn't contain some diacritical letters (e.g. č, ř and š), let me hope it is sufficient as a demonstration of handling UTF-8 strings. We run lualatex mal-letters.tex.

% lualatex mal-letters.tex
\documentclass[a4paper]{article}
\pagestyle{empty}
\parindent=0pt
\usepackage{luacode} % to be able to write Lua code
\usepackage{fontspec} % to be able to load fonts (CJKV)

\begin{document}
\begin{luacode*}
chars={} -- Lua table to store occurencies
function countme() -- the core function
text=tex.toks[0] -- pass an argument from TeX
unicode.utf8.gsub(text, ".", function(s) -- find any utf8 char
   if not chars[s] then chars[s]=0 end -- define it if it is not
   chars[s]=chars[s]+1 -- plus one as a char has been found
   return s -- don't change an original string
   end) -- end of function and gsub
-- print the results to the terminal
for letter,count in pairs(chars) do
   print(letter, count)
   -- comment out the following line, if you don't have that font (texmf-dist/fonts/opentype/public/fandol/fandolsong-regular.otf) and check out the terminal
   tex.print("{\\setmainfont{FandolSong}"..letter.."} ("..count..");")
   end   
end -- function, countme
\end{luacode*}
\def\occurs#1{\toks0{#1}\directlua{countme()}}
% This particular font doesn't contain č, ř, š etc., but it does contain CJKV.
\occurs{你怎么样? Pavel Stříž, číšník. さよなら。}
\end{document}

An example working with CJKV characters

In the terminal, we will spot a similar structure to this one:

ž   1
?   1
你   1
么   1
。   1
な   1
,   1
怎   1
a   1
さ   1
.   1
k   1
样   1
ら   1
P   1
š   1
n   1
č   1
l   1
ř   1
よ   1
    4
í   3
e   1
v   1
S   1
t   1
5

It is easy to program with xstring (with no unuseful test):

\documentclass{extarticle}
\usepackage{xstring}
\newcommand\fios{hellow world}
\newcommand\occurs[1]{%
    \ifhmode\par\fi\begingroup\expandarg
    \StrDel{#1}{ }[\tempstr]%
    \occuraux\endgroup
}
\newcommand\occuraux{%
    \unless\ifx\tempstr\empty
        \StrSplit\tempstr1\currentchar\remainstr
        \currentchar\space-- \StrCount\tempstr\currentchar\par
        \let\tempstr\remainstr
        \StrDel\tempstr\currentchar[\tempstr]%
        \expandafter\occuraux
    \fi
}
\begin{document}
\occurs{hello world}
\bigskip

\occurs\fios
\end{document}

enter image description here

5

With expl3 and l3regex:

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\countchar}{mm}
 {% #1 = character, #2 = string
  \youra_count_char:nn { #1 } { #2 }
  \int_to_arabic:n { \l_youra_count_char_int }
 }

\NewDocumentCommand{\countallchars}{m}
 {% #1 = string
  \youra_count_all_chars:n { #1 }
 }

\int_new:N \l_youra_count_char_int
\seq_new:N \l_youra_count_chars_seq

\cs_new_protected:Npn \youra_count_char:nn #1 #2
 {
  \regex_count:nnN { #1 } { #2 } \l_youra_count_char_int
 }

\cs_new_protected:Npn \youra_count_all_chars:n #1
 {
  \seq_set_split:Nnn \l_youra_count_chars_seq { } { #1 }
  \seq_remove_duplicates:N \l_youra_count_chars_seq
  \seq_remove_all:Nn \l_youra_count_chars_seq { ~ }
  \seq_map_inline:Nn \l_youra_count_chars_seq
   {
    \par
    \youra_count_char:nn { ##1 } { #1 }
    ##1 ~ - ~ \int_to_arabic:n { \l_youra_count_char_int }
    \par
   }
 }

\ExplSyntaxOff

\begin{document}

\countchar{h}{hello world}

\countallchars{hello world}

\end{document}

enter image description here

The single character version just uses \regex_count:nnN and delivers the result obtained; the full version splits the string in a sequence, removes the duplicates and possible spaces, then maps the sequence to do the counting for each item.

4

Here is a non-package solution. It lists the found letters in alphabetical order. It also works fine with pdftex and any traditional 8bit encoding. Not designed for Unicode !

Make sure to save the code sample in iso-latin-1 encoding (automatic if you copy paste to an Emacs buffer due to the magic line at top).

% -*- coding: latin-1; -*-

% Time-stamp: <28-03-2015 14:04:16 CET>


\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[latin1]{inputenc}% THIS CODE ONLY FOR SOME 8bit ENCODING

\makeatletter
\newcommand*\countchars [1]{%
    Here is (in alphabetical order) the count of characters in \emph{#1}:\par
    \let\countchars@list \@empty
    \expandafter\countchars@i\detokenize{#1}\relax }

\def\countchars@i #1{%
    \if\relax #1\expandafter\countchars@print\fi
    \expandafter\let\expandafter\countchars@list\expandafter\@empty
    \expandafter\countchars@seek\expandafter #1\countchars@list a\relax a%
}

\def\countchars@seek #1#2a#3a{%
    \if\relax #3\expandafter\countchars@seekend\fi
    \ifnum`#1<`#2
         \expandafter\def\expandafter\countchars@list\expandafter
            {\countchars@list #1a1a#2a#3a}%
         \expandafter\countchars@endseek
    \else
    \ifnum`#1=`#2
         \expandafter\expandafter\expandafter
          \def
         \expandafter\expandafter\expandafter
          \countchars@list
         \expandafter\expandafter\expandafter
          {\expandafter\countchars@list
            \expandafter#2\expandafter a\the\numexpr #3+\@ne a}%
         \expandafter\expandafter\expandafter\countchars@endseek
      \else
         \expandafter\def\expandafter\countchars@list\expandafter
          {\countchars@list #2a#3a}%
      \fi
    \fi
    \countchars@seek #1%
}

\def\countchars@endseek\countchars@seek #1#2a\relax a{%
     \expandafter
     \def\expandafter\countchars@list\expandafter {\countchars@list #2}%
     \countchars@i
}

\def\countchars@seekend #1\countchars@seek #2{%
    \expandafter
    \def\expandafter\countchars@list\expandafter {\countchars@list #2a1a}%
    \countchars@i
}

\def\countchars@print #1a\relax a{\count@ \z@
    \expandafter\countchars@printi\countchars@list a\relax a}

\def\countchars@printi #1a#2a{\if \relax #2\expandafter\countchars@end\fi
    \makebox[2em]{\scantokens{#1}}: #2\par
    \advance\count@ #2\relax
    \countchars@printi
}
\long\def\countchars@end #1\countchars@printi {There were altogether
  \the\count@{} non space characters found.}     

\makeatother

\begin{document}

\countchars{Hello World}

\medskip

\countchars{La République assure la liberté de conscience. Elle garantit le
  libre exercice des cultes sous les seules restrictions édictées ci-après
  dans l'intérêt de l'ordre public.}

\end{document}

enter image description here

1
  • if word counting is needed (in the sense of counting things separated by spaces) this can be added too.
    – user4686
    Commented Mar 28, 2015 at 13:36
2

Here, using stringstrings package, I provide two versions: one that counts it alphabetically (\countstring), and the next that counts in the order of appearance (\Countstring).

\documentclass{article}
\usepackage{stringstrings}
\def\countstring#1{%
  \caselower[q]{#1}%
  \testchar{\thestring}{a}\testchar{\thestring}{b}\testchar{\thestring}{c}%
  \testchar{\thestring}{d}\testchar{\thestring}{e}\testchar{\thestring}{f}%
  \testchar{\thestring}{g}\testchar{\thestring}{h}\testchar{\thestring}{i}%
  \testchar{\thestring}{j}\testchar{\thestring}{k}\testchar{\thestring}{l}%
  \testchar{\thestring}{m}\testchar{\thestring}{n}\testchar{\thestring}{o}%
  \testchar{\thestring}{p}\testchar{\thestring}{q}\testchar{\thestring}{r}%
  \testchar{\thestring}{s}\testchar{\thestring}{t}\testchar{\thestring}{u}%
  \testchar{\thestring}{v}\testchar{\thestring}{w}\testchar{\thestring}{x}%
  \testchar{\thestring}{y}\testchar{\thestring}{z}%
}
\def\testchar#1#2{%
  \findchars[q]{#1}{#2}\ifnum\theresult>0\par\noindent#2 - \theresult\par\fi}
\def\Countstring#1{\caselower[q]{#1}\expandafter\Countstringhelp\thestring\relax}
\def\Countstringhelp#1#2\relax{%
  \ifcsname #1Chars\endcsname\else%
    \testchar{\thestring}{#1}%
    \ifnum\theresult>0\relax\expandafter\def\csname #1Chars\endcsname{\theresult}\fi%
  \fi
  \ifx\relax#2\else\Countstringhelp#2\relax\fi%
}
\begin{document}
\countstring{Hellow World}
\noindent\hrulefill
\Countstring{Hellow World}
\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .