Lectures
Lecture 1: The Essence of Objects
Lecture 2: Unions of Objects
Lecture 3: Classes of Objects:   Data Definitions
Lecture 4: Classes of Objects:   Interface Definitions
Lecture 5: Interface Design:   Independent and Extensible
Lecture 6: Parametric Interface Definitions and Methods
Lecture 7: Introducing Java:   Syntax and Semantics
Lecture 8: Union, Interfaces, and Lists in Java
Lecture 9: Testing in Java
Lecture 10: Parametric Interfaces in Java
Lecture 11: Computations on Many Structural Arguments:   Double Dispatch
Lecture 12: Parameterized Types and Double Dispatch; Abstracting Values
Lecture 13: Abstracting Computation with Function Objects
Lecture 14: Function Objects & Parameterized Types; Anonymous Classes & Lambda
Lecture 15: The Fundamental List Abstraction:   Fold
Lecture 17: Midterm Review
Lecture 16: Properties of Equality:   Reflexive, Symmetric, Transitive, and Total
Lecture 19: Structural Equality with Double Dispatch; Abstracting and Overridding
Lecture 18: More Double Dispatch
Lecture 22: Optional, Maps, Sets, and Lifting Default Code to Abstract Classes
Lecture 23: The Visitor Pattern
Lecture 24: Implementing Visitors; Bank Accounts
Lecture 25: Imperatives:   Implicit Communication via Side-Effects
Lecture 26: Aside:   List Exercises
Lecture 27: Imperatives:   Cyclic Data
Lecture 28: Imperatives:   Methods over Cylic Data
Lecture 29: BSTs, Maps, The Law of Hash  Code, and Comparable vs Comparators
Lecture 30: Random access and Array  Lists
Lecture 31: Implementing Hash Tables
Lecture 32: Resizing Hash Tables
Lecture 33: Simple Iterators
Lecture 34: List Iterators and Iterator Combinators
Lecture 35: List Iterators and Iterator Combinators
Lecture 36: Zippers
Lecture 37: Naive Tree Iterators
Lecture 38: Efficient Pre-Order Tree Iterators
Lecture 39: Drills
Lecture 40: Drill Solutions
Lecture 41: Wrap-up
On this page:
1 Definitions
2 Concepts
3 Hash code
4 Comparisons
5 Abstract classes
6 Types
7 Tree traversals
8 Computing with iteration
9 Lambda expressions
10 Methods for lists
11 Double dispatch
12 Graphs
13 Visitors
6.12

Lecture 40: Drill Solutions

Video.

1 Definitions

Give a brief definition of
  • interface

    Answer: definition for a set of objects described by method signatures objects in the set must implement.

  • class

    Answer: definition for a set of objects described by fields and methods objects in the set have and a procedure for consucting such objects.

  • object

    Answer: a value with fields and methods.

  • abstract class

    Answer: a class definition which does not allow for instances to be constructed directly.

  • iterator

    Answer: a mutable object that represents an iteration of a set of values.

  • comparator

    Answer: an object that represents a procedure for determining the relative order of two objects.

  • anonymous inner class

    Answer: a short-hand for locally defining an instance of a class without naming the class.

  • lambda expression

    Answer: a short-hand for locally defining an instance of a class that has one method without naming the class or method.

  • visitor

    Answer: an object that represents a computation over the value it visits.

2 Concepts

Describe two ways in which an abstract class and a class differ.

Answer: abstract classes cannot be constructed (directly). Abstract classes do not need to implement all the methods of the interfaces they implement.

What does it mean when
  • a class implements an interface

    Answer: the class implements the method signatures defined in the interface.

  • a class extends a class

    Answer: the class inherits the fields, methods, and constructors of the class it extends.

  • an interface extends an interface

    Answer: the interface includes all of the signatures of the interface it extends.

3 Hash code

If two objects have the same hash code, what can you conclude about the equality of those objects (assuming their equals and hashCode methods are correct)?

Answer: nothing.

4 Comparisons

What’s the difference between a comparator and comparable object?

Answer: a comparable object implements a {\tt compareTo} method that compares it with a given object; a comparator object implements a {\tt compare} method that compares two given objects (and is thus independent of the objects being compared).

5 Abstract classes

Describe a situation in which writing an abstract class is justified.

