被 Lisp 列表比较搞糊涂了
Confused by Lisp list comparison
下面是我的 Lisp 程序,其中比较了列表和列表。一个是根据用户输入生成的,一个是预先生成的 table 的一部分。我单独测试了设置输入的功能;在那里,(isValidMove '(0 0))
returns 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
在 GETUSERMOVE
中,您将 MOVE
绑定为计算 READ-LINE
的结果。 READ-LINE
returns 一个 字符串 而不是 列表 。 (参考:http://www.lispworks.com/documentation/HyperSpec/Body/f_rd_lin.htm)
因此,ISVALIDMOVE
永远不会计算为 T
,因为字符串永远不会 EQUALP
到列表。
要将您的字符串转换为列表,您需要调用 EVAL
。但对用户输入的数据使用 EVAL
时要小心!
让我们通过代码修复问题:
(setf boardValues (make-array '(3 3)
:initial-element " ")
)
不要使用SETF
声明全局变量。使用 DEFVAR
或 DEFPARAMETER
。全局变量是特殊的:为了表明这一点,使用"ear-muffs"。此外,您不需要 Common Lisp 中的注释来说明变量或函数的用途:定义它们时使用文档字符串:
(defvar *board-values* (make-array '(3 3) :initial-element " ")
"Board values stores x/o and defaults to \" \"")
此外,使用 Common Lisp 命名约定(没有驼峰式大小写,使用 -
分隔单词;恕我直言,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)))))
我们逐行来看:
- 使用 CL 名称约定
- 我们将从字符串中读取。 CL有reader宏,强大但危险的东西。通过将
*READ-EVAL*
设置为 NIL
,我们抑制了可能在读取时发生的评估。
- 将用户输入读入变量
MOVE
:READ-LINE
将接受用户输入,return 输入的字符串,READ-FROM-STRING
会将字符串转换为 Lisp 数据.因此,如果用户输入“(1 2)”,MOVE
将包含列表 (1 2)
.
- 我们对有效着手执行多项操作,如果无效则什么都不做。在这种情况下最好使用
WHEN
而不是 IF
:不需要 PORGN
并且与 IF
相比更准确地显示我们在做什么。在 CL 中更习惯于命名谓词以 -P
结尾而不是前面有 IS
,因此 ISVALIDMOVE
被重命名为 VALID-MOVE-P
.
- 这里我假设原始程序有一个错误:你在
VALIDMOVES
上调用 AREF
,这不是一个数组。我假设您想更改董事会的状态。请注意,坐标是使用 MOVE
的 CAR
和 CADR
获取的:MOVE
是列表,而不仅仅是一对,实际上它具有 (CONS X (CONS Y NIL))
的形式。所以,要得到Y
需要取(CAR (CDR MOVE))
或(CADR MOVE)
或(SECOND MOVE)
; (CDR MOVE)
将 return (Y)
- 不是数组的有效索引。
REMOVE
不影响原序列!即使你使用破坏性的 DELETE
你也不应该依赖它改变原始序列,而是使用 returned 值。因此,删除移动后需要重新分配 *VALID-MOVES*
值。此外,需要为 REMOVE
提供不同的相等性测试,因为 (EQL (LIST 1 2) (LIST 1 2))
是 NIL
.
- 递归调用自身。也许,更惯用的 CL 代码会使用
LOOP
,但这里的递归并没有错。
注意:我稍微改变了一个函数的语义:它 return 是一个无效的动作。否则,用户必须中断评估才能停止。更好的办法是有一个特殊的退出输入(符号QUIT
或Q
?)并在无效移动的情况下重新提示输入。
最后,函数 VALID-MOVE-P
在 CL 中非常简单:
(defun valid-move-p (move)
(member move *valid-moves* :test #'equalp))
代替手动遍历列表,可以使用原始(在某种意义上"provided by CL standard")函数MEMBER
。
现在可以使用了:
CL-USER> (get-user-move)
(0 1)
(0 2)
(10 20)
NIL
CL-USER> *board-values*
#2A((" " X X) (" " " " " ") (" " " " " "))
CL-USER> *valid-moves*
((0 0) (1 0) (1 1) (1 2) (2 0) (2 1) (2 2))
PS。代码仍然远非理想,但它有效。
下面是我的 Lisp 程序,其中比较了列表和列表。一个是根据用户输入生成的,一个是预先生成的 table 的一部分。我单独测试了设置输入的功能;在那里,(isValidMove '(0 0))
returns 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
在 GETUSERMOVE
中,您将 MOVE
绑定为计算 READ-LINE
的结果。 READ-LINE
returns 一个 字符串 而不是 列表 。 (参考:http://www.lispworks.com/documentation/HyperSpec/Body/f_rd_lin.htm)
因此,ISVALIDMOVE
永远不会计算为 T
,因为字符串永远不会 EQUALP
到列表。
要将您的字符串转换为列表,您需要调用 EVAL
。但对用户输入的数据使用 EVAL
时要小心!
让我们通过代码修复问题:
(setf boardValues (make-array '(3 3)
:initial-element " ")
)
不要使用SETF
声明全局变量。使用 DEFVAR
或 DEFPARAMETER
。全局变量是特殊的:为了表明这一点,使用"ear-muffs"。此外,您不需要 Common Lisp 中的注释来说明变量或函数的用途:定义它们时使用文档字符串:
(defvar *board-values* (make-array '(3 3) :initial-element " ")
"Board values stores x/o and defaults to \" \"")
此外,使用 Common Lisp 命名约定(没有驼峰式大小写,使用 -
分隔单词;恕我直言,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)))))
我们逐行来看:
- 使用 CL 名称约定
- 我们将从字符串中读取。 CL有reader宏,强大但危险的东西。通过将
*READ-EVAL*
设置为NIL
,我们抑制了可能在读取时发生的评估。 - 将用户输入读入变量
MOVE
:READ-LINE
将接受用户输入,return 输入的字符串,READ-FROM-STRING
会将字符串转换为 Lisp 数据.因此,如果用户输入“(1 2)”,MOVE
将包含列表(1 2)
. - 我们对有效着手执行多项操作,如果无效则什么都不做。在这种情况下最好使用
WHEN
而不是IF
:不需要PORGN
并且与IF
相比更准确地显示我们在做什么。在 CL 中更习惯于命名谓词以-P
结尾而不是前面有IS
,因此ISVALIDMOVE
被重命名为VALID-MOVE-P
. - 这里我假设原始程序有一个错误:你在
VALIDMOVES
上调用AREF
,这不是一个数组。我假设您想更改董事会的状态。请注意,坐标是使用MOVE
的CAR
和CADR
获取的:MOVE
是列表,而不仅仅是一对,实际上它具有(CONS X (CONS Y NIL))
的形式。所以,要得到Y
需要取(CAR (CDR MOVE))
或(CADR MOVE)
或(SECOND MOVE)
;(CDR MOVE)
将 return(Y)
- 不是数组的有效索引。 REMOVE
不影响原序列!即使你使用破坏性的DELETE
你也不应该依赖它改变原始序列,而是使用 returned 值。因此,删除移动后需要重新分配*VALID-MOVES*
值。此外,需要为REMOVE
提供不同的相等性测试,因为(EQL (LIST 1 2) (LIST 1 2))
是NIL
.- 递归调用自身。也许,更惯用的 CL 代码会使用
LOOP
,但这里的递归并没有错。
注意:我稍微改变了一个函数的语义:它 return 是一个无效的动作。否则,用户必须中断评估才能停止。更好的办法是有一个特殊的退出输入(符号QUIT
或Q
?)并在无效移动的情况下重新提示输入。
最后,函数 VALID-MOVE-P
在 CL 中非常简单:
(defun valid-move-p (move)
(member move *valid-moves* :test #'equalp))
代替手动遍历列表,可以使用原始(在某种意义上"provided by CL standard")函数MEMBER
。
现在可以使用了:
CL-USER> (get-user-move)
(0 1)
(0 2)
(10 20)
NIL
CL-USER> *board-values*
#2A((" " X X) (" " " " " ") (" " " " " "))
CL-USER> *valid-moves*
((0 0) (1 0) (1 1) (1 2) (2 0) (2 1) (2 2))
PS。代码仍然远非理想,但它有效。