class: left, middle, title-slide # CSCI-UA 102 ## Data Structures
## Lists (Part 1) .author[ Instructor: Uday Kusupati
] .license[ Copyright 2020 Joanna Klukowska. Unless noted otherwise all content is released under a
[Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/)] --- layout:true template: default name: section class: inverse, middle, center --- layout:true template: default name: poll class: inverse, full-height, center, middle --- layout:true template: default name: breakout class: breakout, middle --- layout:true template:default name:slide class: slide .bottom-left[© Joanna Klukowska. CC-BY-SA.] --- template: section # Abstraction --- ## What is abstraction (in computer science)?
In general, __abstraction__ is a conceptual process where general rules and concepts are derived from generalization of specific examples. .small[ __Example:__ In the image on the right, the _tree_ on the bottom is an abstraction of a tree - it resembles the actual trees just enough to convey a message of being a tree without containing any details of what specific tree it might be (which in many situations might be irrelevant). ] -- In computer science, __abstraction__ is a process of hiding details of implementation in programs and data. .small[ - because such details are irrelevant and distract from the important concepts - because the user/client of a given code/system does not need to know the details in order to use the code/system effectively. ] -- An __abstract data type (ADT)__ is the specification of a data type (a set of values and a collection of operations on those values), independent of its implementation.
- an ADT's operations can be used without knowing their implementations or how the data is stored, as long as the interface to the ADT is precisely specified - an ADT is implementation independent in many different ways - a language independent ADT can be implemented in different programming languages - a language dependent ADT is a way of describing an ADT using a particular programming language, for example, an interface in Java --- ## A List ADT __Intuitive list__ - a collection of elements with some notion of position information for each element - operations: - add/remove at specified positions, - find an element in the list (or determine that it is not there) - retrieve an element from a particular position - determine the length of the list - empty the list -- __A List ADT__ - values stored - the list of elements itself - size - current number of elements in the list - operations - `add(item, pos)` - adds an element at position pos, restriction: 0 <= pos < size, shifts elements starting at pos one to the right (higher position values) - `remove(pos)` - removes and returns an element at position pos, restriction: 0 <= pos < size, shifts elements starting at pos+1 one to the left (lower position values) - `find(item)` - determines if the item is in the list and if so returns its position, otherwise returns -1 - `get(pos)` - retrieves and returns an item from position pos, restriction: 0 <= pos < size - `size()` - returns the current number of elements in the list, i.e. size - `clear()` - removes all elements from the list, and reset size to 0 --- ## A List ADT in Java ```Java // List ADT public interface List
{ // adds an element 'it' at position pos, shifts elements starting at pos by // one position to the right (higher position values) // throws NoSuchElementException if pos < 0 or pos >= size public boolean add(E it, int pos) throws NoSuchElementException; // removes and returns an element at position pos, shifts elements starting // at pos+1 by one to the left (lower position values) // throws NoSuchElementException if pos < 0 or pos >= size public E remove(int pos) throws NoSuchElementException; // determines if 'it' is in the list and if so returns its position, // otherwise returns -1 public int find (E it) ; // retrieves and returns an item from position pos // throws NoSuchElementException if pos < 0 or pos >= size public E get( int pos) throws NoSuchElementException; // returns the current number of elements in the list public int size(); // removes all elements from the list, so it is once again empty public void clear(); } ``` --- ## Java's own List ADT Java provides its own [`List` interface](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) that is implemented by several built in classes, among them the `ArrayList
` class. --- template: section # List Implementations --- ## Specific List Implementations A given list ADT can be implemented using different designs and approaches.
We usually consider two different ways of implementing a list: - using an array as an underlying storage - using a reference based structure as an underlying storage .below-column2[ The differences in how the operations are implemented reflect the differences in how the elements are stored. ] --- template: section # Reference Based Implementation ## a.k.a. Linked List --- ## Nodes (or a Linked List Building Blocks) The list is usually composed of __nodes__ that contain the actual data part and the memory address of the next element. .left-column2-large[ - The first node definition assumes that the data elements are of type `int`. - The second node definition assumes that the data elements are of type `String`. ] .right-column2-small[ ```Java class Node { int data; Node next; } ``` ```Java class Node { String data; Node next; } ``` ] -- .below-column2[ But other than the type of data, is there really any difference in what nodes need to _do_? ] -- .left-column2-large[ - The last definition is __generic__. It defines a node that can store any reference data type. .small[ `Node
n = new Node
();` - creates a node that stores integers ] .small[ `Node
n = new Node
();` - creates a node that stores strings ] .small[ `Node
n = new Node
();` - creates a node that stores penguins ] .small[ (note: in recent versions of Java, the type name can be skipped in the second location:
`Node
n = new Node<>();`) ] ] .right-column2-small[ ```Java class Node
{ E data; Node
next; } ``` ] --- ## Iterating Through a List A common task when working with list is to iterate through an existing list. We'll go over examples of how to do it the right way and the wrong way. --- name: iterate-right ## Iterating Through a List - the right way --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration ] --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration - a good starting point is the first elements whose reference is stored in the `head` variable `Node current = head;` ] --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration - a good starting point is the first elements whose reference is stored in the `head` variable `Node current = head;` - to advance the `current` reference to the next node, we need to change the memory address that it stores to the memory address that is stored in the `next` reference of the node to which it points `current = current.next;` ] --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration - a good starting point is the first elements whose reference is stored in the `head` variable `Node current = head;` - to advance the `current` reference to the next node, we need to change the memory address that it stores to the memory address that is stored in the `next` reference of the node to which it points `current = current.next;` - to go through the entire list, we just repeat the last step ``` while ( ... ) { current = current.next; } ``` ] --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration - a good starting point is the first elements whose reference is stored in the `head` variable `Node current = head;` - to advance the `current` reference to the next node, we need to change the memory address that it stores to the memory address that is stored in the `next` reference of the node to which it points `current = current.next;` - to go through the entire list, we just repeat the last step ``` while ( ... ) { current = current.next; } ``` ] --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration - a good starting point is the first elements whose reference is stored in the `head` variable `Node current = head;` - to advance the `current` reference to the next node, we need to change the memory address that it stores to the memory address that is stored in the `next` reference of the node to which it points `current = current.next;` - to go through the entire list, we just repeat the last step ``` while ( ... ) { current = current.next; } ``` ] --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration - a good starting point is the first elements whose reference is stored in the `head` variable `Node current = head;` - to advance the `current` reference to the next node, we need to change the memory address that it stores to the memory address that is stored in the `next` reference of the node to which it points `current = current.next;` - to go through the entire list, we just repeat the last step ``` while ( ... ) { current = current.next; } ``` ] --- template: iterate-right .center[
] .center80[ - given a list, we need to pick a starting point for the iteration - a good starting point is the first elements whose reference is stored in the `head` variable `Node current = head;` - to advance the `current` reference to the next node, we need to change the memory address that it stores to the memory address that is stored in the `next` reference of the node to which it points `current = current.next;` - to go through the entire list, we just repeat the last step ``` while ( ... ) { current = current.next; } ``` ] --- name: iterate-condition ## Iterating Through a List - the right way __What should be the condition used in the iteration?__ -- Well, this depends on what we are trying to do: .left-column2-smaller[ - go through all the nodes and _fall off_ the list at the end ``` Node current = head; while (current != null ) { current = current.next; } ``` ] .right-column2-larger[
] -- .below-column2[
] .left-column2-smaller[ - go through all the nodes and stop at the last node ``` Node current = head; if (current != null ) { while (current.next != null ) { current = current.next; } } ``` ] .right-column2-larger[
] --- ## Iterating through the list - the wrong way Here is an example of a function that attempts to print the content of the entire list. It is a member function of the list itself. ``` void printList ( ) { while ( head != null ) { System.out.print(head.data + ", "); head = head.next; } } ``` -- - Will the content of the list be printed? -- __YES__ -- - So what is _wrong_ about this iteration? --- template: section ## Adding to a Linked List --- ## Adding to a Linked List In order to add an element to the list we first need to create a new `Node`: .center80[ `Node n = new Node();` ] -- Then, depending on the location at which we are adding, we may consider three separate cases: - adding to the front of a list - adding to the end of a list - adding at some position in between -- We also need to make sure that our algorithm for adding works in case of an empty list, one-element list and a list with many elements. --- name:empty-lists ## An Empty List In an empty list, both `head` and `tail` references point to nothing. In this case, we need to make them both point to the newly created node. .left-column2-small[ an empty list ] .right-colum2-large[
] .below-column2[
] .left-column2-small[ ``` Node n = new Node(); head = n; tail = n; ``` ] .right-colum2-large[
] --- name: add-front ## Adding to the front of a list What is the sequence of steps required to add to the beginning of a list? --- template: add-front .center[
] .center80[ `Node n = new Node();` ] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`head = n; `
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`head = n; `
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`head = n; `
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`head = n; `
] If we change the `head` to point to the newly created node, the rest of the list is lost and cannot be recovered. --- template: add-front .center[
] .center80[ `Node n = new Node();`
`n.next = head`
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`n.next = head`
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`n.next = head`
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`n.next = head`
`head = n`
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`n.next = head`
`head = n`
] --- template: add-front .center[
] .center80[ `Node n = new Node();`
`n.next = head`
`head = n`
] --- name: add-back ## Adding to the end of a list What is the sequence of steps required to add to the end of a list? --- template: add-back .center[
] .center80[ `Node n = new Node(); `
] --- template: add-back .center[
] .center80[ `Node n = new Node(); `
`tail.next = n;`
] --- template: add-back .center[
] .center80[ `Node n = new Node(); `
`tail.next = n;`
] --- template: add-back .center[
] .center80[ `Node n = new Node(); `
`tail.next = n;`
] --- template: add-back .center[
] .center80[ `Node n = new Node(); `
`tail.next = n;`
`tail = tail.next`
] --- template: add-back .center[
] .center80[ `Node n = new Node(); `
`tail.next = n;`
`tail = tail.next`
] --- template: add-back .center[
] .center80[ `Node n = new Node(); `
`tail.next = n;`
`tail = tail.next`
] --- name: add-index ## Adding to an arbitrary position in a list What is the sequence of steps required to add to an arbitrary position in a list? - determined by index - determined by ordering of elements - ... --- template: add-index .center[
] `Node n = new Node(); `
--- template: add-index .center[
] `Node n = new Node(); `
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
`n.next = current.next`
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
`n.next = current.next`
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
`n.next = current.next`
`current.next = n`
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
`n.next = current.next`
`current.next = n`
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
`n.next = current.next`
`current.next = n`
--- template: add-index .center[
] `Node n = new Node(); `
`Node current = head `
`//advance current to the node BEFORE the position at which be want to add`
`n.next = current.next`
`current.next = n`
--- ## Add to list. Putting it all together? Try to come up with a single Java method that implements the `add` method from our interface: ```Java // adds an element 'it' at position pos, shifts elements starting at pos by // one position to the right (higher position values) // throws NoSuchElementException if pos < 0 or pos >= size public boolean add(E it, int pos) throws NoSuchElementException; ``` You will need to use all of the code fragments from the previous slides, plus a bit more to locate the position. Assume that there is a `size` data field that keeps track of the exact number of elements in the list. Make sure that your function works when the list is empty, when it has only one element, and when it has many elements.