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

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?

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)))

Related

String addition assignment in lisp

I have a loop with a condition, based on which I decide whether I should append something to existing string or not.
In Python, it should look like (this is dummy code, just to show the idea):
result_str = ''
for item in range(5):
if item % 2 == 0:
result_str += str(item)
print(result_str)
Output: 024
So the question is: how can I perform addition assignment on strings (+=) in lisp?
String concatenation relies on the more general CONCATENATE function:
(concatenate 'string "a" "b")
=> "ab"
Since it considered verbose by some, you can find libraries that implement shorter versions:
(ql:quickload :rutils)
(import 'rutils:strcat)
And then:
(strcat "a" "b")
In order to assign and grow a string, you need to use SETF with an existing variable.
(let ((string ""))
(dotimes (i 5)
(when (evenp i)
(setf string (strcat string (princ-to-string i)))))
string)
A more idiomatic way in Lisp is to avoid string concatenation, but print in a stream which writes into a buffer.
(with-output-to-string (stream)
;; now, stream is bound to an output stream
;; that writes into a string. The whole form
;; returns that string.
(loop
for i from 0 below 5 by 2
do (princ i stream)))
=> "024"
Here above, stream is just the symbol used for naming the stream, you could use any other one, including *standard-output*, the special variable that represents current output stream. Doing so would make the enclosed code redirect its standard output to the string stream.
An alternative way to build the intermediate list is the following, where iota is a small utility in the alexandria library:
(delete-if #'oddp (alexandria:iota 5))
=> (0 2 4)
In order to produce a string, you can also use FORMAT, which has a directive that can iterate over lists:
(format nil "~{~a~}" '(0 2 4))
=> "024"
The nil stream destination represents a string destination, meaning (format nil ...) returns a string. Each directive starts with a tilde character (~), ~{ and ~} enclose an iteration directive; inside that block, ~a prints the value "aesthetically" (not readably).

How to convert a list into a string in lisp

How do you convert a list into a string? I am trying to use parse-int to take a list of numbers and convert them to decimal, but i end up getting an error saying "The control-string must be a string, not (contents)".
I'm using format, but I'm not sure if I'm using it incorrectly.
Here's my code:
(princ "Enter a or list of hexadecimal numbers: ")
(setq numList (read-from-string (concatenate 'string "(" (read-line) ")")))
(defun hextodec(nums)
(setq numString (format "%s" nums))
(setq newNum (parse-integer numString :radix 16))
(write nums)
(princ " = ")
(write newNum)
) ;This will format the number that the user enters
(hextodec numList)
Since you're using read-from-string anyway, you can just tell Lisp's reader to read base 16 integers:
;; CLISP session
[1]> (let ((*read-base* 16)) (read-from-string "(9 A B C F 10)"))
(9 10 11 12 15 16) ;
14
read-from-string is a potential security hole if the contents of the string are untrusted input, because of the hash-dot evaluation notation.
When using the Lisp reader on untrusted data, be sure bind *read-eval* to nil.
[2]> (read-from-string "(#.(+ 2 2))")
(4) ;
11
Note how the #. notation causes the + function specified in the string data to be executed. That could be any function, such as something that whacks files in your filesystem, or sends sensitive data to a remote server.
Format's first argument is the stream to print to. You seem to intend to return the string instead of printing, so you should put nil there: (format nil "~s" nums)
The format control string "%s" does not contain any format directives. The entire format form does not make much sense here, as you seem to intend to loop over the given nums instead. You should employ some looping construct for that, e. g. loop, do, map, mapcar ….

Common Lisp: read each input character as a list element

Lisp newbie here.
I want to read from standard-in a string of characters such as:
aabc
I want to convert that input into a list, where each character becomes a list element:
(a a b c)
And I want the list assigned to a global variable, text.
I created this function:
(defun get-line ()
(setf text (read)))
but that just results in assigning a single symbol to text, not tokenizing the input into a list of symbols.
What's the right way to implement get-line() please?
Here you go: First using coerce to convert the string to a list of characters, then mapcar to convert each character to a string.
(defun get-line ()
(setf text (mapcar 'string (coerce (string (read)) 'list))))
(loop
for x = (string-upcase (string (read-char)))
while (not (equal " " x))
collecting (intern x))
Note the upcase is there because symbols in CL are not case sensitive and are upcased by default by the reader.

return a line of text if match found

I am having some trouble working out how to return a line of text if a match is found.
(set 'wireshark "http://anonsvn.wireshark.org/wireshark/trunk/manuf")
(set 'arptable (map (fn (x) (parse x " ")) (exec "arp -a")))
(define (cleanIPaddress x)
(slice x 1 -1))
(define (cleanMACaddress x)
(upper-case (join (slice (parse x ":") 0 3) ":")))
(define (addIPandMACaddress x)
(list (cleanIPaddress (nth 1 x)) (cleanMACaddress (nth 3 x))))
(set 'arplist (map addIPandMACaddress arptable))
(set 'routerMAC (last (assoc (exec "ipconfig getoption en1 router") arplist)))
(find-all routerMAC (get-url wireshark))
returns
("20:AA:4B")
so I know that the code "works"
but I would like to retrieve the full line of text
"20:AA:4B Cisco-Li # Cisco-Linksys, LLC"
This can be performed simply by using a string-split procedure that allows us to use remove-if (the Common Lisp version of filter) to search through a string split by newlines removing any lines that do not contain the string we are searching for. That would result in a list of every line containing the string. The functions we will define here are already available via various Common Lisp libraries, but for the education purposes, we will define them all ourselves. The code you need works like so:
; First we need a function to split a string by character
(defun string-split (split-string string)
(loop with l = (length split-string)
for n = 0 then (+ pos l)
for pos = (search split-string string :start2 n)
if pos collect (subseq string n pos)
else collect (subseq string n)
while pos))
; Now we will make a function based on string-split to split by newlines
(defun newline-split (string)
(string-split "
" string))
; Finally, we go through our text searching for lines that match our string.
; Make sure to replace 'needle' with the string you wish to search for.
(remove-if #'(lambda (x)
(equal 'nil (search (string-upcase "needle")
(string-upcase x))))
(newline-split haystack))
You should be able to apply this strategy to the code you posted with a few small modifications. This code was tested on SBCL 1.0.55.0-abb03f9, an implementation of ANSI Common Lisp, on Mac OS X 10.7.5.
In the end I used:
(find-all (string routerMAC ".*") (get-url wireshark))

Convert char to number

I'm in the process of reading a flat file - to use the characters read I want to convert them into numbers. I wrote a little function that converts a string to a vector:
(defun string-to-vec (strng)
(setf strng (remove #\Space strng))
(let ((vec (make-array (length strng))))
(dotimes (i (length strng) vec)
(setf (svref vec i) (char strng i)))))
However this returns a vector with character entries. Short of using char-code to convert unit number chars to numbers in a function, is there a simple way to read numbers as numbers from a file?
In addition to Rainer's answer, let me mention read-from-string (note that Rainer's code is more efficient than repeated application of read-from-string because it only creates a stream once) and parse-integer (alas, there is no parse-float).
Note that if you are reading a CSV file, you should probably use an off-the-shelf library instead of writing your own.
Above is shorter:
? (map 'vector #'identity (remove #\Space "123"))
#(#\1 #\2 #\3)
You can convert a string:
(defun string-to-vector-of-numbers (string)
(coerce
(with-input-from-string (s string)
(loop with end = '#:end
for n = (read s nil end)
until (eql n end)
unless (numberp n) do (error "Input ~a is not a number." n)
collect n))
'vector))
But it would be easier to read the numbers directly form the file. Use READ, which can read numbers.
Note that read-like functions are affected by reader macros.
Pick an example:
* (defvar *foo* 'bar)
*FOO*
* (read-from-string "#.(setq *foo* 'baz)")
BAZ
19
* *foo*
BAZ
As you can see read-from-string can implicitly set a variable. You can disable the #. reader macro by setting *read-eval* to nil but anyway if you have only integers on the input then consider using parse-integer instead.