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
  • 3
    Isn't this easier with external tools such as perl etc.? – user31729 Mar 27 '15 at 19:10
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.

3

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

  • if word counting is needed (in the sense of counting things separated by spaces) this can be added too. – user4686 Mar 28 '15 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

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.