我想编写一个lisp
在字符串中进行多次搜索和替换的函数。例如,我想用和分别"a"
替换字符串导致床。"t"
"e"
"d"
"bat"
我怎样才能做到这一点?
这是一个纯粹的功能版本:
(map 'string (lambda (c)
(case c
(#\a #\e)
(#\t #\d)
(t c)))
"bat")
==> "bed"
为了使其更通用,您可以在编译时使用宏构造 lambda:
(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"
请注意,宏的参数make-translation-lambda
必须是字符串文字。
或者,更灵活但效率更低,您可以这样做
(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"
使用宏的版本的性能make-translation-lambda
与正在翻译的字符串呈线性关系 ( O(length(input))
)。
该函数的性能translate-string
是O(length(input) * length(from))
。
如果您想从原始字符串中一次替换一个字符,类似于tr unix 实用程序的工作方式,您应该一次处理一个字符并收集转换后的字符:
(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")
我将LOOP
宏与这些子句一起使用:
仅作记录:
(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)))
也许对于 Unicode 字符串来说不是最好的主意,但对于 ASCII 来说会很好。
编辑
稍微修改它以无需额外的 lambdas 生成。