将列表作为函数参数传递并从函数返回列表

kar*_*sss -1 lisp list common-lisp

我有以下代码:

(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)))
Run Code Online (Sandbox Code Playgroud)

目标是读取输入行并将它们存储在列表中.问题是列表coords保持不变(因此仅包含NIL).我究竟做错了什么?

cor*_*ump 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)))
Run Code Online (Sandbox Code Playgroud)

现在,回报价值是read_coords多少?函数包含隐式PROGN,因此它是最后一种形式.这里,最后一个表格是IF.根据测试结果,返回值是或者在else位置in的返回值.因此,测试失败时的返回值是通过调用获得的值.当递归最终没有错误结束时,它必然在返回的then分支中.请注意,在整个执行过程中永远不会修改.PROGN(read_coords in)IFinin

实际上,APPEND根据其输入创建了一个新的列表.换句话说,新列表是调用返回的值APPEND,遗憾的是,它永远不会存储在任何地方.计算完成没有任何副作用,其结果被丢弃.您可能应该这样做:

(read_coords (append in (cons x nil)))
Run Code Online (Sandbox Code Playgroud)

因此,新列表作为参数传递给递归调用.

备注

(setq x (read-line))
Run Code Online (Sandbox Code Playgroud)

不要SETQ用于定义局部变量.你需要在LET这里使用-binding.否则,您将以与实现相关的方式更改全局词法范围(除非您定义了一个名为的全局变量x,这是不好的,因为它违反了特殊变量的命名约定,应该具有这个特性*earmuffs*).在函数内部变换全局变量使其可能不可重入,这非常糟糕.

(cons x nil)
Run Code Online (Sandbox Code Playgroud)

要使用一个元素构建列表,只需使用LIST:

(list x)
Run Code Online (Sandbox Code Playgroud)

最后,请注意,您不应在名称中使用下划线,而是选择短划线.因此,您的功能应该被命名read-coords,或者更好read-coordinates.

(equalp x "0")
Run Code Online (Sandbox Code Playgroud)

尽管上面的说法是正确的,但最好在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))
Run Code Online (Sandbox Code Playgroud)

我们在参数中传递输入流,默认为*STANDARD-INPUT*.然后READ-LINE在忽略错误的同时读取行(感谢第一个NIL).如果发现错误,例如当我们到达文件结尾时,返回的值是NIL(由于第二个NIL).LOOP读取行的结尾为NIL或等于"0".循环将所有连续的行累积到列表中.