Functional programming 河内塔中圆盘移动的惯用功能方法

Functional programming 河内塔中圆盘移动的惯用功能方法,functional-programming,scheme,towers-of-hanoi,Functional Programming,Scheme,Towers Of Hanoi,我正在学习这个计划,作为一个玩具例子,我正在为河内塔做一个解决方案验证(而不是求解)。 我想使用一种纯粹的功能性风格(只是为了进入思维模式),我将塔表示为三个列表的简单列表。开始状态可以如下所示: “((01234)() 我该如何实现一个函数,它接受一个状态、一个源索引和一个目标索引并返回新状态?在命令式风格中,这将是一些琐碎的事情,如: state[target].push(state[source].pop()) 但我能想到的每一个功能解决方案都非常复杂。例如: (define (make

我正在学习这个计划,作为一个玩具例子,我正在为河内塔做一个解决方案验证(而不是求解)。 我想使用一种纯粹的功能性风格(只是为了进入思维模式),我将塔表示为三个列表的简单列表。开始状态可以如下所示: “((01234)()

我该如何实现一个函数,它接受一个状态、一个源索引和一个目标索引并返回新状态?在命令式风格中,这将是一些琐碎的事情,如:

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个。

这里有一个非常简单的方法。我不确定光盘“数字”在您的实现中的意义,但我让它的行为与您的答案相同,按下并弹出它们

(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))))
如果您假设存在一个带有索引的
映射
函数,该函数在许多语言和库中都是标准的,但没有内置到Scheme中,那么您可以将每个塔上的底层操作集汇总到对该函数的调用中,这样会更干净

一般来说,试着把纯函数降到尽可能低的级别,以实现您想要的功能。在这个解决方案中,我发明了一个纯函数“altertower”,它可以在单个塔上返回命令的结果,这使得解决方案的其余部分非常简单

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

编辑:例如,这里有一个版本使用了的列表理解:

(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 (lambda (tower idx) (alter-tower tower idx disc)) state)

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

谢谢!不过,我希望有更简单的事情。似乎你让我的解决方案变得更简单了,部分原因是你让它变得不那么通用(对塔的数量进行硬编码)。我将对问题进行编辑,以便更清楚地表明我更喜欢概括性。这些数字是磁盘大小,因此不能用较大的数字表示较小的数字。正如我所提到的,表达这一点的普通功能方式是将每个塔的硬编码更改为考虑索引的
映射。我会马上编辑我的答案,告诉你我的意思。好了,很好,我选择你的答案。我想也许“真正”的方式是做一些与我所做的完全不同的事情。可以将关联列表与状态((0(01 2 3))(1())(2())一起使用,并使用源索引和目标索引生成新的Alist。