下面我正在使用我的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
答案 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
来声明全局变量。使用DEFVAR
或DEFPARAMETER
。全局变量特殊:为了表明这一点,请使用“耳罩”。此外,您不需要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)))))
让我们逐行看看:
*READ-EVAL*
设置为NIL
,我们可以取消在阅读时可能发生的评估。MOVE
:READ-LINE
将获取用户输入并返回输入字符串,READ-FROM-STRING
将字符串转换为Lisp数据。因此,如果用户输入“(1 2)”,MOVE
将包含列表(1 2)
。WHEN
而不是IF
:不需要PORGN
,并且与IF
相比,更准确地显示我们正在做的事情。在CL中使用-P
结尾命名谓词而不是前面有IS
更为惯用,因此ISVALIDMOVE
被重命名为VALID-MOVE-P
。AREF
上调用的VALIDMOVES
不是数组。我假设你想改变董事会的状态。请注意,使用CAR
的{{1}}和CADR
进行坐标:MOVE
列表不仅仅是一对,实际上它的格式为MOVE
。因此,要(CONS X (CONS Y NIL))
需要Y
或(CAR (CDR MOVE))
或(CADR MOVE)
; (SECOND MOVE)
将返回(CDR MOVE)
- 不是数组的有效索引。(Y)
不会影响原始序列!即使您使用破坏性REMOVE
,也不应该依赖它来更改原始序列,而是使用返回的值。因此,需要在移除移动后重新分配DELETE
值。此外,由于*VALID-MOVES*
为REMOVE
,因此需要为(EQL (LIST 1 2) (LIST 1 2))
提供不同的相等测试。NIL
,但这里的递归没有错。注意:我稍微改变了函数的语义:它返回无效的移动。否则,用户必须中断评估才能停止。更好的方法是为退出(符号LOOP
或QUIT
?)提供特殊输入,并在无效移动时重新提示输入。
最后,函数Q
在CL中非常简单:
VALID-MOVE-P
不是手动遍历列表,而是可以使用原语(在某种意义上“由CL标准提供”)函数(defun valid-move-p (move)
(member move *valid-moves* :test #'equalp))
。
现在可行:
MEMBER
PS。代码仍然远非理想,但它确实有效。