lists we’ve seen an array-based list implementation, the arraylist. advantage of an array-based...

30
Lists • We’ve seen an array-based list implementation, the ArrayList. • Advantage of an array-based implementation: – fast access to a specific index – typically less space usage than other options • Disadvantage of an array-based implementation: – can be expensive to insert items – resizing is an expensive operation

Post on 20-Dec-2015

223 views

Category:

Documents


0 download

TRANSCRIPT

Lists

• We’ve seen an array-based list implementation, the ArrayList.

• Advantage of an array-based implementation:– fast access to a specific index– typically less space usage than other options

• Disadvantage of an array-based implementation:– can be expensive to insert items– resizing is an expensive operation

Amortization

• Resizing of array-based data structures involves a tradeoff:– many insertions (as in our Bag) are very

efficient since no allocation of space is required (an array is allocated as a big block of memory)

– some insertions (as in our Bag) are very expensive since resizing must take place

Linked List

• A linked list is a list implementation which spreads out the cost of space allocation evenly to all insertions.

• Each insertion involves allocation of space

Advantages/Disadvantages

• Advantages– predictable cost of insertion– efficient insertion at any point in structure

• Disadvantages– extra space required to store links– inefficient indexing

Comparison of storage

A pair has a first and a second element

When pairs are used to construct lists,the first is called the “head” or the “car”the second is called the “tail”, “rest” or “cdr”the pair is called a “cons cell” or simply a “cons”.

Linked list implementations

• Textbook discusses a typical linked list implementation

• java.util.LinkedList is another typical implementation

• These implementations have procedural, not object-oriented, roots.

Traditional implementations

• provide a large number of methods – making it difficult to reuse if less functionality is desired;

• provide no means, beyond inheritance, to extend functionality to suit a specific situation; and

• adding functionality requires knowledge of internal structure of the list.

So…

• These implementations are inflexible, and do not exhibit good OO design.

• In lecture we will discuss a state-based list implementation which can be easily extended with new functionality.

Variant/Invariant decomposition

• Design principle which leads to cohesive and decoupled components

• That which is invariant is put into one component• Variant properties/behaviors are factored out into

separate components• Compare this to what we saw with e.g. Observer pattern:

– decoupling of event generation (invariant) from event handling (variant)

a list = a Linear Recursive Structure(LRS or LRStruct)

• What is a list?1. the empty list is a list2. a pair whose tail is a list is itself a list

• This is a recursive definition! (1) is called the base case, and (2) is called the recursive case.

• Note that traditional implementations do not follow this precise definition of what a list is:

– many have no explicit representation of an empty list– none are recursive on the list; instead they recurse on a list node

• This has implications for how the structure can support extension (see Visitor support, in later slides)

States

• A list can therefore be in one of two states:– empty (corresponding to the base case)– non-empty (corresponding to the recursive case)

• The state-based implementation we will study makes this distinction explicit in the representation

Empty vs. NonEmpty state(look Ma, no NullPointerException!)• An LRS object delegates all calls to its LRS

State object – which can respond to all messages.

• Empty and NonEmpty states respond differently.

• There is never a null pointer in the structure! (Think about the implications of this for a while.)

What is basic (invariant) list functionality?

• insert new item at front

• remove item from front

• set/get first item (head)

• set/get rest (tail)

• plus (in Java) methods inherited from Object (toString, equals, etc)

How are these methods defined?

public LRStruct<E> insertFront(E item) {

return _state.insertFront(this, item);}public E removeFront() {

return _state.removeFront(this);}public LRStruct<E> setDatum(E item) {

return _state.setDatum(this, item);}public E getDatum() {

return _state.getDatum(this);}public LRStruct<E> setRest(LRStruct<E> rest) {

return _state.setRest(this, rest);}public LRStruct<E> getRest() {

return _state.getRest(this);}

LRStruct delegates to State!

• All of these methods delegate to the state of the LRStruct.

• States polymorphically determine what happens.

An empty LRS

• LRS a = new LRS();

_state

LRS Empty

a

Inserting an item into an empty LRS

• a.insertFront(“fred”);

_state

LRS Empty

a

Inserting an item into an empty LRS

• a.insertFront(“fred”);

_state

LRS

a

Empty

Inserting an item into an empty LRS

• a.insertFront(“fred”);

_state

LRS

NonEmpty

a

Empty

_tail

_dat=“fred”

Inserting an item into an empty LRS

• a.insertFront(“fred”);

_state

LRS

NonEmpty

a

LRS

Empty

_state_tail

_dat=“fred”

Inserting an item into an empty LRS

• a.insertFront(“fred”);

_state

LRS

NonEmpty

a

LRS

Empty

_state_tail

_dat=“fred”

Removing an item from an LRS

• a.removeFront();

_state

LRS

NonEmpty

a

LRS

Empty

_state_tail

_dat=“fred”

Removing an item from an LRS

• a.removeFront();

_state

LRS

NonEmpty

a

LRS

Empty

_state_tail

_dat=“fred”

These gray objectswill be garbage-collectedat some point in the future

How about extensibility?

• Enter the Visitor pattern

• A visitor is an object which encapsulates an algorithm

• A visitor for the LRS defines what to do in each of the states:– emptyCase– nonEmptyCase

Visitor interface to LRS

• the execute method is also generic (I=input,O=output):

public <I,O> O execute(IAlgo<I,E,O> algo, I arg)

• the first parameter, alg, is the visitor (the algorithm)• the second parameter, arg, is optional input to the

algorithm• the return type of the method is of type O

execute definition in states

• Empty state:

public <I, O> O execute(IAlgo<I, E, O> algo, LRStruct<E> host, I arg){

return algo.emptyCase(host, arg);}

• NonEmpty state:

public <I, O> O execute(IAlgo<I, E, O> algo, LRStruct<E> host, I arg){

return algo.nonEmptyCase(host, arg);}

Designing a visitor (IAlgo)

• A visitor to solve a given problem must provide the answer in each of two cases:– the empty case– the non-empty case

• One example: finding the length of a list.

Length visitor (Java5 version: with generics & autoboxing)

• in the empty case the answer is zero• in the non-empty case the answer is one more than the

length of the rest of the list

public Integer emptyCase(LRStruct<E> host, Object _){

return 0;

}

public Integer nonEmptyCase(LRStruct<E> host, Object _){

return 1 + host.getRest().execute(this,_);

}

Another visitor

• How about a visitor for a list of Strings, which doubles each String

• Ex:– starting list: (Fred Wilma Pebbles)

– resulting list: (FredFred WilmaWilma PebblesPebbles)

StringDouble visitor

• in the empty case there is nothing to do• in the non-empty case the thing to do is to get the first,

concatenate it to itself, and set the first to this new value

public Object emptyCase(LRStruct<String> host, Object _){ return null;}public Object nonEmptyCase(LRStruct<String> host, Object _){ String s = host.getFirst(); host.setFirst(s+s); return host.getRest().execute(this,_);}