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?
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-prefixesWith 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 fusingsegsandnon-empty-prefixesinto a single function that computes both answers:Then define
segsas 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
segslooks 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
valuesandlet-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
appendnear 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.)