Reputation: 51
I would like to write a lisp
function that does multiple search and replace in a string. for example I want to replace "a"
and "t"
with "e"
and "d"
respectively in string "bat"
resulting in bed.
How can I do this?
Upvotes: 2
Views: 253
Reputation:
If you're looking to replace a character at a time from the original string, similar to how the tr unix utility works, you should be processing the string one character a time and collecting the transformed character:
(defun transform-chars (replacements str)
"replacements is a list of lists: (FROM-CHAR TO-CHAR)"
(coerce
(loop for char across str
for tr = (assoc char replacements)
if (null tr) collect char
else collect (second tr))
'string))
(transform-chars '((#\a #\e) (#\t #\d)) "bat")
I'm using the LOOP
macro with these sub-clauses:
Also we're coercing the collected characters from a list into a string.
Upvotes: 1
Reputation:
Just for the record:
(defun make-sparse-charmap (from to)
(loop with map =
(loop with map = (make-string 128 :initial-element #\x)
for i from 0 below 128 do
(setf (char map i) (code-char i))
finally (return map))
for x across from
for y across to do
(setf (char map (char-code x)) y)
finally (return map)))
(defun tr (source from to)
(loop with map = (make-sparse-charmap from to)
and result = (make-string (length source) :initial-element #\x)
for c across source
for i from 0 do
(setf (char result i) (char map (char-code c)))
finally (return result)))
Maybe not the best idea for Unicode strings, but for ASCII will do nicely.
EDIT
Slightly modified it to do without extra lambdas generation.
Upvotes: 0
Reputation: 60014
Here is a purely functional version:
(map 'string (lambda (c)
(case c
(#\a #\e)
(#\t #\d)
(t c)))
"bat")
==> "bed"
To make this more general purpose, you can construct the lambda at compile time with a macro:
(defmacro make-translation-lambda (from to)
`(lambda (c) (case c ,@(map 'list (lambda (i o) `(,i ,o)) from to) (t c))))
(map 'string (make-translation-lambda "at" "ed") "bat")
==> "bed"
Note that the arguments to the macro make-translation-lambda
must be string literals.
Alternatively, more flexibly but less efficiently, you can do
(defun translate-string (input from to)
(assert (= (length from) (length to)))
(map 'string
(lambda (c)
(let ((pos (position c from)))
(if pos
(char to pos)
c)))
input))
(translate-string "bed" "at" "ed")
==> "bed"
The performance of the version using the macro make-translation-lambda
is linear with the string being translated (O(length(input))
).
The performance of the function translate-string
is O(length(input) * length(from))
.
Upvotes: 3