lists we’ve seen an array-based list implementation, the arraylist. advantage of an array-based...
Post on 20-Dec-2015
223 views
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.
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,_);}