Linked List List “Pro” version:)
Recall - Array
Sequential
One continuous block of memory
Random access based on memory address
address = first_address + (element_size * index)
Fixed Size
Since memory adjacent to the block may be used
Efficient when you know many elements you’ll need to store
Array
Arrays are stored on the heap
Pointer to index 0 goes on the stack
add index * sizeOfElement to 1503 to find each element
This is called random access
Recall - Linked List
Sequential
Spread across memory
Each element knows the memory address of the next element
Follow the addresses to find each element
Variable Size
Store new element anywhere in memory
New element stores address of the first eloement
Linked List
myList stores a list containing: [5,3,1]
Last link stores null
We say the list is “null terminated”
When we read a value of null we know we reached the end of the list
1 2 3 4 5 6 7 class LinkedListNode [A ](var value: A , var next: LinkedListNode [A ] ) {} var myList: LinkedListNode [Int ] = new LinkedListNode [Int ](1 , null )myList = new LinkedListNode [Int ](3 , myList) myList = new LinkedListNode [Int ](5 , myList)
We create our own linked list class by defining a node
A node represents one “link” in the list
The list itself is a reference to the first / head node
Note: This is a mutable list
You’ll build immutable lists in CSE250
1 var myList: LinkedListNode [Int ] = new LinkedListNode [Int ](1 , null )
Create a new variable to store the head (first node) of the list
Create a new node with the value 1
The list has size 1
1 myList = new LinkedListNode [Int ](3 , myList)
We prepend a new node to the list
Create a new node with value 3
The new node “refers” to the rest of the list
The list has size 2
1 myList = new LinkedListNode [Int ](5 , myList)
Repeat the process to build a list of size 3
Each node refers to the next node in the list
The last node doesn’t refer to anything (null) indicating the end of list
Linked List Algorithms
We know the structure of a linked list
How do we operate on these lists?
We would like to:
Find the size of a list
Print all the elements of a list
Access elements by location
Add / Remove elements
Find a sprecific value
Size
Navigate through the entire listr until the next reference is null
Count the number of nodes visited
Could use a loop. Recursive example shown
1 2 3 4 5 6 7 def size (): Int = { if (this .next == null ){ 1 }else { this .next.size() + 1 } }
To String
Same as size, but accumulate the value as strings instead of counting the number of nodes
Recursive makes it easier to manage our commas
“,” is only appended if it’s not the last element
1 2 3 4 5 6 7 override def toString : String = { if (this .next == null ) { this .value.toString }else { this .value.toString + ", " + this .next.toString } }
Debugger Demo // Check the video //
Access Element by Location
Simulates array access
Take an “index” and advance through the list that many times
MUCH slower than array access
Calls next n times - O(n) runtime
ex.apply(4) is the same as this.next.next.next.next
1 2 3 4 5 6 7 def apply (i: Int ): LinkedListNode [A ] = { if (i == 0 ){ this } else { this .next.apply(i - 1 ) } }
Add an Element
To add an element we first need a reference to the node before the location of the new element
Update the next reference of this node
Want to add 2 in this list after 3
Need reference to the node contianing 3
Create the new node with next equal to this node’s next
This node’s next is set to the new node
1 2 3 def insert (element: A ): Unit = { this .next = new LinkedListNode [A ](element, this .next) }
Delete a Node
Want to delete the node containing 2
Need a reference to the previous node
Update that node’s next to bypass the deleted node
Don’t have to update deleted node
The list no longer refers to this node
1 2 3 def deleteAfter (): Unit = { this .next = this .next.next }
Find a Value
Navigate through the list one node at at time
Check if the node contains the value
If it doesn’t, move to the next node
If the end of the list is reached, the list does not contain the element
1 2 3 4 5 6 7 8 9 def find (toFind: A ): LinkedListNode [A ] = { if (this .value == toFind) { this } else if (this .next == null ) { null } else { this .next.find(toFind) } }
Find - Recursion v. Iteration 1 2 3 4 5 6 7 8 9 10 def findIterative (toFind: A ): LinkedListNode [A ] = { var node = this while (node != null ) { if (node.value == toFind) { return node } node = node.next } null }
1 2 3 4 5 6 7 8 9 def find (toFind: A ): LinkedListNode [A ] = { if (this .value == toFind) { this } else if (this .next == null ) { null } else { this .next.find(toFind) } }
ForEach
Call a function on each node of the list
1 2 3 4 5 6 def foreach (f: A => Unit ): Unit = { f(this .value) if (this .next != null ) { this .next.foreach(f) } }
Map Useage
Recall the map method for builtin List
Used to transform every element in a list
1 2 3 val numbers: List [Int ] = List (1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 )val numbersSquared: List [Int ] = numbers.map((n: Int ) => n * n)println(numbersSquared)
Map
Apply a function to each element of the list
Return a new list containing the return values of the function
1 2 3 4 5 6 7 8 def map (f: A => A ): LinkedLisNode [A ] = { val newValue = f(this .value) if (this .next == null ) { new LinkedListNode [A ](newValue, null ) } else { new LinkedListNode [A ](newValue, this .next.map(f)) } }
Map - Change Type
Can change the type of the returned list with a second type parameter
A could be equal to B if you don’t want to change the type
Example: You want to divide a list of Ints by 2 and have to return a list of Doubles to avoid rounding1 2 3 4 5 6 7 8 def map [B ](f: A => B ): LinkedListNode [B ] = { val newValue = f(this .value) if (this .next == null ) { new LinkedListNode [B ](newValue, null ) } else { new LinkedListNode [B ](newValue, this .next.map(f)) } }
Lecture Question Task: Write reduce for our linked list!!!! (go to example repo) || link
Write a method in the week8.linkedlist.LinkedListNode class (from the examples repo) named reduce that:
Takes a function of type (A, A) => A
Returns A
Combines all the elements of the list into a single value by applying the provided function to all elements
You may assume the function is commutative
If the list has size 1, return that element without calling the provided function
Testing: In a package named “tests” create a class named “TestReduce” as a test suite that tests the functionality listed above (Do not test with an empty list/null)
Example: If head stores a reference to the List(4, 6, 2)
1 head.reduce((a: Int , b: Int ) => a + b) == 12