我正在努力学习计划编程。我的教授说我一直陷入程序性思维的陷阱。我知道他是对的。我想学习如何做到这一点,然而,阅读书籍对我来说变得非常直观,因为这些应该是简单的项目,不需要大量的阅读和研究。
从概念上讲,我理解我的跟踪功能应该如何。 (虽然我可能错了) 在这种情况下,我正在处理一个名为ndelete的程序,该程序接收一个列表并返回列表,并删除每个第n个元素。
下面我跟踪了我认为我的功能应该是什么样子。
ndelete( '( a b c d e) 2)
(a b c d e)
> (b c d e)
> > (c d e)
> > > (d e)
> > > > (e)
> > > > > ( )
< < < < < ( ) ; return null
< < < < (e) ; return e 1
< < < (e) ; return 'd 2 removed 'd,
< < (c e); return 'c 3
< (c e) ; return 'b 4 removed'b
(a c e) ; return 'a 5
我觉得我应该很容易理解这一点,这非常令人沮丧。也许,有人可以指出我正确的方向或告诉我如何利用我在概念上理解的东西。
这是我尝试过的代码。我使用一些我认为可以从我教授的例子中使用的示例代码随意地把它放在一起。
(define x '(1 2 3 4 5 6 7 8 9 10))
(define ndelete(lambda (alist n)
(if '()
(car alist)
(ndelete (cdr alist)
(- n 1))
)))
(ndelete x 10)
答案 0 :(得分:2)
通常的做法是尝试从一个例子中概括,提供:
你的例子不太受限制。您希望确保涵盖所有功能案例。跟踪是有益的,因为不同的情况通常发生在递归函数的不同深度。此外,您可能需要尝试多个示例来信任您的解决方案。
您确信跟踪代表了实际可能的执行,并且不仅仅是制造错误(例如,跳过某个步骤或突然返回不同的结果)。你要记住,痕迹可能形成得很糟糕。您最终会通过仔细检查并将您的功能与您的示例对比来确定这是否属实。
不要尝试一次编写所有内容,从您看到和可以推断的内容逐步构建您的功能。我相信大多数经验丰富的函数式程序员仍然会执行以下步骤,但可能只是在他们的脑海中(随着时间的推移,模式将开始出现)。
在这里,您知道您会收到一个列表和一个号码:您称之为lst
和n
,因为这是其他人在Scheme中的表现。您还知道该函数应返回一个列表。
(a b c d e)
> (b c d e)
...
< (c e)
(a c e)
(cdr lst)
递归调用该函数作为list参数。(car lst)
。您可以撰写初稿:
(define ndelete
(lambda (lst n)
(cons (car lst) (ndelete (cdr lst) n))))
但这还不够,因为你的函数没有解释跟踪的其他部分,比如:
> > > > > ( )
< < < < < ( )
显然,空列表应该有一个基本案例。 我们更新功能:
(define ndelete
(lambda (lst n)
(if (null? lst)
'()
(cons (car lst) (ndelete (cdr lst) n)))))
这是另一个不一致的地方:
> > > (d e)
> > > > (e)
....
< < < < (e)
< < < (e)
你永远不会cons
。实际上,只有在跟踪中有偶数<
时才会执行此操作,这对应于调用函数时偶数个>
。您理解2应该推广到n
,并且您必须添加一个计数器。
您可能想重复使用现有的n
,但对于初稿,请添加另一个参数。如果真的可以从n
派生,那么您将有机会稍后重写。
必要时,必须将计数器从一个级别传递(和修改)到另一个级别。我不会在这里复制粘贴它,但结果是Chris Jester-Young's answer。他使用局部函数,但这是可以在之后完成的重构。