【问题标题】:Idiomatic functional way to move disc in Towers of Hanoi在河内塔中移动圆盘的惯用功能方式
【发布时间】:2011-09-28 15:42:30
【问题描述】:

我正在学习 Scheme,作为一个玩具示例,我正在为 Hanoi Towers 做一个解决方案验证器(而不是求解器)。 我想使用纯粹的功能风格(只是为了进入心态),我将塔表示为三个列表的简单列表。起始状态可能如下所示: '((0 1 2 3 4) () ())

如何实现一个接受状态、源索引和目标索引并返回新状态的函数?在命令式风格中,这将是微不足道的,例如:

state[target].push(state[source].pop())

但我能想到的每一个功能性解决方案都非常复杂。例如:

(define (makeMove state source target)
  (letrec ((recMake (lambda(tower pos disc)
              (if (null? tower) '()
              (cons (if (eqv? pos source)
                    (cdr (car tower))
                    (if (eqv? pos target) 
                    (cons disc (car tower))
                    (car tower)))
                (recMake (cdr tower)
                     (+ pos 1)
                     disc))))))
    (recMake state 0 (car (list-ref state source)))))

这似乎可行,但必须有更好的方法。我想地图会比递归好一些,但还是太多了。如果我用不同的方式表示状态会更容易吗?

另外,请随意批评我的代码。我真的不知道我在做什么。

编辑: 如果可能的话,我希望你不要假设塔的数量总是 3。

【问题讨论】:

    标签: functional-programming scheme towers-of-hanoi


    【解决方案1】:

    这是一种超级简单的方法。我不确定光盘“数字”在您的实现中的意义是什么,但我让它的行为与您的答案相同,推送和弹出它们。

    (define (make-move state source target)
      (define (alter-tower tower index disc)
        (cond ((= index source) (cdr tower))       ; remove a disc
              ((= index target) (cons disc tower)) ; add a disc
              (else tower)))                       ; this tower wasn't changed
      (let ((disc (car (list-ref state source))))
        (let ((s0 (alter-tower (list-ref state 0) 0 disc))
              (s1 (alter-tower (list-ref state 1) 1 disc))
              (s2 (alter-tower (list-ref state 2) 2 disc)))
          (list s0 s1 s2))))
    

    如果您假设存在 map-with-index 函数,该函数在许多语言和库中是标准的,但未内置在 Scheme 中,那么您可以将每个塔上的底部操作集汇总为对该函数的调用,并且会干净很多。

    一般来说,尝试提出尽可能低的纯函数来满足您的需求。在这个解决方案中,我发明了一个纯函数“alter-tower”,它可以在单个塔上返回您的命令结果,这就是解决方案的其余部分非常简单的原因。

    由于您要求提供反馈,我注意到= 在应用于数字时与eqv? 相同,内部defines 在Scheme 中工作并按照您的预期行事(例如,您可以递归调用它们)和Lisp 中通常的命名约定是用连字符分隔多词标识符,而不是使用驼峰式。 祝你好运!

    编辑:例如,这是一个使用Racket 的列表推导的版本:

    (define (make-move state source target)
      (define (alter-tower tower index disc)
        (cond ((= index source) (cdr tower))       ; remove a disc
              ((= index target) (cons disc tower)) ; add a disc
              (else tower)))                       ; this tower wasn't changed
      (let ((disc (car (list-ref state source))))
        (for/list ([(tower idx) (in-indexed state)])
          (alter-tower tower idx disc))))
    

    许多函数式语言都有一个map,它可以接受一个消耗索引的谓词,所以这两行可能看起来像:

    (map (lambda (tower idx) (alter-tower tower idx disc)) state)
    

    因此,根据您的 Scheme 方言和库,它可能会有所不同。 (我认为这没有 SRFI,但我可能弄错了。)或者你总是可以自己写上面版本的map

    【讨论】:

    • 谢谢!不过,我希望有更简单的东西。看来您通过降低通用性(硬编码塔的数量)使我的解决方案更简单。我将编辑问题以更清楚地表明我更喜欢一般性。这些数字是光盘大小,因此不能将较大的数字转换为较小的数字。
    • 就像我提到的,表达这一点的普通函数方式是将硬编码的 for-each-tower 事物更改为考虑索引的map。我稍后会编辑我的答案,向您展示我的意思。
    • 好的,很好,我选择你的答案。我认为也许“真正的”方式是做一些与我正在做的完全不同的事情。也许使用状态为 ((0 (0 1 2 3)) (1 ()) (2 ())) 的关联列表,并使用源和目标索引来生成新的 alist。
    猜你喜欢
    • 2016-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-01
    • 2013-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多