Recursive Data Structures
Read this page. In the previous unit of our course we studied recursive algorithms. Recursion is a concept that also applies to data. Here we look at recursive data structures - lists, trees, and sets. A list is a structure that consists of elements linked together. If an element is linked to more than one element, the structure is a tree. If each element is linked to two (sub) elements, it is called a binary tree. Trees can be implemented using lists, as shown in the resource for this unit. Several examples of the wide applicability of lists are presented. A link points to all the remaining links, i.e. the rest of the list or the rest of the tree; thus, a link points to a list or to a tree - this is data recursion.
The efficiency of the programming process includes both running time and size of data. This page discusses the latter for recursive lists and trees.
Lastly, why read the last section on sets? Sets are another recursive data structure and the last section 2.7.6, indicates their connection with trees, namely, a set data type can be implemented in several different ways using a list or a tree data type. Thus, the programming process includes implementation decisions, in addition, to design or algorithm decisions. Each of these types of decisions is constrained by the features of the programming language used. The decision choices, such as which data structure to use, will impact efficiency and effectiveness of the program's satisfaction of the program's requirements.
Note: You will notice an unusual use of C++ here. What the author is doing is showing how to pass a fixed-value data-structure as a calling argument.
7. But why?
Now, we argued above that we've neatly separated the concerns by making three separate functions, instead of interleaving dividing two-dimensional squares into regions, rotating regions, and then reassembling two-dimensional squares.
But the converse side of this is that what we're doing is now a lot less efficient: We're recursing through our data structures three separate times, instead of once. And frankly,
designed such that the
divide function breaks things up, and the
puts them back together, so these concerns are already mostly separate once we use
multirec instead of a bespoke recursive function.
One reason to break the logic up into three separate functions would be if we want to do lots of different kinds of things with quadtrees. Besides rotating quadtrees, what else might we do?
Well, we might want to superimpose one image on top of another. This could be part of an image editing application, where we have layers of images and want to superimpose all the layers to derive the finished image for the screen. Or we might be implementing Conway's Game of Life, and might want to 'paste' a pattern like a glider onto a larger universe.
Let's go with a very simple implementation: We're only editing black-and-white images, and each 'pixel' is either a
⚫️. If we use two-dimensional arrays to represent our images, we need to iterate over every 'pixel' to perform the superimposition:
const superimposeCell = (left, right) => (left === '⚫️' || right === '⚫️') ? '⚫️' : '⚪️'; const superimposeRow = (left, right) => [...zipWith(superimposeCell, left, right)]; const superimposeArray = (left, right) => [...zipWith(superimposeRow, left, right)]; const canvas = [ ['⚪️', '⚪️', '⚪️', '⚪️'], ['⚪️', '⚪️', '⚪️', '⚪️'], ['⚪️', '⚪️', '⚪️', '⚫️'], ['⚪️', '⚪️', '⚪️', '⚫️']]; const glider = [ ['⚪️', '⚪️', '⚪️', '⚪️'], ['⚪️', '⚫️', '⚪️', '⚪️'], ['⚫️', '⚪️', '⚪️', '⚪️'], ['⚫️', '⚫️', '⚫️', '⚪️']]; superimposeArray(canvas, glider) //=> ([ ['⚪️', '⚪️', '⚪️', '⚪️'], ['⚪️', '⚫️', '⚪️', '⚪️'], ['⚫️', '⚪️', '⚪️', '⚫️'], ['⚫️', '⚫️', '⚫️', '⚫️'] ])
Seems simple enough. How about superimposing a quadtree on a quadtree?