CSE-250 Fall 2022 - Section B - Linked Lists and Iterators

Sept 21, 2022

#### Seq[T]

Array[T]
Pros: $O(1)$ apply, update
Cons: $O(n)$ remove, insert, append
ArrayBuffer[T]
Pros: $O(1)$ apply, update, Amortized $O(1)$ append
Cons: $O(n)$ remove, insert
Pros: ???
Cons: ???

#### mutable.List[T] : mutable.Seq[T]

{

/* ... */
}

var value: T,
)

length: Int
Count the number of elements in the seq.
apply(idx: Int): A
Get the element (of type A) at position idx.
insert(idx: Int, elem: A): Unit
Insert an element at position idx with value elem.
remove(idx: Int): A
Remove the element at position idx and return the removed value.

#### mutable.List[T] : mutable.Seq[T]

Implementing length.

def length: Int =
{
var i = 0
while(current.isDefined){ i += 1; curr = curr.get.next }
return i
}

Complexity: $O(n)$

#### mutable.List[T] : mutable.Seq[T]

Idea: Keep track of the length

{
var length = 0

/* ... */
}

Complexity: $O(1)$

apply(2).

#### mutable.List[T] : mutable.Seq[T]

Implementing apply.

def apply(idx: Int): T =
{
for(i <- 0 until idx){
if(current.isEmpty) { throw IndexOutOfBoundsException(idx) }
current = current.get.next
}
if(current.isEmpty) { throw IndexOutOfBoundsException(idx) }
return current
}

Complexity: $O(n)$ (or $\Theta(\texttt{idx})$)

insert(1, "D").

#### mutable.List[T] : mutable.Seq[T]

Implementing insert.

def insert(idx: Int, value: T): Unit =
{
if(idx == 0){
} else {
for(i <- 0 until idx){
if(curr.isEmpty) { throw IndexOutOfBoundsException(idx) }
curr = curr.get.next
}
curr.next = Some( new SinglyLinkedListNode(value, curr.next) )
}
length += 1
}

Complexity: $O(n)$ (or $\Theta(\texttt{idx})$)

Let's use apply()

#### mutable.List[T] : mutable.Seq[T]

def sum(list: List[Int]): Unit =
{
val total: Int = 0
for(i <- 0 until list.length){ total += list(i) }
}

What is the complexity?

#### mutable.List[T] : mutable.Seq[T]

$\sum_{i = 0}^{n-1} T_{apply}(i)$ $=\sum_{i = 0}^{n-1} i$

$$=\sum_{i = 0}^{n-1} \frac{(n-1)(n-1+1)}{2} = \frac{n^2 - n}{2} = \Theta(n^2)$$

Can we do better?

#### mutable.List[T] : mutable.Seq[T]

def sum(list: List[Int]): Unit =
{
val total: Int = 0
while(current.isDefined){
total += current.get.value
current = current.get.next
}
}

What is the complexity? $\sum_{i = 0}^{n-1} \Theta(1) = (n-1+1)\cdot \Theta(1) = \Theta(n)$

#### Access-by-Reference vs -by-Index

Why does this work?

What is the expensive part of apply?

#### Access-by-Reference vs -by-Index

Index â†’ Value: $\Theta(idx)$
(access by index)

SinglyLinkedListNode â†’ Value: $\Theta(1)$
(access by reference)

#### Iterator[T]

hasNext: Boolean
Returns true if there are more items to retrieve.
next:T
Returns the next item to retrieve.

An iterator is:

• A reference to an element of the collection
• A way to get to the next1 element of the collection.

1: For some definition of next.

#### ListIterator[T] : Iterator[T]

class ListIterator[T](
)
{
def hasNext: Boolean = current.isDefined
def next: T =
{
val ret = current.get.value
current = current.get.next
return ret
}
}

#### mutable.List[T] : mutable.Seq[T]

insertAfter(pos, "D")

#### mutable.List[T] : mutable.Seq[T]

Implementing a positional insertAfter

def insertAfter(pos: SinglyLinkedListNode[T], value: T) =
{
pos.next = Some(
)
length += 1
}

What is the complexity? $\Theta(1)$

#### mutable.List[T] : mutable.Seq[T]

How would you implement a positional remove?

{
val prev = ??? /* Problem: Need element **before** pos. */
prev.next = pos.next
length -= 1
return pos.get.value
}

#### mutable.List[T] : mutable.Seq[T]

{
var length = 0

/* ... */
}

var value: T,
)

#### mutable.List[T] : mutable.Seq[T]

How would you implement a positional insertAfter?

def insertAfter(pos: DoublyLinkedListNode[T], value: T) =
{
val newNode = new DoublyLinkedListNode(value, prev = Some(pos))
if(pos.next.isDefined){  pos.next.prev = Some(newNode)
newNode.next = pos.next }
else {                   last = newNode
newNode.next = None }
pos.next = Some(newNode)
length += 1
}

#### mutable.List[T] : mutable.Seq[T]

How would you implement a positional remove?

{
if(pos.prev.isDefined){ pos.prev.next = pos.next }
else                  { head = pos.next }

if(pos.next.isDefined){ pos.next.prev = pos.prev }
else                  { tail = pos.prev }

length -= 1
return pos.get.value
}

Operation Array[T] ArrayBuffer[T] List[T] (Index) List[T] (Ref)
apply(i) $\Theta(1)$ $\Theta(1)$ $\Theta(i)$, $O(n)$ $\Theta(1)$
update(i, value) $\Theta(1)$ $\Theta(1)$ $\Theta(i)$, $O(n)$ $\Theta(1)$
insert(i, value) $\Theta(n)$ $O(n)$, ... $\Theta(i)$, $O(n)$ $\Theta(1)$
remove(i, value) $\Theta(n)$ $\Theta(n-i)$, $O(n)$ $\Theta(i)$, $O(n)$ $\Theta(1)$
append(i) $\Theta(n)$ $O(n)$, Amortized $\Theta(1)$ $\Theta(i)$, $O(n)$ $\Theta(1)$