Answer: two classes which implement the same interface have some methods that are identical.

6 Types

Is this program well-typed?

Listof<Integer> is = new Cons <>(5, new Empty<>());

System.out.println(is.first);

Is this program well-typed?

Answer: no. (Even though it would run without error.)

Pairof<Integer, Boolean> p = null;

System.out.println(p.left);

Answer: yes. (Even though it will crash when run.)

7 Tree traversals

Suppose you had a binary search tree and you want to produce a list of elements in the tree in sorted order. Which traversal do you want?

Answer: LNR.

8 Computing with iteration

Implement the following method:

// Compute the average of all the numbers in it.

// Assume: it has at least one number

Double avg(Iterable<Double> it) { ... }

Answer:

 

// Compute the average of all the numbers in it.

// Assume: it has at least one number

Double avg(Iterable<Double> it) {

  Inteter ct = 0;

  Double sum = 0.0;

  for (Double d : it) {

    ct = ct + 1;

    sum = sum + d;

  }

  return sum / ct;

}

 

9 Lambda expressions

Rewrite the following to use lambda expression notation:

Function<Double,Double> dbl = new Function<>() {

    public Double apply(Double d) {

        return d * d;

    }

};

Answer:

Function<Double,Double> dbl = d -> d * d;

10 Methods for lists

Assume the following interface exists:

// Functions X -> Optional<Y>

interface OptFun<X, Y> extends Function<X, Optional<Y>> {}

Design the following methods for Listof<X>:

// Produce the list of elements in this list which satisfy p

Listof<X> filter(Predicate<X> p);

 

// Produce the list of results for which f produces something in this list

<R> Listof<R> filterMap(OptFun<X,R> f);

 

// Produce the first element in this list for which p produces true

Optional<X> findPred(Predicate<X> p);

Answer:

// In Empty

 

    public <R> Listof<R> filter(Predicate<X> p) {

        return new Empty<>();

    }

 

    public <R> Listof<R> filterMap(OptFun<X, R> f) {

        return new Empty<>();

    }

 

    public <R> Optional<R> findPred(Predicate<X> p) {

        return Optional.empty();

    }

 

// In Cons

 

    public <R> Listof<R> filter(Predicate<X> p) {

        return p.test(this.first) ?

               new Cons<>(this.first, this.rest.filter(p)) :

               this.rest.filter(p);

    }

 

    public <R> Listof<R> filterMap(OptFun<X, R> f) {

        Optional<X> r = f.apply(this.first);

        return r.isPresent() ?

               new Cons<>(r.get(), this.rest.filterMap(f)) :

               this.rest.filterMap(f);

    }

 

    public <R> Optional<R> findPred(Predicate<X> p) {

        return p.test(this.first) ?

               Optional.of(this.first) :

               this.rest.findPred(p);

    }

Show that you can define filter in terms of filterMap:

abstract class AListof<X> implements Listof<X> {

  // Produce the list of elements in this list which satisfy p

  public Listof<X> filter(Predicate<X> p) {

    ...

  }

}

Answer:

// In AListof

 

    public Listof<X> filter(Predicate<X> p) {

        return this.filterMap(x -> p.test(x) ?

               Optional.of(x) :

               Optional.empty());

    }

11 Double dispatch

When is it justified to use the double dispatch pattern?

Answer: when computing something in terms of two values belonging to union data definitions.

Assume the following definitions:

interface BT<X> {

  // Produce the tree obtained by following p in this tree

  BT<X> follow(BTPath p);

}

 

class Leaf<X> implements BT<X> { ...usual defn...}

 

class Node<X> implements BT<X> { ...usual defn...}

 

// Interp: a path through a binary tree

interface BTPath {}

 

// Interp: end of a path

class End implements BTPath {}

 

// Interp: go left, followed by path rest

class Left implements BTPath {

    BTPath rest;

    Left(BTPath rest) {

        this.rest = rest;

    }

}

 

// Interp: go right, followed by path rest

class Right implements BTPath {

    BTPath rest;

    Right(BTPath rest) {

        this.rest = rest;

    }

}

Using the double dispatch pattern, design the follow method. You may raise an exception if following a path leads you off the end of a binary tree.

Answer:

 

// In Leaf

 

    public BT<T> follow(BTPath p) {

        return p.followLeaf(this);

    }

 

