Stacks

Course links

Exercise 1

Revise the flatten procedure from the reading on deep recursion so that it uses a ``to-do'' stack of unprocessed subtrees, like the second version of sum-of-number-tree in today's reading. How will the order of the :push! messages affect the result of the procedure?

Exercise 2

Some authors add a sixth operation to the definition of the stack ADT: size, which returns the number of elements in the stack. Extend the Scheme implementation of make-stack in the reading so that the stacks it constructs will accept the message :size and perform this operation when it is received.

Exercise 3

Documents on the World Wide Web usually contain special strings, called tags, that serve as instructions to the browser about what the document contains, how it is structured, and how the text should be displayed. In many cases, tags occur in pairs: The opening tag marks the beginning of a region of text that constitutes some natural unit within the document structure or should be displayed in some special way, and closing tag marks the end of that region.

An opening tag is a sequence of letters and digits enclosed between a less-than character at the beginning and a greater-than character at the end. For instance, "<html>" is the opening tag for a document that contains hypertext markup, and "<title>" is the opening tag for the title of such a document. The corresponding closing tags look almost the same, but a closing tag has a slash character after the less-than character: "</html>", "</title>".

Create a new stack and name it tags. Push onto this stack the opening tag "<html>". Next, push "<head>", the tag that begins the header of a hypertext document, and then "<title>". Now pop the stack. The tag that appears is the one that must be matched first by a closing tag in order for the tags to be correctly nested. Pop the stack two more times and confirm that the stack is a ``last-in, first-out'' data structure.

Exercise 4

Firefox and other browsers use a stack of tags like this one -- a stack containing tags that must eventually be matched but have not been matched yet -- to determine whether the HTML document to be displayed is correctly constructed. Write a Scheme procedure correctly-nested? that takes a list of HTML opening and closing tags and determines whether they are correctly nested.

> (correctly-nested? '("<html>" "<head>" "<title>" "</title>" "</head>" "<body>" "<b>" "</b>" "</body>" "</html>"))
#t
> (correctly-nested? '("<html>"  "<head>" "</html>" "</head>"))
#f

Additional stack utilities

If you use stacks frequently, you will find that several other operations on them are useful enough to write up once and for all and keep in a library. Here are a few that one might consider:

Now that we understand how objects work, we are confronted in these cases with a design issue: Should we implement these as free-standing procedures that stand outside the stacks that they operate on and send messages to them, or should we extend the repertoire of messages that a stack can understand to include :display, :equal?, :copy, etc.?

One difficulty with implementing them as free-standing procedures is that, in that case, we will have to pop each element off the stack in order to examine the ones after it. However, ordinarily, we would not want to destroy the stack in order to display it, test it for equality with another stack, etc. So each of these procedures would have to store the popped elements temporarily and push them all back on again to restore the stack to the state in which it arrived.

Such an approach is less efficient than adding more messages to the repertoire of messages that stacks can handle, since the internal implementation of a :display could simply traverse the underlying list, without using :push! and pop! at all. On the other hand, every message that is added to the interface makes it that much harder to reason about stacks, to guarantee that stack invariants are always satisfied, and to reimplement stack objects with a different underlying data structure.

Try implementing one or more of the operations listed above, first as a standalone procedure, then as a new stack message. What information would be it be useful for a programmer to have in order to decide which implementation is better and how to resolve the design issue?

Acknowledgements

I am indebted to Professor Henry Walker for some of the exercises in this lab.