Website/src/teaching/cse-250/2022fa/slide/9b-LinkedListIterator.erb

449 lines
11 KiB
Plaintext

---
template: templates/cse250_2022_slides.erb
title: Linked Lists and Iterators
date: Sept 21, 2022
textbook: "Ch. 7"
---
<section>
<h4 class="slide_title">Seq[T]</h4>
<dl>
<dt>Array[T]</dt>
<dd><b>Pros:</b> $O(1)$ <tt>apply</tt>, <tt>update</tt></dd>
<dd><b>Cons:</b> $O(n)$ <tt>remove</tt>, <tt>insert</tt>, <tt>append</tt></dd>
<dt>ArrayBuffer[T]</dt>
<dd><b>Pros:</b> $O(1)$ <tt>apply</tt>, <tt>update</tt>, Amortized $O(1)$ <tt>append</tt></dd>
<dd><b>Cons:</b> $O(n)$ <tt>remove</tt>, <tt>insert</tt></dd>
<div class="fragment">
<dt>List[T] (linked list)</dt>
<dd><b>Pros:</b> ???</dd>
<dd><b>Cons:</b> ???</dd>
</div>
</dl>
</section>
<section>
<svg data-src="graphics/09b/linked-list-example.svg"/>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<pre><code class="scala">
class SinglyLinkedList[T] extends Seq[T]
{
var head: Option[SinglyLinkedListNode[T]] = None
/* ... */
}
</code></pre>
<pre><code class="scala">
class SinglyLinkedListNode(
var value: T,
var next: Option[SinglyLinkedListNode[T]] = None
)
</code></pre>
</section>
<section>
<h4 class="slide_title">The <tt>mutable.Seq</tt> ADT</h4>
<dl style="font-size: 80%;">
<dt><tt>length: Int</tt></dt>
<dd>Count the number of elements in the seq.</dd>
<dt><tt>apply(<b>idx</b>: Int): A</tt></dt>
<dd>Get the element (of type A) at position <b>idx</b>.</dd>
<dt><tt>insert(<b>idx</b>: Int, <b>elem</b>: A): Unit</tt></dt>
<dd>Insert an element at position <b>idx</b> with value <b>elem</b>.</dd>
<dt><tt>remove(<b>idx</b>: Int): A</tt></dt>
<dd>Remove the element at position <b>idx</b> and return the removed value.</dd>
</dl>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p>Implementing <tt>length</tt>.</p>
<pre class="fragment"><code class="scala">
def length: Int =
{
var i = 0
var current = head
while(current.isDefined){ i += 1; curr = curr.get.next }
return i
}
</code></pre>
<p class="fragment"><b>Complexity: </b> $O(n)$</p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p><b>Idea:</b> Keep track of the length</p>
<pre><code class="scala">
class SinglyLinkedList[T] extends Seq[T]
{
var head: Option[SinglyLinkedListNode[T]] = None
var length = 0
/* ... */
}
</code></pre>
<p class="fragment"><b>Complexity: </b> $O(1)$</p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p><tt>apply(2)</tt>.</p>
<svg data-src="graphics/09b/linked-list-apply.svg"/>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p>Implementing <tt>apply</tt>.</p>
<pre><code class="scala">
def apply(idx: Int): T =
{
var current = head
for(i <- 0 until idx){
if(current.isEmpty) { throw IndexOutOfBoundsException(idx) }
current = current.get.next
}
if(current.isEmpty) { throw IndexOutOfBoundsException(idx) }
return current
}
</code></pre>
<p class="fragment"><b>Complexity: </b> $O(n)$ <span class="fragment">(or $\Theta(\texttt{idx})$)</span></p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p><tt>insert(1, "D")</tt>.</p>
<svg data-src="graphics/09b/linked-list-insert.svg"/>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p>Implementing <tt>insert</tt>.</p>
<pre style="transform: scale(0.8); margin-top: -50px; margin-bottom: -50px;"><code class="scala">
def insert(idx: Int, value: T): Unit =
{
if(idx == 0){
head = Some( new SinglyLinkedListNode(value, head) )
} else {
var current = head
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
}
</code></pre>
<p class="fragment"><b>Complexity: </b> $O(n)$ <span class="fragment">(or $\Theta(\texttt{idx})$)</span></p>
</section>
<section>
<p>Let's use <tt>apply()</tt></p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<pre><code class="scala">
def sum(list: List[Int]): Unit =
{
val total: Int = 0
for(i <- 0 until list.length){ total += list(i) }
return total
}
</code></pre>
<p>What is the complexity?</p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<center>
$\sum_{i = 0}^{n-1} T_{apply}(i)$
<span class="fragment">
$=\sum_{i = 0}^{n-1} i$
</span>
</center>
<p class="fragment">
$$=\sum_{i = 0}^{n-1} \frac{(n-1)(n-1+1)}{2} = \frac{n^2 - n}{2} = \Theta(n^2)$$
</p>
<p class="fragment">Can we do better?</p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<pre><code class="scala">
def sum(list: List[Int]): Unit =
{
val total: Int = 0
val current = list.head
while(current.isDefined){
total += current.get.value
current = current.get.next
}
return total
}
</code></pre>
<p>What is the complexity?
<span class="fragment">$\sum_{i = 0}^{n-1} \Theta(1) = (n-1+1)\cdot \Theta(1) = \Theta(n)$</span>
</p>
</section>
<section>
<h4 class="slide_title">Access-by-Reference vs -by-Index</h4>
<p>Why does this work?</p>
<p class="fragment">What is the expensive part of <tt>apply</tt>?</p>
</section>
<section>
<h4 class="slide_title">Access-by-Reference vs -by-Index</h4>
<p>
Index → Value: $\Theta(idx)$<br>(access by index)
</p>
<p style="margin-top: 100px;" class="fragment">
<tt>SinglyLinkedListNode</tt> → Value: $\Theta(1)$<br>(access by reference)
</p>
</section>
<section>
<h4 class="slide_title">Iterator[T]</h4>
<dl>
<dt><tt>hasNext: Boolean</tt></dt>
<dd>Returns <tt>true</tt> if there are more items to retrieve.</dd>
<dt><tt>next:T</tt></dt>
<dd>Returns the next item to retrieve.</dd>
</dl>
</section>
<section>
<p>An iterator is:
<ul>
<li>A reference to an element of the collection</li>
<li>A way to get to the next<sup class="fragment" data-fragment-index=1 style="font-size: 50%">1</sup> element of the collection.</li>
</ul>
</p>
<p class="fragment" data-fragment-index=1>1: For some definition of next.</p>
</section>
<section>
<h4 class="slide_title">ListIterator[T] : Iterator[T]</h4>
<pre><code class="scala">
class ListIterator[T](
var current: Option[SinglyLinkedListNode[T]]
)
{
def hasNext: Boolean = current.isDefined
def next: T =
{
val ret = current.get.value
current = current.get.next
return ret
}
}
</code></pre>
</section>
<section>
<p>... back to LinkedLists</p>
<p class="fragment">How about positional operations:<br><tt style="font-size: 80%;">insertAfter(pos: SinglyLinkedListNode[T], value: T)</tt></p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p><tt>insertAfter(pos, "D")</tt></p>
<svg data-src="graphics/09b/linked-list-insertPositional.svg"/>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p>Implementing a positional <tt>insertAfter</tt></p>
<pre><code class="scala">
def insertAfter(pos: SinglyLinkedListNode[T], value: T) =
{
pos.next = Some(
new SinglyLinkedListNode(value, pos.next)
)
length += 1
}
</code></pre>
<p>What is the complexity?
<span class="fragment">$\Theta(1)$</span>
</p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p>How would you implement a positional <tt>remove</tt>?</p>
<pre class="fragment"><code class="scala">
def remove(pos: SinglyLinkedListNode[T]): T =
{
val prev = ??? /* Problem: Need element **before** pos. */
prev.next = pos.next
length -= 1
return pos.get.value
}
</code></pre>
</section>
<section>
<p><b>Idea:</b> Add a "backwards" pointer.</p>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<pre><code class="scala">
class DoublyLinkedList[T] extends Seq[T]
{
var head: Option[DoublyLinkedListNode[T]] = None
var last: Option[DoublyLinkedListNode[T]] = None
var length = 0
/* ... */
}
</code></pre>
<pre><code class="scala">
class DoublyLinkedListNode[T](
var value: T,
var next: Option[DoublyLinkedListNode[T]] = None
var prev: Option[DoublyLinkedListNode[T]] = None
)
</code></pre>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p>How would you implement a positional <tt>insertAfter</tt>?</p>
<pre><code class="scala">
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
}
</code></pre>
</section>
<section>
<h4 class="slide_title">mutable.List[T] : mutable.Seq[T]</h4>
<p>How would you implement a positional <tt>remove</tt>?</p>
<pre class="fragment"><code class="scala">
def remove(pos: DoublyLinkedListNode[T]): T =
{
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
}
</code></pre>
</section>
<section>
<table style="font-size: 50%">
<tr>
<th>Operation</th>
<th>Array[T]</th>
<th>ArrayBuffer[T]</th>
<th>List[T] (Index)</th>
<th>List[T] (Ref)</th>
</tr>
<tr>
<td><tt>apply(i)</tt></td>
<td>$\Theta(1)$</td>
<td>$\Theta(1)$</td>
<td>$\Theta(i)$, $O(n)$</td>
<td>$\Theta(1)$</td>
</tr>
<tr>
<td><tt>update(i, value)</tt></td>
<td>$\Theta(1)$</td>
<td>$\Theta(1)$</td>
<td>$\Theta(i)$, $O(n)$</td>
<td>$\Theta(1)$</td>
</tr>
<tr>
<td><tt>insert(i, value)</tt></td>
<td>$\Theta(n)$</td>
<td>$O(n)$, ...</td>
<td>$\Theta(i)$, $O(n)$</td>
<td>$\Theta(1)$</td>
</tr>
<tr>
<td><tt>remove(i, value)</tt></td>
<td>$\Theta(n)$</td>
<td>$\Theta(n-i)$, $O(n)$</td>
<td>$\Theta(i)$, $O(n)$</td>
<td>$\Theta(1)$</td>
</tr>
<tr>
<td><tt>append(i)</tt></td>
<td>$\Theta(n)$</td>
<td>$O(n)$, Amortized $\Theta(1)$</td>
<td>$\Theta(i)$, $O(n)$</td>
<td>$\Theta(1)$</td>
</tr>
</table>
</section>