Search code examples
emacs

How do I count the number of specific characters in a string in Emacs Lisp?


Let's say I have a string s.

And this string s could contain this:

asdf-asdfasdfasf-fasdf-asdfasdfasdf

or this:

asf-asdfaf

but also this:

aasdaf

How do I count the number of dashes (-) in this string using Emacs Lisp and store this number in some variable e.g. count-of-dashes?


Solution

  • The following function should do it:

    (defun count-chars (char str)
      (let ((s (char-to-string char))
            (count 0)
            (start-pos -1))
        (while (setq start-pos (string-search s str (+ 1 start-pos)))
          (setq count (+ 1 count)))
        count))
    
    

    You call it like this:

    (count-chars ?- "---") ==> 3
    (count-chars ?- "foo-bar") ==> 1
    (count-chars ?- "-foo-bar-baz") ==> 3
    (count-chars ?- "foobarbaz") ==> 0
    

    To set a variable to the number found, you just use setq:

    (setq count-of-chars (count-chars ?- "foo-bar-baz"))
    

    Basically, we loop looking for the first dash: if we find it we remember where so that we start looking at the place just to the right of it the next time around the loop. The loop body then just counts every one we see. When we can't find any more, string-search (and the setq) returns nil and the loop exits, whereupon we return the accumulated count. See the doc string of the function string-search with C-h f string-search for the details.

    Another method is more akin to the split string method of python: split-string splits a string on a separator into a list of parts. We then count the parts (the length of the list) and subtract 1: "a-b-c" is split into ("a" "b" "c") so there are three parts but only two separators.

    (defun count-chars (char str)
      (let ((s (char-to-string char)))
        (- (length (split-string str s)) 1)))
    

    Again, see the doc string of split-string (C-h f split-string) for all the details.

    In both cases, we converted the character argument to a string argument, because both string-search in the first case and split-string in the second expect a string argument (to search for in the first case and to use as a separator in the second case - in fact, split-string can use a regular expression as a separator). Characters and strings are different data types in Emacs Lisp, so the conversion is necessary if you really want a character s the first argument of count-chars. But you could make it a string instead:

    (defun count-seps (sep str)
       (- (length (split-string str sep)) 1))
    

    and then you would call it like this instead:

    (count-seps "-" "abc-def-ghi-")
    

    which is simpler and more general:

    (count-seps "-;-" "abc-;-def") ==> 1
    

    but you do have to worry about special characters in the separator string:

    (count-seps "-*-" "abcd-------def") ==> 1
    

    since the regular expression -*- matches one or more dashes so it matches all seven dashes: there is only one separator. Whether that's what you want is debatable. If you don't want it, you'd need to escape the special characters in the separator string:

    (defun count-chars (sep str)
       (let ((qsep (regexp-quote sep)))
         (- (length (split-string str qsep)) 1)))