Relevant Reading Material
TBD
Chapters from How to Design Classes (HtDC)
TBD
The many ways to iterate
Recall our generic List<X>
from last lecture
for
-loop
We can iterate over an instance of our generic list by using size
and a for
-loop
Main
class using our generic listpublic static void main(String[] args) {
// empty lines for more visible output in IntelliJ's Console
System.out.println();
System.out.println();
System.out.println();
List<Integer> intList = List.createEmpty();
intList = intList.add(1).add(10).add(100);
System.out.println("\tPrint list as is: " + intList);
System.out.println("\tPrint list joined with \",\": " + intList.join(SEPARATOR));
System.out.println("\nUsing for-loop");
for (int i = 0; i < intList.size(); i++) {
System.out.println("\tFor-loop element at: " + i + " is: " + intList.elementAt(i));
}
}
Print list as is: (100(10(1))) Print list joined with ",": 100, 10, 1, Using for-loop For-loop element at: 0 is: 100 For-loop element at: 1 is: 10 For-loop element at: 2 is: 1
Iterators
Iterating over a list (or a collection of objects) is a very common
task. Java provides as part of the java.util
package an interface called
Iterator
which includes the following signatures
-
hasNext()
- check if there is a next element in the list -
next()
- obtain the next element in the iteration moving our iterator one element -
remove()
- remove the current element in the underlying collection default implementation throws an exception
An iterator is not the same as the list (collection) that it is pointing to. An iterator provides a view of the collection.
So let’s implement Iterator
for our generic list. We will add a method on our List
that creates and returns our GenericListIterator
//Inside our List interface
interface List<X> {
...
Iterator<X> iterator();
...
}
// In a new file we define our GenericListIterator
public class GenericListIterator<X> implements Iterator<X> {
private AList<X> list;
public GenericListIterator(AList<X> xes) {
this.list = xes;
}
@Override
public boolean hasNext() {
return !list.isEmpty();
}
@Override
public X next() {
try {
X element = list.getFirst();
list = list.getRest();
return element;
} catch (GenericListException glex) {
throw new NoSuchElementException(glex.getMessage());
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("Don't even think about it!");
}
}
Now that we have our own iterator we can iterate over our List
Main
class using our new iterator...
System.out.println("\nUsing iterator interface");
Iterator<Integer> it = intList.iterator();
while (it.hasNext()) {
System.out.println("\tIterator element: " + it.next());
}
...
And the output
Using iterator interface Iterator element: 100 Iterator element: 10 Iterator element: 1
Notice that when using our iterator we have no access to the current index. In fact we could have implemented our iterator to provide elements in any order we want. We could even provide multiple such iterators to allow different order to iterating through the elements of our collection.
foreach
in Java
Notice how the code used to iterate over a collection that supports an Iterator
is pretty much the same
Iterator<..> it = o.iterator(); // get the iterator
while(it.hasNext()){
element = it.next();
... // compute using this element
}
Repetitions like these are good candidate for abstraction. Java has already done that for us through the Iterable
interface that is part of java.lang
. [1]
If our collection implements Iterable
then we can use the for-each
syntax, recall
for-each
...
for (X element: o) { // o is an instance of our Collection
}
...
This an even more succinct way to iterate over the list of a collection. So let’s extend our List
implementation to implement Iterable
. Thankfully we have already developed the method iterator()
which is the minimum requirement needed to implement Iterable
Iterable
public interface List<X> extends Iterable<X> {
...
}
Now we should be able to use for-each
to iterate over our list
for-each
on our lists. System.out.println("\nUsing iterable interface");
for (Integer element : intList) {
System.out.println("\tList element: " + element);
}
for-each
on our listUsing iterable interface List element: 100 List element: 10 List element: 1
Iterators are not just for lists!
We can use Iterator
s to present a view over a sequence, whether ordered or unordered. We can even use nested iterators (or higher-order iterators) to provide a custom view of an existing iterator, e.g.,
-
given an iterator that iterates over a collection 1 element at a time, create a new iterator that iterates 2 elements at a time, or,
-
given an iterator of iterators (a view of a list of lists) create an iterator that iterates over our list of lists as if it was a single, flat (non-nested) list.
Fibonacci numbers, again.
Here is an iterator that provides the next number in the Fibonacci sequence. The iterator keeps the last 2 numbers in the sequence and generates the next Fibonacci number.
public class FibIterator implements Iterator<Integer> {
Integer previous;
Integer current;
public FibIterator(Integer previous, Integer current){
this.previous = previous;
this.current = current;
}
@Override
public boolean hasNext() {
return true; // we can always get the next fibonacci number
}
@Override
public Integer next() {
Integer newCurrent = this.previous + this.current;
this.previous = this.current;
this.current = newCurrent;
return current;
}
@Override
public void remove() {
throw new UnsupportedOperationException("NO!");
}
}
Abstracting over behaviour, again.
We’ve seen in Racket how higher-order functions can be used to abstract over behaviour making our implementations more succinct and more reusable. Let’s see how we can achieve similar benefits in Java.
Add1 to our list elements
We will attempt a series of simple operations on our elements and we will use our implementation(s) to investigate how, if at all, we can abstract over behaviour. We used a similar tactic last semester with Racket.
First let’s focus on non-generic lists like List<Integer>
class Main {
public static void main(String[] args) {
List<Integer> intList = List.createEmpty();
intList = intList.add(1).add(10).add(100);
// ADD 1 to each element
List<Integer> add1List = List.createEmpty();
for (Integer element : intList) {
add1List = add1List.add(element + 1);
}
}
}
We could generalize to adding any value to each element of a List<Integer>
by writing a method outside of List<X>
class Main {
public static void main(String[] args) {
List<Integer> intList = List.createEmpty();
intList = intList.add(1).add(10).add(100);
// ADD 1 to each element
List<Integer> add1List = List.createEmpty();
for (Integer element : intList) {
add1List = add1List.add(element + 1);
}
}
private static List<integer> addN(List<Integer> list) {
List<Integer> result = List.createEmpty();
for (Integer element : list) {
result = result.add(element + 1);
}
}
}
How about,
-
Multiplying by a number
n
-
Dividing by a number
n
Even if we keep adding methods to implement these tasks, we are only dealing with List<Integer>
. It is easy to imagine useful operations
on lists that hold objects of some other type like
-
Append all the strings in a list of strings to get one string as the result
-
Join the elements of a list of string and include a separator between them
-
Apply logical or on a list of booleans, etc.
What we are aiming for here is something like map
and fold
that will allow us to pass in the operation(s) that we want to perform on the elements of our list.
Functional Objects
How can we define and create a Java value such that
-
We can pass it as an argument
-
We can return it as a result of calling a method
-
Contain code (much like a Racket function or
lambda
) that a method can call or apply to arguments
The only Java value that fits these criteria is a reference type that we can define to accept and return values of the types we want. Let’s define an interface for such a reference type.
public interface Function <I,O> {
/**
* Apply this funtction to {@code input} and return the result.
*
* @param input fucntion's input
* @return return the result of applying this function to input.
*/
O apply(I input);
}
The Function
interface is our first attempt to define a function that takes 1 input and returns 1 output.
Our input will be of some type I
and our output will be of some other type O
. It may be that I
and O
are the same
type, for example a function that consumes an integer and increments it by 1
will have
-
I
asInteger
-
O
asInteger
.
While a function that takes an Integer
and returns the letter in the English alphabet at that index will have
-
I
asInteger
-
O
asString
.
Or even a function that takes a List<List<Posn>>
and creates a Bullet
image for each Posn
will have
-
I
asList<List<Posn>>
-
O
asList<List<Bullet>>
.
Our Function
interface guarantees the existence of apply
, a method that we can use to call or apply the operation
that we have encoded as an instance of Function
.
Let’s code a couple of Function
s
Add1
as a Function
object.public class Add1 implements Function<Integer, Integer> {
@Override
public Integer apply(Integer input) {
return input + 1 ;
}
}
MapToAlphabet
as a Function
object takes an integer index
and returns the English letter found at index
in the English alphabet.public class MapToAlphabet implements Function<Integer, String> {
private String[] alphabet;
public MapToAlphabet() {
this.alphabet = new String[]{"A", "B", "C", "D", "E", "F",
"G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
"R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
}
@Override
public String apply(Integer input) {
int alphaIndex = (input % alphabet.length) - 1;
return alphabet[alphaIndex];
}
}
map
in Java
Now that we have a way to capture functions as objects (thus the name Functional Object or Functional Interface) we need a method on our
List<X>
that will take as argument a Function<I,O>
and apply that function to each element of the list, returning a new list with
the results. This is map
List<X>
that will map a function over each list element.// In List.java
public interface List<X> extends Iterable<X> {
...
/**
* Apply {@code f} to each element of the list collecting the results into a new list.
* This is a version of the higher order function `map`.
*
* @param f function object to be applied to each element
* @param <Y> type returned by f when applied to each element
* @return the list of results obtained by applying f to each list element.
*/
<Y> List<Y> applyToEach(Function<X,Y> f);
}
// In Empty.java
class Empty<X> extends AList<X> {
...
@Override
public <Y> AList<Y> applyToEach(Function<X, Y> f) {
return new Empty<Y>();
}
}
// In Cons.java
class Cons<X> extends AList<X> {
...
@Override
public <Y> AList<Y> applyToEach(Function<X, Y> f) {
return new Cons<Y>(f.apply(getFirst()), getRest().applyToEach(f));
}
}
Let’s use our new method.
Main
class using our applyToEach
methodpublic class Main {
private static final String SEPARATOR = ", ";
public static void main(String[] args) {
List<Integer> intList = List.createEmpty();
intList = intList.add(1).add(10).add(100);
System.out.println("\nFunctional Objects: Add1: "
+ intList.applyToEach(new Add1()).join(SEPARATOR));
System.out.println("\nFunctional Objects: MapToAlphabet: "
+ intList.applyToEach(new MapToAlphabet()).join(SEPARATOR))
}
}
applyToEach
Functional Objects: Add1: 101, 11, 2, Functional Objects: MapToAlphabet: V, J, A,
Recall from CS5001 the signature of map
;; map : List<X> [X -> Y] -> Y
In Racket the list on which map
operates on is passed as the first argument. In Java however the list is the object upon which we call our applyToEach
method which is List<X>
.
In Racket we use [X → Y]
in our Racket signature to denote an argument that is a function that takes one argument X
and returns one result Y
. In Java our code our method applyToEach
takes as an argument Function<X,Y>
.
In Racket map
returns a Y
. In Java applyToEach
returns Y
. [2]
fold
in Java
Now that we have seen how to reproduce a map
like method in Java, let’s also create a fold
like method in Java.
Recall from Racket the signature for fold
fold: List<X> Y [X Y -> Y] -> Y
For fold
like behaviour we will need
-
A base value
Y
, something that will be passed to our code as the result of thefold
when we hit the empty case. -
A function
[X Y → Y]
that can take two inputs-
An element of our collection
X
-
The current result of the
fold
operation up to this point (accumulator)Y
-
Returns the new value for our accumulator
Y
-
We can create a new functional interface that has an apply
method that takes 2 arguments, or, we can add a new method in our existing Functional<I,O>
interface. We will do the later.
Functional
interface to accommodate fold-like operations.public interface Function <I,O> {
/**
* Apply this funtction to {@code input} and return the result.
*
* @param input fucntion's input
* @return return the result of applying this function to input.
*/
O apply(I input);
/**
* Combines the result of applying this function to {@code input} with the current accumulator
* {@code acc}.
*
* @param input to this function
* @param acc current result (result thus far)
* @return combination of acc and result of applying this function to input.
*/
O combine(I input, O acc);
}
Lets create a couple of classes that implement our updated Functional
interface.
Add
functional object for addition.public class Add implements Function<Integer, Integer> {
private Integer val;
public Add(Integer val){
this.val = val;
}
public Add(){
this.val = 0;
}
@Override
public Integer apply(Integer input) {
return input + val;
}
@Override
public Integer combine(Integer input, Integer acc) {
return apply(input) + acc;
}
}
Multiply
functional object for multiplication.public class Multiply implements Function<Integer, Integer> {
private Integer factor;
public Multiply() {
this.factor = 1;
}
public Multiply(Integer factor) {
this.factor = factor;
}
@Override
public Integer apply(Integer input) {
return factor * input;
}
@Override
public Integer combine(Integer input, Integer acc) {
return apply(input) * acc;
}
}
Let’s make a new class MapToAlphabetList
that is similar to MapToAlphabet
but instead of returning a one element String
of the letter
in the English alphabet it returns a List<String>
.
MapToAlphabetList
functional object.public class MapToAlphabetList implements Function<Integer, List<String>> {
private MapToAlphabet mToA;
public MapToAlphabetList() {
this.mToA = new MapToAlphabet();
}
@Override
public List<String> apply(Integer input) {
List<String> result = List.createEmpty();
return result.add(mToA.apply(input));
}
@Override
public List<String> combine(Integer input, List<String> acc) {
return acc.add(mToA.apply(input));
}
}
Now let’s update our List<X>
to include a fold
like method.
foldOver
implementation.// In List.java
public interface List<X> extends Iterable<X> {
...
/**
* A fold-like operation over our list.
*
* @param combiner function to apply to each element and acc
* @param base result in the case of empty list
* @param <Y> type of value returned by the method
* @return the result of folding over the list with combiner.
*/
<Y> Y foldOver(Function<X,Y> combiner, Y base);
}
// In Empty.java
class Empty<X> extends AList<X> {
...
@Override
public <Y> Y foldOver(Function<X, Y> combiner, Y base) {
return base;
}
}
// In Cons.java
class Cons<X> extends AList<X> {
...
@Override
public <Y> Y foldOver(Function<X, Y> combiner, Y base) {
return combiner.combine(getFirst(), getRest().foldOver(combiner, base));
}
}
Let’s use our new method!
Main
class using our foldOver
method.public class Main {
private static final String SEPARATOR = ", ";
public static void main(String[] args) {
List<Integer> intList = List.createEmpty();
intList = intList.add(1).add(10).add(100);
MapToAlphabetList mToAList = new MapToAlphabetList();
System.out.println("\nfold over intList: MapToAlphabetList: "
+ intList.foldOver(mToAList, List.createEmpty()).join(SEPARATOR));
Add adder = new Add(0);
System.out.println("\nfold over inList: Sum: " + intList.foldOver(adder,0));
Multiply mul = new Multiply(1);
System.out.println("\nfold over inList: Product: " + intList.foldOver(mul,1));
Integer ans = intList.applyToEach(new Add(10))
.applyToEach(new Multiply(5))
.foldOver(new Multiply(), 1);
System.out.println("\nand the answer is ... : " + ans);
}
And the output …
foldOver
.fold over intList: MapToAlphabetList: V, J, A, fold over inList: Sum: 111 fold over inList: Product: 1000 and the answer is ... : 3025000
Graphs
Graphs are a popular representation for a lot of common problems
-
Who is connected to you on your LinkedIn account?
-
Who are all your friends on Facebook?
-
What is the shortest route from Seattle to San Francisco if we are driving?
Graphs capture connection between entities.
The entities can be simple, like a city name, or reach like course information and with their pre-requisites.
The connections between entities in a graph can also be simple, directional like a two-way street or a one way-street and can also have properties attached to them, the distance between two cities, the cost of gas to drive between two cities or the cost of the tolls along the highway.
Graphs a little more formally.
A Graph \(G\) is defined as a pair \(G = (V,E)\) where
-
\(V\) is a set of vertices.
-
\(E\) is a set of edges.
-
An edge consists is an ordered pair of vertices \((v_1, v_2)\).
-
We can represent the preceding example as
Representing Graphs
There are different ways to capture graphs using OO.
Adjacency Matrix
The idea here is to capture the connections between vertices in a 2d matrix. We map each vertex name to an index, e.g.
Then we can represent the graph in a matrix like so
We are using the symbol \(\cdot\) to show absence, typically implementations use null
.
Adjacency List
Another popular way to model a graph is using a list for each vertex to store the vertex’s direct neighbours.