河内的塔与名为光盘

时间:2011-10-10 17:27:38

标签: common-lisp towers-of-hanoi

对于作业,我必须使用命名光盘在Common LISP中创建河内塔。我需要获得看起来像这样的输出:

[1]> (hanoi '(Small Medium Large))
Moved SMALL from Peg 1 to Peg 3
Moved MEDIUM from Peg 1 to Peg 2
Moved SMALL from Peg 3 to Peg 2
Moved LARGE from Peg 1 to Peg 3
Moved SMALL from Peg 2 to Peg 1
Moved MEDIUM from Peg 2 to Peg 3
Moved SMALL from Peg 1 to Peg 3
NIL
[2]> peg1
NIL
[3]> peg2
NIL
[4]> peg3
(Small Medium Large)

然而,当我运行我创建的程序时,我得到这样的输出:

[1]> (hanoi '(Small Medium Large))
Move SMALL from Peg 1 to Peg 2
Move SMALL from Peg 1 to Peg 2
Move NIL from Peg 2 to Peg 2
Move SMALL from Peg 1 to Peg 2
Move NIL from Peg 2 to Peg 1
Move NIL from Peg 2 to Peg 2
Move SMALL from Peg 1 to Peg 2
NIL
[2]> peg1
(Small Medium Large)
[3]> peg2
NIL
[4]> peg3
NIL

这是我的代码:

(defvar *peg1* '())
(defvar *peg2* '())
(defvar *peg3* '())

(defun peg-name (peg)
     (cond ((equal peg *peg1*) "Peg 1")
     ((equal peg *peg2*) "Peg 2")
     ((equal peg *peg3*) "Peg 3")))

(defun move-disk (from to)
     (format t "Move ~a from ~a to ~a~%" (first from) (peg-name from) (peg-name to))
     (push (pop from) to))

(defun transfer (n source aux dest)
     (if (> n 0)
          (progn
          (transfer (1- n) source dest aux)
          (move-disk source dest)
          (transfer (1- n) aux source dest))))

(defun hanoi (disk-list)
     (setq *peg1* disk-list)
     (transfer (length disk-list) *peg1* *peg2* *peg3*))

代码的问题显然是移动磁盘功能,因为它只是在调用后丢弃结果。但我不确定我究竟能够确定哪些全局变量应该推送和弹出。我已经摆弄使用大型列表来表示塔并将其作为子列表,但我有同样的问题来确定要修改的列表的哪个部分。任何帮助,将不胜感激。我觉得自己处于完全死胡同。

4 个答案:

答案 0 :(得分:1)

代码很容易修复。但是你的解决方案并不是最好的方式,因为钉子是全局变量。

代码中的主要混淆是列表和变量之间的混淆。像PUSH和POP这样的宏正在处理“位置”,如符号值,变量或对象的插槽。直接使用列表无法按预期工作。

(defvar *peg1* '())
(defvar *peg2* '())
(defvar *peg3* '())

确保比较符号,而不是值。

(defun peg-name (peg)
  (cond ((equal peg '*peg1*) "Peg 1")
        ((equal peg '*peg2*) "Peg 2")
        ((equal peg '*peg3*) "Peg 3")))

由于我们传递符号,我们需要弹出并推送到符号的值。

(defun move-disk (from to)
  (let ((disc (pop (symbol-value from))))
    (format t "Move ~a from ~a to ~a~%" disc (peg-name from) (peg-name to))
    (push disc (symbol-value to))))

(defun transfer (n source aux dest)
  (when (> n 0)
    (transfer (1- n) source dest aux)
    (move-disk source dest)
    (transfer (1- n) aux source dest)))

传递符号,而不是列表。重置其他钉子也很有用。

(defun hanoi (disk-list)
  (setq *peg1* disk-list)
  (setq *peg2* '())
  (setq *peg3* '())
  (transfer (length disk-list) '*peg1* '*peg2* '*peg3*))

测试:

CL-USER 15 > (hanoi '(Small Medium Large))
Move SMALL from Peg 1 to Peg 3
Move MEDIUM from Peg 1 to Peg 2
Move SMALL from Peg 3 to Peg 2
Move LARGE from Peg 1 to Peg 3
Move SMALL from Peg 2 to Peg 1
Move MEDIUM from Peg 2 to Peg 3
Move SMALL from Peg 1 to Peg 3
NIL

CL-USER 16 > *peg3*
(SMALL MEDIUM LARGE)

CL-USER 17 > *peg1*
NIL

答案 1 :(得分:0)

这里的基本问题是所有函数都在变量peg1 peg2和peg3的内容上运行,而不是在变量本身上运行。在peg-name函数中,我们最初的peg2和peg3都是equals和eq,因为它们都是NIL所以这种给出名字的逻辑不起作用。同样,push和pops正在修改move-disk中的fromto变量,但对全局列表没有任何作用。

您需要找到一种不同的方式来传递列表名称。基本上是某种实际数组或键 - >值映射而不是硬编码变量,因此您可以传递密钥来修改正确的列表。

你还可以考虑一个更纯粹的功能性解决方案,它将peg的名称与其内容一起传递(并使用cons,car和cdr而不是push和pop)。这将完全避免导致所有麻烦的可变赋值运算符。

答案 2 :(得分:0)

“简单”修复是使用列表向量作为你的钉子,然后传递你正在操纵的钉子的索引。

这会让你的MOVE-DISK功能像:

(defun move-to (from to)
   (push (pop (aref *pegs* from)) (aref *pegs* to))

其他修改应该非常简单,我认为这是基础。

答案 3 :(得分:0)

首先,如果我们只想生成运动序列,我们不需要保持任何内部状态;以下是无副作用:

(defun hanoi (disk-list)
  (labels ((transfer (i source aux dest)
             (when (< 0 i)
               (transfer (1- i) source dest aux)
               (move (1- i) source dest)
               (transfer (1- i) aux source dest)))
           (move (disk source dest)
             (format t "Move ~A from Peg ~A to Peg ~A~%"
                     (elt disk-list disk) source dest)))
    (transfer (length disk-list) 1 2 3)))

示例:

CL-USER> (hanoi '(small medium large))
Move SMALL from Peg 1 to Peg 3
Move MEDIUM from Peg 1 to Peg 2
Move SMALL from Peg 3 to Peg 2
Move LARGE from Peg 1 to Peg 3
Move SMALL from Peg 2 to Peg 1
Move MEDIUM from Peg 2 to Peg 3
Move SMALL from Peg 1 to Peg 3

其次,如果我们确实想要跟踪状态变化,那么将状态保持在一个地方而不是将其分布在许多全局变量上更为可取:

(defun hanoi* (disk-list)
  (let ((state (list disk-list nil nil)))
    (labels ((transfer (i source aux dest)
               (when (< 0 i)
                 (transfer (1- i) source dest aux)
                 (move (1- i) source dest)
                 (transfer (1- i) aux source dest)))
             (move (disk source dest)
               (format t "Move ~A from Peg ~A to Peg ~A~%"
                       (elt disk-list disk) (1+ source) (1+ dest))
               (push (pop (elt state source)) (elt state dest))
               (show state))
             (show (state)
               (format t "~{  |~{~A~}~%~}" (mapcar #'reverse state))))
      (show state)
      (transfer (length disk-list) 0 1 2))))

示例:

CL-USER> (hanoi* '(#\▂ #\▄ #\█))
  |█▄▂
  |
  |
Move ▂ from Peg 1 to Peg 3
  |█▄
  |
  |▂
Move ▄ from Peg 1 to Peg 2
  |█
  |▄
  |▂
Move ▂ from Peg 3 to Peg 2
  |█
  |▄▂
  |
Move █ from Peg 1 to Peg 3
  |
  |▄▂
  |█
Move ▂ from Peg 2 to Peg 1
  |▂
  |▄
  |█
Move ▄ from Peg 2 to Peg 3
  |▂
  |
  |█▄
Move ▂ from Peg 1 to Peg 3
  |
  |
  |█▄▂