如何导出函数段?
如何构造函数 segs
返回列表中所有连续段的列表? 例如,(segs '(list))
应该产生以下答案:
(() (t) (s) (s t) (i) (i s) (i s t) (l) (l i) (l i s) (l i s t))
我对如何按照HtDP中描述的设计原则解决这个问题特别感兴趣(不,这不是书上的问题,欢迎大家讨论!)如何解决?程序推导时应遵循哪些原则?
How can I construct the function segs
that returns a list of all contiguous segments in a list?
For example, (segs '(l i s t))
should produce the following answer:
(() (t) (s) (s t) (i) (i s) (i s t) (l) (l i) (l i s) (l i s t))
I'm especially interested in how to solve this problem in accordance with the design principles described in HtDP (no, this is not the problem from the book, so please feel free to discuss it!) How to solve it? Which principles to use in program derivation?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
首先构建一组相关的示例,首先是最简单的:
通过查看示例,您可以进行观察(我使用格式来突出显示):每个示例的答案都包含答案中的所有元素对于前面的例子。事实上,非空列表的连续子序列只是其尾部的连续子序列加上列表本身的非空前缀。
因此,将 main 函数搁置起来,编写
non-empty-prefixes
有了该辅助函数,就可以轻松编写 main 函数。
(可选)生成的朴素函数的复杂性很差,因为它重复调用
非空前缀
。考虑(segs (cons head tail))
。它调用(non-empty-prefixes tail)
两次:一次是因为它调用(segs tail)
,后者又调用(non-empty-prefixes tail)
>,再次因为它调用(non-empty-prefixes (cons head tail))
,它递归地调用(non-empty-prefixes tail)
。这意味着朴素函数具有不必要的复杂性。问题是
(segs tail)
计算(non-empty-prefixes tail)
然后忘记它,所以(segs (cons head tail))
code> 必须重做工作。解决方案是通过将segs
和non-empty-prefixes
融合到计算两个答案的单个函数中来保留这些额外信息:然后定义
segs 作为适配器函数,只删除第二部分。这解决了复杂性的主要问题。
(编辑添加)关于
segs+ne-prefixes
:这是定义非空前缀
的一种方法。 (注意:空列表没有非空前缀。不需要引发错误。)并且
segs
看起来像这样:您可以像这样融合它们:
这个函数仍然遵循设计配方(或如果我展示了我的测试,它会的):特别是,它使用列表上的结构递归模板。 HtDP 不讨论
values
和let-values
,但您可以使用辅助结构来对信息进行分组,从而完成相同的操作。HtDP 稍微讨论了复杂性,但这种计算重组通常在算法课程的“动态编程和记忆”下更多地讨论。请注意,融合这两个函数的另一种方法是记忆
非空前缀
;这也可以解决复杂性问题。最后一件事:靠近末尾的
append
的参数应该反转为(append ne-prefixes segs-of-rest)
。 (当然,这意味着重写所有测试以使用新顺序,或者编写/查找顺序不敏感的列表比较函数。)尝试在大型列表(大约 300-400 个元素)上对函数的两个版本进行基准测试,看看你是否能分辨出差异,并看看你是否能解释它。 (这更多是算法材料,而不是设计。)Start by building up a set of related examples, most trivial first:
By looking at the examples you can make an observation (which I've used the formatting to highlight): the answer for each example includes the all of the elements from the answer for the previous example. In fact, the contiguous subsequences of a non-empty list are just the contiguous subsequences of its tail together with the non-empty prefixes of the list itself.
So put the main function on hold and write
non-empty-prefixes
With that helper function, it's easy to write the main function.
(Optional) The resulting naive function has bad complexity, because it repeats calls to
non-empty-prefixes
. Consider(segs (cons head tail))
. It calls(non-empty-prefixes tail)
twice: once because it calls(segs tail)
which calls(non-empty-prefixes tail)
, and again because it calls(non-empty-prefixes (cons head tail))
which calls(non-empty-prefixes tail)
recursively. That means the naive function has unnecessarily bad complexity.The problem is that
(segs tail)
computes(non-empty-prefixes tail)
and then forgets it, so(segs (cons head tail))
has to redo the work. The solution is to hold on to that extra information by fusingsegs
andnon-empty-prefixes
into a single function that computes both answers:Then define
segs
as an adapter function that just drops the second part. That fixes the main issue with the complexity.(Edited to add) Regarding
segs+ne-prefixes
: here's one way to definenon-empty-prefixes
. (Note: the empty list has no non-empty prefixes. No need to raise an error.)And
segs
looks like this:You can fuse them like this:
This function still follows the design recipe (or it would, if I had shown my tests): in particular, it uses the template for structural recursion on a list. HtDP doesn't talk about
values
andlet-values
, but you could do the same thing with an auxiliary structure to group the information.HtDP talks about complexity a little bit, but this sort of regrouping of computation is usually discussed more in an algorithms course, under "dynamic programming and memoization". Note that an alternative to fusing the two functions would have been to memoize
non-empty-prefixes
; that also would have fixed the complexity.One last thing: the arguments to
append
near the end should be reversed, to(append ne-prefixes segs-of-rest)
. (Of course, that means rewriting all of the tests to use the new order, or writing/finding an order-insensitive list comparison function.) Try benchmarking the two versions of the function on a large list (around 300-400 elements), see if you can tell a difference, and see if you can explain it. (This is more algorithms material, not design.)正在进行两次递归:第一次从左侧砍掉原子,第二次从右侧砍掉原子。下面是我如何用两个函数递归地解决这个问题,用简单的术语来说(因为我不太熟悉Scheme):
累积所有结果,你就很优秀了。
There's 2 recursions going on: the first chops atoms off the left, and the second chops atoms off the right. Here's how I'd solve it recursively in 2 functions, in plain terms (since I'm not fluent in Scheme):
Accumulate all results, and you're golden.