我有以下代码:
(defun read_coords (in)
(setq x (read-line))
(if (equalp x "0")
in
(progn
(append in (cons x nil))
(read_coords in)
)
)
)
(setq coords (read_coords (cons nil nil)))
目标是读取输入行并将它们存储在列表中。问题是列表 coords
保持不变(因此只包含 NIL
)。我做错了什么?
最佳答案
这是您的代码,格式更好:
(defun read_coords (in)
(setq x (read-line))
(if (equalp x "0")
in
(progn
(append in (cons x nil))
(read_coords in))))
(setq coords (read_coords (cons nil nil)))
现在,read_coords
的返回值是多少?函数包含隐式 PROGN
,因此它是最后一种形式。在这里,最后一种形式是 IF
.根据测试的结果,返回值是 in
或 PROGN
在 else 位置的返回值。因此,测试失败时的返回值是通过调用 (read_coords in)
获得的值。当递归最终无误结束时,它必然在 IF
的 then 分支中,它返回 in
。 请注意,in
在整个执行过程中从未被修改。
确实,APPEND
根据其输入创建一个新列表。换句话说,新列表是调用 APPEND
返回的值,遗憾的是它从未存储在任何地方。计算是在没有任何副作用的情况下完成的,其结果将被丢弃。
您可能应该这样做:
(read_coords (append in (cons x nil)))
因此,新列表作为参数传递给递归调用。
备注
(setq x (read-line))
不要使用 SETQ
定义局部变量。您需要使用 LET
- 在这里绑定(bind)。否则,您将以依赖于实现的方式更改全局词法范围(除非您定义了一个名为 x
的全局变量,这很糟糕,因为它违反了特殊变量的命名约定,应该有*耳罩*
)。在函数内改变全局变量可能使其不可重入,这是非常糟糕的。
(cons x nil)
为了构建一个只有一个元素的列表,只需使用 LIST
:
(list x)
最后,请注意您不应在名称中使用下划线,而应使用破折号。因此,您的函数应该命名为 read-coords
,或者更好的是 read-coordinates
。
(equalp x "0")
即使上面是正确的,在这里使用 string=
可能会更好,因为你知道 READ-LINE
返回一个字符串。
性能
您一次又一次地附加一个列表,它为每个被读取的元素创建一个副本。对于可以使用线性算法完成的任务,您的代码在时间和空间使用方面是二次方的。
此外,您正在使用尾递归函数,其中简单的迭代会更清晰、更惯用。我们通常不在 Common Lisp 中使用尾递归过程,因为该语言提供了迭代控制结构,并且尾合并优化不能保证始终应用(优化不是强制性的(这是一件好事)不会阻止实现提供它,即使它可能需要用户的额外声明)。最好使用 LOOP
这里:
(defun read-coordinates (&optional (input-stream *standard-input*))
(loop for line = (read-line input-stream nil nil)
until (or (not line) (string= line "0"))
collect line))
我们在参数中传递一个输入流,默认为*STANDARD-INPUT*
.然后 READ-LINE
读取行并忽略错误(感谢第一个 NIL)。如果发现错误,比如当我们到达文件末尾时,返回值为 NIL(多亏了第二个 NIL)。当读取的行为 NIL 或等于 “0”
时,LOOP
结束。循环将所有被读取的连续行累积到一个列表中。
关于list - 将列表作为函数参数传递并从函数返回列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40939053/