AHS
AHS

Reputation: 51

Lisp: multi level search and replace

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

Answers (3)

user9903
user9903

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

user797257
user797257

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

sds
sds

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

Related Questions