我正在学习Scheme,作为一个玩具示例,我正在为河内塔楼做一个解决方案验证器(不是解算器)。 我想使用纯粹的功能风格(只是为了进入思维模式),我将塔代表为三个列表的简单列表。起始状态可能如下所示: '((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。
答案 0 :(得分: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中通常的命名约定是用连字符分隔多字标识符,而不是使用camel-case。
祝你好运!
编辑:这是一个使用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
。