2016-02-27 123 views
0

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

;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 
+1

读取行返回一个字符串。看起来你正在将它与数字进行比较,而不是先转换它。 –

+0

风格点:请不要在他们自己的路线上留下拖尾。此外,评论说明表达式的末尾是不必要的。缩进和paren匹配显示表达式。 – verdammelt

+0

我会记住这一点。我个人觉得更难阅读,但我会适应Lisp的风格。 – Klladdy

回答

1

让我们通过代码固定的问题:

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

不要使用SETF声明全局变量。使用DEFVARDEFPARAMETER。全局变量为特殊:为了表明这一点,请使用“耳罩”。此外,您不需要在Common Lisp中讲述一个变量或函数是什么意见为:定义使用DOC-字符串时,其中:

(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))))) 

让我们来看看它行由行:

  1. 使用CL命名惯例
  2. 我们将从字符串中读取。 CL有阅读器宏,功能强大但危险的东西。通过将*READ-EVAL*设置为NIL,我们可以抑制在读取时可能发生的评估。
  3. 将用户输入读入变量MOVEREAD-LINE将取用户输入并返回一个字符串的输入,READ-FROM-STRING将字符串转换为Lisp-data。所以如果用户输入“(1 2)”,MOVE将包含列表(1 2)
  4. 我们对有效移动执行多项操作,如果无效,则不执行任何操作。在这种情况下,最好使用WHEN而不是IF:不需要PORGN,并且与IF相比,更精确地显示我们正在做的事情。在CL中比较习惯的用-P结尾而不是前面的IS来命名谓词,因此ISVALIDMOVE被重命名为VALID-MOVE-P
  5. 这里我假设原来的程序有一个错误:你打电话AREFVALIDMOVES这不是一个数组。我假设你想改变董事会的状态。请注意,使用CAR,的CADR获取坐标:MOVE不仅是一对的列表,实际上它具有(CONS X (CONS Y NIL))的形式。所以,要得到Y需要采取(CAR (CDR MOVE))(CADR MOVE)(SECOND MOVE); (CDR MOVE)将返回(Y) - 不是数组的有效索引。
  6. REMOVE不影响原始序列!即使你使用破坏性的DELETE,你也不应该依靠它改变原始序列,而是使用返回的值。因此,在移除移动后需要重新指定*VALID-MOVES*的值。此外,由于(EQL (LIST 1 2) (LIST 1 2))NIL,因此需要提供REMOVE的不同平等测试。
  7. 递归调用自己。也许,更习惯的CL代码将使用LOOP,但这里的递归没有任何问题。

注意:我已经改变了函数的语义:它返回一个无效的移动。否则,用户必须中断评估以停止。更好的办法是有一个退出的特殊输入(符号QUITQ?),并在无效移动的情况下重新提示输入。

最后,功能VALID-MOVE-P是相当微不足道在CL:

(defun valid-move-p (move) 
    (member move *valid-moves* :test #'equalp)) 

不用手动通过列表行走,可以使用原语(在“由CL标准提供”有义)函数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。代码仍然远离理想,但它的工作原理。

1

GETUSERMOVE绑定MOVE是评估READ-LINE的结果。 READ-LINE返回一个字符串不是列表。 (ref:http://www.lispworks.com/documentation/HyperSpec/Body/f_rd_lin.htm

因此ISVALIDMOVE永远不能评估到T给定一个字符串永远不会EQUALP列表。

要将字符串转换为列表,您需要致电EVAL。但要谨慎使用用户输入的数据EVAL

+0

谢谢你指出。 – Klladdy

+0

你不需要使用'eval','read'就足够了。你甚至不需要,你也可以使用'split-sequence'和'parse-integer'。 – Svante