我需要创建一个函数,用列表L中的Z替换所有出现的X和Y。我的问题是我的compare语句正在根据我的理解与位置本身进行比较。
类似于使用其他语言循环时I值如何工作?当我运行此代码时,它将替换位置3和位置7的项目:(2 2 2 6 4 5 8 4 5 8 4) (2 2 7 6 4 5 7 4 5 8 4)而不是执行应做的事情。因此,我需要弄清楚如何比较该位置而不是位置本身的值。
如果我用print语句代替(replace)函数,它将打印出来,2 2 2 6所以我不太了解。我也尝试过(setf (nth Y Z) i)用相同的结果代替replace函数,这就是我能够找出问题的原因在于compare语句。我也尝试使用eq,=和equal所有相同的结果。
(defun replace-z (X Y Z L)
(loop for i in L
do
(cond
((equal Y i) (replace L (list Z) :start1 i))
((= X i) (replace L (list Z) :start1 i)))) L)
(trace replace-z)
(print (replace-z 2 6 7 '(2 2 2 6 4 5 8 4 5 8 4) ))
Run Code Online (Sandbox Code Playgroud)
Because CL is an industrial strength language, there are several functions already provided by the language to do things like this. However from the point of view of learning Lisp it's good to think about what the algorithm you would need to use to write something to do this if it did not already exist, and then actually implement that using basic operations rather than use one of the functions which already do it: doing this teaches you to program in Lisp, while finding & using the preexisting tool teaches you to look things up in manuals, and these are different skills.
So what's the algorithm?
Given a list l:
X or Y then the result is (cons 'Z ...) otherwise it is (cons <existing first element> ...), where ... means 'perform the same procedure on the rest of the list'.So we can turn this into code immediately:
(defun replace-x/y-with-z (l)
(if (null l)
l
(cons (if (or (eql (first l) 'x)
(eql (first l) 'y))
'z
(first l))
(replace-x/y-with-z (rest l)))))
Run Code Online (Sandbox Code Playgroud)
Well, this function is ugly in several ways: one of them is that it calls first a lot. We can deal with this by binding a local variable. And we can also neatly replace the hairy conditional with case:
(defun replace-x/y-with-z (l)
(if (null l)
l
(let ((first (first l)))
(cons (case first
((x y) 'z)
(otherwise first))
(replace-x/y-with-z (rest l))))))
Run Code Online (Sandbox Code Playgroud)
And then we can get carried away and decide that we want to make this general, using member:
(defun replace-things-with-thing (l things thing &key (test #'eql))
(if (null l)
l
(let ((first (first l)))
(cons (if (member first things :test test)
thing
first)
(replace-things-with-thing (rest l) things thing :test test)))))
Run Code Online (Sandbox Code Playgroud)
And all of this is avoiding the elephant in the room: none of these functions really work properly. They don't work properly because they are recursive, and in due course they will run out of stack.
If we want such a function to work in any conforming CL we need either to write them in an explicitly iterative way, or using primitives which CL warrants as working on even very long lists. To do that we need to think about a different algorithm. Here's that algorithm:
Given a function f of one argument defined so that:
X or Y the value is Z;Then map this function over the list.
And this is what mapcar does. So now:
(defun replace-x/y-with-z (l)
(mapcar (lambda (e)
(case e
((x y) 'z)
(otherwise e)))
l))
Run Code Online (Sandbox Code Playgroud)
And this is a nice easy-to-understand function which does what we want.
And again we can generalise this:
(defun replace-things-with-thing (l things thing &key (test #'eql))
(mapcar (lambda (e)
(if (member e things :test test)
thing
e))
l))
Run Code Online (Sandbox Code Playgroud)
This is a pretty nice, general, solution I think: I like this answer best
Of course, if we were using a language which, at the language level, promised to turn suitable recursive procedures into iterative ones, we could write a tail-recursive version of this and feel all clever. But in fact the version using mapcar is nicer I think, because tail-recursive versions of these things return their results backwards and they then have to be reversed. But there's still a place where we can write a nice, naturally-tail-recursive function which will help: member.
An element is a member of a list if:
And we can write this function:
(defun memp (elt list &key (test #'eql))
(if (null list)
nil
(or (funcall test elt (first list))
(memp elt (rest list) :test #'eql))))
Run Code Online (Sandbox Code Playgroud)
This function is tail recursive, and thus any implementation which eliminates tail calls will turn it into iterative code. We could use this function above instead of member (note that it does not return quite the same result that member does: member returns the tail of the list).
And finally, if you want to get all fancy and worry about performance you can write memp in such a way that you don't have to keep worrying about keyword arguments &c, using a local function:
(defun memp (elt list &key (test #'eql))
(labels ((memp-loop (tail)
(if (null tail)
nil
(or (funcall test elt (first tail))
(memp-loop (rest tail))))))
(memp-loop list)))
Run Code Online (Sandbox Code Playgroud)
This idiom is so common in Scheme – a language which does eliminate tail calls – that there is special syntax for it (below code is Racket):
(define (mem? elt lst #:test (test? eqv?))
(let mem-loop ([tail lst])
(if (null? tail)
#f
(or (test? elt (first tail))
(mem-loop (rest tail))))))
Run Code Online (Sandbox Code Playgroud)
Finally, let's look at an approach which I'll call the extremist-language-learning approach: let's assume that we really want to implement all of the functionality we need to do this ourselves, relying only on very basic facilities provided by the language. In particular we're going to allow ourselves these facilities:
cond & if (we could easily just use one of these);null, first, rest, cons;eql (provided as an argument);funcall because CL is a Lisp-2 (not needed in a Lisp-1);labels – we could define all the helper functions globally but it's just nicer to package them all inside a single function.And in addition we'll use fancy argument processing once, for the top-level function.
We are not allowed to use any functions which map over lists, or anything like that: all those we need to write.
With those restrictions we end up with something like this horror:
(defun replace-things-with-thing (l things thing &key (test #'eql))
;; an absurdly purist, tail recursive version
(labels ((memp (e tail)
;; is E in TAIL compared using TEST. In real life this
;; would be MEMBER
(cond ((null tail)
nil)
((funcall test e (first tail))
t)
(t (memp e (rest tail)))))
(rev (tail accum)
;; reverse TAIL. In real life this would be REVERSE
(if (null tail)
accum
(rev (rest tail) (cons (first tail) accum))))
(replace-loop (tail accum)
;; the implementation of the function
(if (null tail)
;; we're done: the reverse of ACCUM is the answer
(rev accum '())
(let ((e (first tail)))
(replace-loop (rest tail)
(cons (if (memp e things)
thing
e)
accum))))))
(replace-loop l '())))
Run Code Online (Sandbox Code Playgroud)
I think you could argue that writing something like this helps you learn Lisp. I'm not sure it's true though.
(And you can go further than this of course: the real purist would use the Y combinator here, and if you think that helps you learn the language, well.)
| 归档时间: |
|
| 查看次数: |
116 次 |
| 最近记录: |