我是Lisp的新手,希望通过递归实现函数length
。我编写以下代码只是为了发现它只适用于列表而不适用于字符串。
(defun mylen (l)
(if (eq (cdr l) nil)
1
(+ 1 (mylen (cdr l)))))
我想我是否可以只编写一个既能用于列表又能用于字符串的函数?
答案 0 :(得分:4)
首先,请注意Common Lisp的length
适用于任何序列,而不仅仅是列表(而不仅仅是字符串,& c。)。所以你实际根本不需要写这个。还有一些其他函数通常在序列上运行,例如map
。一般来说,如果在列表和向量上都有效,那么编写通用代码就不那么容易了。实现通常通过检查首先提供的序列类型,然后使用专用版本来实现采用任意序列的函数。在可用时使用这些功能对您有利。您可以查看HyperSpec中的17.3 Sequences Dictionary,了解更多可在任意序列上运行的函数。
您可以使用map
并实施sequence-length
,如下所示:
(defun sequence-length (sequence &aux (length 0))
(map nil (lambda (x)
(declare (ignore x))
(incf length))
sequence)
length)
上面的sequence-length
突出显示map
适用于每个reduce
。一个更惯用的解决方案是使用另一个函数来处理序列,1
,Rainer Joswig points out(此代码基于他的答案中的代码,但使用constantly
来创建函数忽略其参数并返回(defun sequence-length (sequence)
(reduce '+ sequence :key (constantly 1)))
):
CL-USER> (sequence-length '(1 2 3))
3
CL-USER> (sequence-length "foobar")
6
count-if
另一种选择是在
中使用(count-if (constantly t) sequence)
(constantly t)
计算cons
返回true的项目数,即所有项目。
您正在实现的递归类型确实依赖于(defun list-elements (sequence start end)
(if (= start end)
'()
(cons (elt sequence start)
(list-elements sequence (1+ start) end))))
单元格构建的列表,您可以轻松处理列表的第一个部分,然后休息列表。通常没有函数对序列执行此操作,因为获取向量的其余部分通常意味着获取它的副本。通常,如果需要这样的东西,该函数将采用开始和结束索引,并对这些索引进行递归,而不是对序列进行递归。如,
CL-USER> (list-elements "abcdefghijklmnop" 3 7)
(#\d #\e #\f #\g)
reduce
subseq
的大多数实现只是检查序列是列表还是向量,然后调度到以最合适的方式处理这些序列的专用版本。例如,如果你看一下SBCL's implementation of reduce
(从第1242行开始),你会看到有四种宏的实现:
和一个序列遍历:
你需要更多地了解SBCL中的定义形式,以了解它们是如何被使用的,但它们确实证明了实现通用序列函数是有点工作的。
在说完所有这些之后,我会指出你可以使用reverse-into-list
,它可以在任意序列上运行,在向量和列表上进行这种递归,但它真的效率不高。例如,这是一个简单的(defun reverse-into-list (sequence &optional (result '()))
(if (eql 0 (length sequence))
result
(reverse-into-list (subseq sequence 1)
(cons (elt sequence 0) result))))
函数。
CL-USER> (reverse-into-list "hello")
(#\o #\l #\l #\e #\h)
CL-USER> (reverse-into-list '(1 2 3))
(3 2 1)
length
使用{{1}}确定何时停止,但您可以编写一个更高效的版本,首先检查它是否是列表,如果是,则检查它是否为空(常量时间),如果它不是一个列表,只是调用长度(也应该是常量时间)。
答案 1 :(得分:3)
只是添加约书亚的回答:不是map
,请使用reduce
:
(reduce #'+ sequence :key (constantly 1))
计算字符串的长度在Lisp中是无用的,因为每个向量都将长度存储为属性 - 没有像C中那样的结束标记。因此,向量的长度 - 和字符串是向量 - 不需要在Lisp中计算。
答案 2 :(得分:1)
您的Lisp实现的标准库length
函数可能通过访问某些内部属性来处理字符串和其他向量。但是如果你想以递归的方式计算向量的长度,你最好的选择可能是coerce
他们到列表。向量本身并不是列表所用的递归数据结构。
顺便说一下,你当前的实现也是列表的错误。对于空列表,它将失败(即返回1
而不是0
)。您可以通过简化它来使其在空列表中正常工作。
答案 3 :(得分:1)
可以从序列中提取“休息”,如果它是一个向量,则只需要少量:
(defun sequence-rest (seq)
(if (listp seq)
(rest seq)
;; A full version would also care about element-type etc.
(make-array (1- (length seq)) :displaced-to seq
:displaced-index-offset 1)))
(defun mylen (seq)
(if (zerop (length seq))
1
(+ 1 (mylen (sequence-rest seq)))))