我有以下代码:
(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
)。我做错了什么?
答案 0 :(得分:5)
这是您的代码,具有更好的格式:
(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
或 else 位置的PROGN
的返回值。因此,测试失败时的返回值是通过调用(read_coords in)
获得的值。当递归最终没有错误结束时,它必然位于IF
的 then 分支中,返回in
。 请注意,in
在整个执行期间永远不会被修改。
确实,APPEND
根据其输入创建了一个新的列表。换句话说,新列表是对APPEND
的调用返回的值,遗憾的是,它永远不会存储在任何地方。计算完成没有任何副作用,其结果被丢弃。
你应该这样做:
(read_coords (append in (cons x nil)))
因此,新列表将作为参数传递给递归调用。
(setq x (read-line))
不要使用SETQ
来定义局部变量。你需要在这里使用LET
- 绑定。否则,您将以与实现相关的方式更改全局词法作用域(除非您定义了一个名为x
的全局变量,这是不好的,因为它违反了特殊变量的命名约定,它应该具有{{1 }})。在函数内部变换全局变量使其可能不可重入,这非常糟糕。
*earmuffs*
要使用一个元素构建列表,只需使用LIST
:
(cons x nil)
最后,请注意,您没有在名称中使用下划线,更喜欢破折号。因此,您的函数应该命名为(list x)
,或者更好,read-coords
。
read-coordinates
即使上面的内容是正确的,最好在这里使用(equalp x "0")
,因为您知道READ-LINE
会返回一个字符串。
您正在反复添加列表,这会为正在读取的每个元素创建一个副本。对于可以使用线性算法完成的任务,您的代码在时间和空间使用方面是二次的。
此外,您正在使用尾递归函数,其中简单的迭代将更清晰,更惯用。我们通常不会在Common Lisp中使用尾递归过程,因为该语言提供了迭代控制结构,并且因为不能保证始终应用尾部合并优化(优化不是强制性的(这是一件好事)不会防止实现提供它,即使它可能需要用户的其他声明)。最好在这里使用LOOP
:
string=
我们在参数中传递输入流,默认为*STANDARD-INPUT*
。然后(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))
读取行而忽略错误(感谢第一个NIL)。如果发现错误,例如当我们到达文件结尾时,返回的值是NIL(由于第二个NIL)。当读取的行是NIL或等于READ-LINE
时,LOOP
结束。循环将所有连续的行累积到列表中。
答案 1 :(得分:3)
append
不修改其参数,即,因为您没有使用它的值,所以您没有做任何事情。
(defun read-coords (&optional accumulation)
(let ((x (read-line)))
(if (string= x "0")
(nreverse accumulation) ; done
(read-coords (cons x accumulation)))))
(defvar *coords* (read-coords))