被Lisp列表比较困惑

时间:2016-02-27 01:37:19

标签: lisp common-lisp

下面我正在使用我的Lisp程序,并在列表和列表之间进行比较。一个是从用户输入生成的,一个是预生成的表的一部分。我分别用set输入测试功能;在那里,(isValidMove '(0 0))返回T,但是当使用 query-io 构建用于比较的列表时,我得到了错误。在任何语言中,比较总是给我带来麻烦,因为有多少不同的东西与我相同,但显然与计算机有很大不同;我认为这是我在这里遇到的同样问题。 (顺便说一下,我只包括一个较大程序的一部分)。

;Local Variables (program wide)
;Board values stores x/o and defaults to " "
(setf boardValues (make-array '(3 3)
    :initial-element " ")
);end boardValues

;List of all valid moves remaining 
(setf validMoves (list 
    (list 0 0) (list 0 1) (list 0 2) 
    (list 1 0) (list 1 1) (list 1 2) 
    (list 2 0) (list 2 1) (list 2 2)))

;Functions

;Function call the will prompt the user for input, if the move is
;not vaild, repromts for a move
(defun getUserMove ()

    (let ((move (read-line *query-io*)))
    (if (isValidMove move)
        (progn
            (setf (aref validMoves (car move) (cdr move)) 'x)
            (remove move validMoves))
            (getUserMove)))
);end getUserMove

;Function call that process the move, returns T if move is valid
;and F if move is invalid

(defun isValidMove (move)
    (dolist (m validMoves) 
    (if (equalp m move)
        (return T)))
) ;end isValidMove

2 个答案:

答案 0 :(得分:1)

GETUSERMOVE中,您绑定MOVE是评估READ-LINE的结果。 READ-LINE返回字符串而不是列表。 (参考:http://www.lispworks.com/documentation/HyperSpec/Body/f_rd_lin.htm

因此,ISVALIDMOVE永远无法评估为T,因为字符串永远不会是EQUALP列表。

要将字符串转换为列表,您需要调用EVAL。但是对于在用户输入的数据上使用EVAL时要小心!

答案 1 :(得分:1)

让我们来解决代码修复问题:

(setf boardValues (make-array '(3 3)
    :initial-element " ")
)

不要使用SETF来声明全局变量。使用DEFVARDEFPARAMETER。全局变量特殊:为了表明这一点,请使用“耳罩”。此外,您不需要Common Lisp中的注释来告诉变量或函数的用途:在定义变量或函数时使用doc-strings:

(defvar *board-values* (make-array '(3 3) :initial-element " ")
  "Board values stores x/o and defaults to \" \"")

此外,使用Common Lisp命名约定(没有驼峰的情况,使用-来分隔单词; IMHO Lisp的约定更具可读性。)

VALIDMOVES相同的问题。将您的定义替换为:

(defvar *valid-moves*
  (list '(0 0) '(0 1) '(0 2) 
        '(1 0) '(1 1) '(1 2) 
        '(2 0) '(2 1) '(2 2))
  "List of all valid moves remaining ")

现在,功能的变化更加复杂:

(defun get-user-move ()
  (let ((*read-eval* nil))
    (let ((move (read-from-string (read-line *query-io*))))
      (when (valid-move-p move)
        (setf (aref *board-values* (car move) (cadr move)) 'x)
        (setf *valid-moves* (remove move *valid-moves* :test #'equalp))
        (get-user-move)))))

让我们逐行看看:

  1. 使用CL名称约定
  2. 我们将从字符串中读取。 CL有读取器宏,功能强大但危险的东西。通过将*READ-EVAL*设置为NIL,我们可以取消在阅读时可能发生的评估。
  3. 将用户输入读入变量MOVEREAD-LINE将获取用户输入并返回输入字符串,READ-FROM-STRING将字符串转换为Lisp数据。因此,如果用户输入“(1 2)”,MOVE将包含列表(1 2)
  4. 我们对有效移动执行多个操作,如果无效则不执行任何操作。在这种情况下,更好地使用WHEN而不是IF:不需要PORGN,并且与IF相比,更准确地显示我们正在做的事情。在CL中使用-P结尾命名谓词而不是前面有IS更为惯用,因此ISVALIDMOVE被重命名为VALID-MOVE-P
  5. 这里我假设原始程序出错:您在AREF上调用的VALIDMOVES不是数组。我假设你想改变董事会的状态。请注意,使用CAR的{​​{1}}和CADR进行坐标:MOVE列表不仅仅是一对,实际上它的格式为MOVE。因此,要(CONS X (CONS Y NIL))需要Y(CAR (CDR MOVE))(CADR MOVE); (SECOND MOVE)将返回(CDR MOVE) - 不是数组的有效索引。
  6. (Y)不会影响原始序列!即使您使用破坏性REMOVE,也不应该依赖它来更改原始序列,而是使用返回的值。因此,需要在移除移动后重新分配DELETE值。此外,由于*VALID-MOVES*REMOVE,因此需要为(EQL (LIST 1 2) (LIST 1 2))提供不同的相等测试。
  7. 递归调用自己。也许,更惯用的CL代码会使用NIL,但这里的递归没有错。
  8. 注意:我稍微改变了函数的语义:它返回无效的移动。否则,用户必须中断评估才能停止。更好的方法是为退出(符号LOOPQUIT?)提供特殊输入,并在无效移动时重新提示输入。

    最后,函数Q在CL中非常简单:

    VALID-MOVE-P

    不是手动遍历列表,而是可以使用原语(在某种意义上“由CL标准提供”)函数(defun valid-move-p (move) (member move *valid-moves* :test #'equalp))

    现在可行:

    MEMBER

    PS。代码仍然远非理想,但它确实有效。