Sept. 3
Mutable vs Immutable Data
X = [ "Alice", "Bob", "Carol", "Dave" ]
X : | Alice | Bob | Carol | Dave |
print(X[2]) // -> "Carol"
X[2] = "Eve"
X : | Alice | Bob | Eve | Dave |
print(X[2]) // -> "Eve"
X = [ Alice, Bob, Carol, Dave ]
X : | Alice | Bob | Carol | Dave |
Thread 1 | Thread 2 |
---|---|
|
|
🤔 |
Can these problems be avoided?
Mutable vs Immutable Data
X = [ "Alice", "Bob", "Carol", "Dave" ]
X : | Alice | Bob | Carol | Dave |
print(X[2]) // -> "Carol"
X[2] = "Eve"
Don't allow writes!
But what if we need to update the structure?
X : | Alice | Bob | Carol | Dave |
X' : | Alice | Bob | Eve | Dave |
Slooooooooooooooooooooooow!
Data is always added, not replaced!
How would you implement:
list update(list, index, new_value)
Implement a set with:
set init()
boolean is_member(set, elem)
set insert(set, elem)
Can we do better?
|
Fast (Just saving a 'todo') |
|
Slow (Performing the 'todo') |
|
Fast ('todo' already done) |
Make it better!
concatenate(a, b) {
a', front = pop(a)
if a' is empty {
return (front, b)
} else {
return (front, "concatenate(a', b)")
}
}
What is the time complexity of this concatenate?
What happens to reads?
Save work for later...
Allow operation A to 'pay it forward' for another operation B that hasn't happened yet.
... or allow an operation B to 'borrow' from another operation A that hasn't happened yet.
queue enqueue(queue, item) {
return {
current : queue.current,
todo : push(queue.todo, item)
)
}
What is the cost?
queue dequeue(queue) {
if(queue.current != NULL){
return { current: pop(queue.current), todo: queue.todo }
} else if(queue.todo != NULL) {
return { current: reverse(queue.todo), todo: NULL }
} else {
return { current: NULL, todo: NULL }
}
}
What is the cost?
Critical requirement of amortized analysis: Must ensure that every credit consumed was once created.