Reputation: 995
I'm confused how append()
is able to update a nested node in a linked list, despite overwriting this.tail
.
How can this.tail.next
update both this.tail
and whatever this.tail.next
is referencing, in the first case, this.head
, and so forth? Especially when this.tail
is overwritten to newNode
right afterwards. Shouldn't that overwrite this.head
entirely? How is the next
key "saved" despite the entire this.tail
being set to newNode
?
Separately, I understand that this.tail.next
is referencing the node before - but how does the references next
key get updated if all we are doing is setting this.tail.next
, and not its reference?
To illustrate this point - I set up a variable, potatoes
, and another variable, fish
, referencing potatoes
. Despite updating potatoes
, it is clear fish does not get updated as well, even though it is only a reference to potatoes
.
let potatoes = 5
let fish = potatoes
potatoes = 2
console.log(fish)
class LinkedList {
constructor(value) {
this.head = {
value,
next: null
}
this.tail = this.head
this.length = 1
}
append(value) {
const newNode = {
value,
next: null
}
this.tail.next = newNode
this.tail = newNode
this.length++
return this
}
}
const linkedList = new LinkedList(5)
linkedList.append(2)
linkedList.append(8)
console.log(linkedList)
Upvotes: 0
Views: 170
Reputation: 350310
How can
this.tail.next
update boththis.tail
and whateverthis.tail.next
is referencing, in the first case,this.head
, and so forth?
this.head
has a (deep) reference to this.tail
via the next
property chain. So when you mutate this.tail.next
, then this change can be "seen" from any other node that has a next
chain that eventually ends up at this.tail
.
Especially when
this.tail
is overwritten tonewNode
right afterwards. Shouldn't that overwritethis.head
entirely?
No, assigning to this.tail
does not affect the object that this.tail
was referencing before that assignment. The only way to alter that object is to change the value of one of its properties.
How is the next key "saved" despite the entire
this.tail
being set tonewNode
?
As said above, the object that this.tail
referred to was not altered by this assignment. The assignment just "switches" this.tail
to (reference) a different object.
Separately, I understand that
this.tail.next
is referencing the node before - but how does the reference'snext
key get updated if all we are doing is settingthis.tail.next
, and not its reference?
When we do this.tail.next = newNode
we are setting a reference to what newNode
refers to. At that moment the linked list has become one node longer. When we then follow up with this.tail = newNode
we make sure that this.tail
references the last node in the list, which is an invariant we want to restore.
It may help to visualise the example you have presented in your script.
When const linkedList = new LinkedList(5)
is executed, we set this.head
to a new node, and we get this state:
linkedList (this)
↓
┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │
│ │ │ next: null│
└───────────┘ └───────────┘
The left box is the linked list instance, and the right box is the (first) Node instance.
The constructor continues with this.tail = this.head
. This merely copies the reference to the new object into a new tail
property:
linkedList (this)
↓
┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │
│ tail: ───────> │ next: null│
└───────────┘ └───────────┘
I skip the length
property management here, as it is not relevant to the question.
Then linkedList.append(2)
is executed. First newNode
gets a reference to a new node which is for the moment is not related to the list in any way:
linkedList (this)
↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │ │ value: 2 │
│ tail: ───────> │ next: null│ │ next: null│
└───────────┘ └───────────┘ └───────────┘
↑
newNode
...and this reference is assigned to this.tail.next
as well:
linkedList (this)
↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │ │ value: 2 │
│ tail: ───────> │ next: ───────> │ next: null│
└───────────┘ └───────────┘ └───────────┘
↑
newNode
So the effect of this.tail.next = newNode
is reflected by middle horizontal arrow. Note that indeed newNode
and this.tail.next
(if you follow the arrows) refer to the same node.
Now we execute this.tail = newNode
. This is the (simple) effect of that:
linkedList (this)
↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │ │ value: 2 │
│ tail: ──────┐ │ next: ───────> │ next: null│
└───────────┘ │ └───────────┘ ┌> └───────────┘
└────────────────┘ ↑
newNode
Note that with such an assignment mutates the linked list instance, but not any node instance. The append
method finishes, and the newNode
variable gets out of scope and is no longer relevant.
Let's continue with linkedList.append(8)
. Again a newNode
variable gets a reference to a new node:
linkedList (this)
↓
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │ │ value: 2 │ │ value: 8 │
│ tail: ──────┐ │ next: ───────> │ next: null│ │ next: null│
└───────────┘ │ └───────────┘ ┌> └───────────┘ └───────────┘
└────────────────┘ ↑
newNode
...and again we mutate the node that tail
is referring to with this.tail.next = newNode
:
linkedList (this)
↓
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │ │ value: 2 │ │ value: 8 │
│ tail: ──────┐ │ next: ───────> │ next: ───────> │ next: null│
└───────────┘ │ └───────────┘ ┌> └───────────┘ └───────────┘
└────────────────┘ ↑
newNode
...and the only thing that remains is to let tail
reference the last node (the invariant) with this.tail.next = newNode
:
linkedList (this)
↓
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ head: ───────> │ value: 5 │ │ value: 2 │ │ value: 8 │
│ tail: ──────┐ │ next: ───────> │ next: ───────> │ next: null│
└───────────┘ │ └───────────┘ └───────────┘ ┌> └───────────┘
└─────────────────────────────────┘ ↑
newNode
Hope this clarifies it.
Upvotes: 2
Reputation: 1298
Here is why it changes both the tail.next
and tail
to the appended Node.
// This is setting the new Node to be the next Node to the tail Node.
this.tail.next = newNode
// Now it's setting the new node as the tail.
this.tail = newNode
To answer the reference question, In JavaScript only Arrays and Objects can be referenced, so in your pototoe example, it's not being referenced, but is assigning the value of the other variable.
// Numbers, Strings and booleans, can't be referenced.
let potatoes = 5
let fish = potatoes
potatoes = 2
console.log("Number:", fish)
let orange = "5"
let raspberry = orange
orange = "2"
console.log("String:", raspberry)
let pineapple = false
let watermelon = pineapple
pineapple = true
console.log("Boolean:", watermelon)
// Arrays and Objects can.
let pear = [5]
let apple = pear
pear[0] = 2
console.log("Array:", apple[0])
let banana = {
value: 5
}
let grape = banana
banana.value = 2
console.log("Object:", grape.value)
Upvotes: 1