Differences
This shows you the differences between two versions of the page.
— |
people:mlsmith:infinite-trees [2020/05/01 14:13] (current) mlsmith created |
||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Infinite Trees ====== | ||
+ | |||
+ | ((lecture notes adapted and used with permission from Kathi Fisler)) | ||
+ | |||
+ | ===== 1 Motivation ===== | ||
+ | |||
+ | Up until now, we’ve mostly looked at trees as a data structure, but we haven’t looked at them in the context of any particular problems. Trees are used in many applications in computer science, robotics, and game design. One common application is for representing the possible moves in a game or search problem. Imagine that you were exploring the space that a player (character or robot) could move through. On each move, the player would change position (and possible acquire the ability to do new tasks based on location – we’ll set that aside for now). | ||
+ | |||
+ | Imagine that you wanted to run programs to optimize the movement of your player through the space. You would need a representation of the space, a way to determine the new position after each move, and a way to estimate which moves now could yield the best payoff a few moves down the road. Trees are a useful data structure here: each node contains data about the present location (where it is, its features, etc), each type of move yields a new location (aka node). For example, consider a grid-based simple game in which a player can only move between adjacent squares (ie, forward, backward, left, right). This yields a location tree like: | ||
+ | <code text> | ||
+ | | ||
+ | / / | ||
+ | < | ||
+ | / / \ \ | ||
+ | ----- / \ ------- | ||
+ | / / | ||
+ | < | ||
+ | </ | ||
+ | (Ignore that we have <4,8> twice in this picture – we’ll return to that tomorrow.) | ||
+ | |||
+ | The problem with building this tree upfront is that set of possible game/robot configurations (or positions) tends to be large, if not (effectively) infinite. In this case, creating the tree upfront simply isn’t feasible. Instead, we need to create the tree in a demand-driven fashion: we create each node when we need it, remembering just enough information to let us create more nodes as necessary. | ||
+ | |||
+ | Our task today is to figure out how to develop infinite trees in Java. To simplify the problem, we will work simply with binary trees, each of which has an integer for its datum. The left child of each node is the node with data value minus 1; the right child is the node with data value plus 1. In other words: | ||
+ | <code text> | ||
+ | 0 | ||
+ | / \ | ||
+ | -1 1 | ||
+ | / | ||
+ | | ||
+ | ... ... | ||
+ | </ | ||
+ | |||
+ | ===== 2 Modeling Infinite Trees ===== | ||
+ | |||
+ | Let’s start with our previous definition of binary trees and see how well it fits our new problem: | ||
+ | <code java> | ||
+ | interface INode {} | ||
+ | |||
+ | class Leaf implements INode { | ||
+ | Leaf(){} | ||
+ | } | ||
+ | |||
+ | class Node implements INode { | ||
+ | int data; | ||
+ | INode left; | ||
+ | INode right; | ||
+ | |||
+ | Node (int data, INode left, INode right) { | ||
+ | super(data); | ||
+ | this.left = left; | ||
+ | this.right = right; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Examples { | ||
+ | INode myTree = new Node(4, | ||
+ | new Node(3, new Leaf(), new Leaf()), | ||
+ | new Leaf()); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This isn’t a great representation, | ||
+ | |||
+ | Following conventional CS terminology, | ||
+ | <code java> | ||
+ | class NodePromise implements INode { | ||
+ | int data; | ||
+ | |||
+ | NodePromise(int data) { | ||
+ | this.data = data; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Our prior example would be written as following using '' | ||
+ | <code java> | ||
+ | class Examples { | ||
+ | INode myTree = new Node(4, | ||
+ | new NodePromise(3), | ||
+ | new NodePromise(5)); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | That is, the tree has one completed node (with data 4), and two " | ||
+ | |||
+ | To demand that a node be created, we " | ||
+ | <code java> | ||
+ | private INode force() { | ||
+ | return new Node(this.data, | ||
+ | new NodePromise(this.data - 1), | ||
+ | new NodePromise(this.data + 1)); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | So if we were to run '' | ||
+ | |||
+ | ==== 2.1 The Class Hierarchy So Far ==== | ||
+ | |||
+ | To summarize, we now have the following class hierarchy for infinite trees, cleaned up with an abstract class to capture the data common to the two kinds of '' | ||
+ | <code java> | ||
+ | interface INode {} | ||
+ | |||
+ | abstract class AbsNode implements INode { | ||
+ | int data; | ||
+ | |||
+ | AbsNode(int data) { | ||
+ | this.data = data; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class Node extends AbsNode { | ||
+ | private INode left; | ||
+ | private INode right; | ||
+ | |||
+ | Node (int data, INode left, INode right) { | ||
+ | super(data); | ||
+ | this.left = left; | ||
+ | this.right = right; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class NodePromise extends AbsNode { | ||
+ | NodePromise(int data) { | ||
+ | super(data); | ||
+ | } | ||
+ | |||
+ | private INode force() { | ||
+ | return new Node(this.data, | ||
+ | new NodePromise(this.data - 1), | ||
+ | new NodePromise(this.data + 1)); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== 3 When to Force Node Creation ===== | ||
+ | |||
+ | Now that we have force, we need to decide when to use it. We said that we wanted to construct the tree "on demand", | ||
+ | |||
+ | Notice that '' | ||
+ | <code java> | ||
+ | interface INode { | ||
+ | INode getLeft(); | ||
+ | INode getRight(); | ||
+ | } | ||
+ | |||
+ | class Node extends AbsNode { | ||
+ | private INode left; | ||
+ | private INode right; | ||
+ | |||
+ | // constructor omitted | ||
+ | |||
+ | public INode getLeft() { return this.left; } | ||
+ | |||
+ | public INode getRight() { return this.right; } | ||
+ | } | ||
+ | |||
+ | class NodePromise extends AbsNode { | ||
+ | |||
+ | // constructor omitted | ||
+ | |||
+ | private INode force() { | ||
+ | return new Node(this.data, | ||
+ | new NodePromise(this.data - 1), | ||
+ | new NodePromise(this.data + 1)); | ||
+ | } | ||
+ | |||
+ | public INode getLeft() { return this.force().getLeft(); | ||
+ | public INode getRight() { return this.force().getRight(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | To convince ourselves that this does hide the details from a user, we can experiment with our interface (in DrJava, you can use the interactions window for this). | ||
+ | <code text> | ||
+ | > NodePromise root = new NodePromise(0); | ||
+ | > root.getLeft().getData() | ||
+ | -1 | ||
+ | > root.getLeft().getLeft().getData() | ||
+ | -2 | ||
+ | > root.getLeft().getRight().getLeft().getData() | ||
+ | -1 | ||
+ | > root.getLeft().getRight().getRight().getData() | ||
+ | 1 | ||
+ | </ | ||
+ | |||
+ | This assumes that we also have an '' | ||
+ | |||
+ | ===== 4 Computing the Size ===== | ||
+ | |||
+ | Of course, we might feel more confident that we aren’t constructing the whole tree if we added a method to compute its size. What if we added the following method to '' | ||
+ | <code java> | ||
+ | abstract class AbsNode implements INode { | ||
+ | |||
+ | int data; | ||
+ | |||
+ | // constructor omitted | ||
+ | |||
+ | public int getData() { return this.data; } | ||
+ | |||
+ | public int size() { | ||
+ | return 1 + getLeft().size() + getRight().size(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | If you run this method on our root example, Java eventually reports a "stack overflow" | ||
+ | |||
+ | As for termination, | ||
+ | |||
+ | As a side note: some of you may have learned about induction in high school or other courses. Induction is how we usually prove that functions over recursively-defined data are correct. Induction, however, relies on having a base case, which isn’t true here. If you are interested in the mathematics of infinite data structures, look up co-induction, | ||
+ | |||
+ | To see the " | ||
+ | <code java> | ||
+ | public int size() { return 0; } | ||
+ | </ | ||
+ | |||
+ | ===== 5 Lazy Computation and Two Kinds of Functions ===== | ||
+ | |||
+ | What you’re seeing here is a powerful programming concept known as // | ||
+ | |||
+ | Stop and think about '' | ||
+ | |||
+ | ===== 6 Looking Ahead in the Tree ===== | ||
+ | |||
+ | We mentioned that a common operation on game trees is to look ahead a few steps to help determine which of several moves to take. To wrap up this example, let’s write a '' | ||
+ | |||
+ | First, let’s agree on the interface and examples: | ||
+ | <code java> | ||
+ | interface INode { | ||
+ | int getData(); | ||
+ | INode getLeft(); | ||
+ | INode getRight(); | ||
+ | IList< | ||
+ | int size(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Calling '' | ||
+ | |||
+ | Note that nothing in this description differentiates between '' | ||
+ | <code java> | ||
+ | public IList< | ||
+ | if (depth == 0) | ||
+ | return new ConsList< | ||
+ | else | ||
+ | return this.getLeft().lookAhead(depth-1).append(this.getRight().lookAhead(depth -1)); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This code uses a method '' | ||
+ | |||
+ | ==== 6.1 Using Java’s Built-In List Classes ==== | ||
+ | |||
+ | Actually, this is a good opportunity to introduce you to Java’s built-in classes for lists. Java uses the term '' | ||
+ | <code java> | ||
+ | import java.util.LinkedList; | ||
+ | </ | ||
+ | |||
+ | The '' | ||
+ | <code java> | ||
+ | public LinkedList< | ||
+ | if (depth == 0) { | ||
+ | LinkedList< | ||
+ | newlist.add(this); | ||
+ | return newlist; | ||
+ | } | ||
+ | else { | ||
+ | LinkedList< | ||
+ | this.getLeft().lookAhead(depth - 1); | ||
+ | LinkedList< | ||
+ | this.getRight().lookAhead(depth - 1); | ||
+ | leftresult.addAll(rightresult); | ||
+ | return leftresult; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | First, we see that the class name for a built-in list is '' | ||
+ | <code java> | ||
+ | newlist.add(this); | ||
+ | </ | ||
+ | |||
+ | After adding this, we can return newlist. | ||
+ | |||
+ | If you try to write these three lines more compactly as | ||
+ | <code java> | ||
+ | return new LinkedList< | ||
+ | </ | ||
+ | Java gives you a type error. Our '' | ||
+ | |||
+ | ==== 6.2 Testing lookAhead ==== | ||
+ | |||
+ | One way to test '' | ||
+ | <code java> | ||
+ | class Examples { | ||
+ | NodePromise root = new NodePromise(0); | ||
+ | |||
+ | boolean testLookAhead0 (Tester t) { | ||
+ | return t.checkExpect(root.lookAhead(0).size(), | ||
+ | } | ||
+ | |||
+ | boolean testLookAhead1 (Tester t) { | ||
+ | return t.checkExpect(root.lookAhead(1).size(), | ||
+ | } | ||
+ | |||
+ | boolean testLookAhead3 (Tester t) { | ||
+ | return t.checkExpect(root.lookAhead(3).size(), | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | For more detailed testing, we would want to check the contents of each node. We will hold off on that for now, as we haven’t yet shown you how to iterate over Java’s built-in lists. | ||
+ | |||
+ | ===== 7 And Some More Java Lessons... ===== | ||
+ | |||
+ | Actually, this example works well for illustrating some other Java-isms that you need to know coming out of this course. The final version of the code, posted to the website, illustrates three additional features/ | ||
+ | |||
+ | * Access modifiers: Java lets you control access to fields and methods across classes. You’ve already encountered " | ||
+ | * //Private// fields and methods are only visible inside of the class (they are not accessible in subclasses); | ||
+ | * // | ||
+ | * //Public// fields and methods are accessible to any class or object. Methods in interfaces are required to be public (that’s the spirit of being an interface). \\ \\ | ||
+ | | ||
+ | This summary really is all you need to know about public/ | ||
+ | |||
+ | * Wrapper classes: It is common to use a separate class to " | ||
+ | * Multiple Constructors: | ||
+ | |||
+ | ===== 8 Summary ===== | ||
+ | |||
+ | We’ve covered a lot of ground here. What should you understand from this lecture? | ||
+ | * That we can support infinite data by defining classes and methods to build more data as it is needed. \\ \\ | ||
+ | * How to create and augment Java’s built-in lists (iteration will come later). \\ \\ | ||
+ | * Java’s access modifiers, and when to use each. \\ \\ | ||
+ | * The idea of wrapper classes and multiple constructors to give users a need-to-know view of your data. \\ \\ | ||