// In Node

 

    public BT<T> follow(BTPath p) {

        return p.followNode(this);

    }

 

 

// Interp: a path through a binary tree

interface BTPath {

    <X> BT<X> followLeaf(Leaf<X> l);

    <X> BT<X> followNode(Node<X> l);

}

 

// Interp: end of a path

class End implements BTPath {

    public <X> BT<X> followLeaf(Leaf<X> l) {

        return l;

    }

 

    public <X> BT<X> followNode(Node<X> n) {

        return n;

    }

}

 

// Interp: go left, followed by path rest

class Left implements BTPath {

    BTPath rest;

    Left(BTPath rest) {

        this.rest = rest;

    }

 

    public <X> BT<X> followLeaf(Leaf<X> l) {

        throw new RuntimeException("ran off end");

    }

 

    public <X> BT<X> followNode(Node<X> n) {

        return n.left.follow(this.rest);

    }

}

 

// Interp: go right, followed by path rest

class Right implements BTPath {

    BTPath rest;

    Right(BTPath rest) {

        this.rest = rest;

    }

 

    public <X> BT<X> followLeaf(Leaf<X> l) {

        throw new RuntimeException("ran off end");

    }

 

    public <X> BT<X> followNode(Node<X> n) {

        return n.right.follow(this.rest);

    }

}

 

12 Graphs

Here is a representation for directed graphs:

// A node in a directed graph

class GNode {

    String name;

    Listof<GNode> neighbors;

 

    GNode(String name) {

        this.name = name;

        this.neighbors = new Empty<>();

    }

 

    // EFFECT: add the given node to this node's neighbor

    public void addNeighbor(GNode n) {

        this.neighbors = new Cons<>(n, this.neighbors);

    }

 

    // Is there a path from this node to the given node?

    public Boolean hasPath(GNode to) { ... }

}

Design the hasPath method. Here some examples:

void testGraph(Tester t) {

    // +---------+

    // v         |

    // A--->B--->C-->D

    //      +--->E-->F

    //

    GNode a = new GNode("A");

    GNode b = new GNode("B");

    GNode c = new GNode("C");

    GNode d = new GNode("D");

    GNode e = new GNode("E");

    GNode f = new GNode("F");

 

    a.addNeighbor(b);

    b.addNeighbor(c);

    c.addNeighbor(d);

    b.addNeighbor(e);

    e.addNeighbor(f);

    c.addNeighbor(a);

 

    t.checkExpect(a.hasPath(a), true);

    t.checkExpect(a.hasPath(b), true);

    t.checkExpect(a.hasPath(e), true);

    t.checkExpect(e.hasPath(d), false);

}

Answer:

// Is there a path from this node to given node?

public Boolean hasPath(GNode to) {

    return this.hasPathAcc(to, new Empty<>());

}

 

public Boolean hasPathAcc(GNode to, Listof<GNode> seen) {

    return !seen.exists(n -> n.name.equals(this.name)) &&

            this.equals(to) ||

            this.neighbors.exists(n ->

                    n.hasPathAcc(to, new Cons<>(this, seen)));

}

13 Visitors

Recall the following method for Listof<X>:

// Is this list sorted in ascending order according to c?

Boolean isSorted(Comparator<X> c);

It can be implemented with an accumulator helper method that remembers the prior element in the list.

Suppose this method was not available for Listof<X> and you couldn’t make any further changes to Listof<X>. Design a visitor that computes the same thing as this method.

class IsSorted<X> implements ListVisitor<X, Boolean> {

    Comparator<X> c;

 

    IsSorted(Comparator<X> c) {

        this.c = c;

    }

 

    public Boolean visitEmpty(Empty<X> e) {

        return true;

    }

 

    public Boolean visitCons(Cons<X> c) {

        return c.rest.accept(new IsSortedAcc(c.first));

    }

 

    class IsSortedAcc implements ListVisitor<X, Boolean> {

        X prior;

 

        IsSortedAcc(X prior) {

            this.prior = prior;

        }

 

        public Boolean visitEmpty(Empty<X> e) {

            return true;

        }

 

        public Boolean visitCons(Cons<X> cons) {

            return c.compare(prior, cons.first) <= 0 &&

                    cons.rest.accept(new IsSortedAcc(cons.first));

        }

    }

}