-
Notifications
You must be signed in to change notification settings - Fork 0
AVL tree #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/lib.rs
Outdated
/// assert!(tree.contains(&String::from("world!"))); | ||
/// ``` | ||
pub fn insert(&mut self, value: T) { | ||
if let Ok(node) = self.create_node(value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If create_node
returns an Err
, is the insert swallowed or ...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create_node
returns an Err
if value
can already be found in the tree. So if Err
is returned, insert won't do anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that's a weird return type to denote a non-bad thing :) Why not have create_node
just return a bool
(e.g. true
means "this node already existed")?
src/lib.rs
Outdated
|
||
fn create_node(&mut self, value: T) -> Result<Node<T>, usize> { | ||
match self.get_node(&value) { | ||
Ok(index) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can just be Ok(index) => Err(index)
(without the curly brackets).
std::cmp::max(left_height, right_height) | ||
} | ||
|
||
fn balance_factor(&self, node: &Node<T>) -> i8 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this return a signed int?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The balance factor can be negative. In my code the balance factor is defined as height(left_child) - height(right_child)
. If it is negative and less than -2, the tree is 'right-heavy'. If it's positive and greater than 2, the tree is 'right-heavy'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, got it. But can the balance exceed +/- 128?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should always be in the range [-2, 2]
. If at some point it becomes +/- 2
, the tree is (should be) rebalanced
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood... but how far can it exceed +/- 2 before it's rebalanced?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In an AVL tree, the heights of the two child subtrees of any node differ by at most one
(wikipedia)
The 2 and -2 cases trigger rebalancing. You can't have < -2
or > 2
because you only ever insert/delete one node at a time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, got it.
src/lib.rs
Outdated
self.nodes[right_index].right_child); | ||
self.nodes[right_index].parent = self.nodes[index].parent; | ||
self.nodes[index].parent = Some(right_index); | ||
//self.update_heights(right_index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove commented code.
Some relatively minor comments from me. Have you run clippy over this? https://github.com/rust-lang-nursery/rust-clippy |
@ltratt Thank you for the review! I'll have a look at clippy |
src/lib.rs
Outdated
fn get_node(&self, value: &T) -> Result<usize, Option<usize>> { | ||
let mut current_index = self.root; | ||
let mut previous_index = None; | ||
if !self.nodes.is_empty() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this check is needed. In the case of the tree being empty, root
would be None, and thus he while
loop would not be entered, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point!
src/lib.rs
Outdated
} | ||
} | ||
|
||
fn get_node(&self, value: &T) -> Result<usize, Option<usize>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add comment indicating what the error state communicates?
src/lib.rs
Outdated
} | ||
|
||
fn update_heights(&mut self, start_index: usize) { | ||
if !self.nodes.is_empty() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is, since I call self.root.unwrap()
at 148:
self.nodes[self.root.unwrap()].height =
self.compute_height(self.nodes[self.root.unwrap()].left_child,
self.nodes[self.root.unwrap()].right_child);
I should probably store the root index though, instead of unwrapping it everywhere
} | ||
|
||
fn left_rotation(&mut self, index: usize) { | ||
if let Some(right_index) = self.nodes[index].right_child { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it mean if you don't enter the body of this conditional? i.e. if right_index
is None
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps comment this section of code up more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, the right child of index
becomes the parent of index
and the left child of right_index
becomes the right child of index
. However, right_index
might not have any children, which is why this condition is necessary.
src/lib.rs
Outdated
self.left_right_rotation(index); | ||
} | ||
} else if balance_factor < -2 || balance_factor > 2 { | ||
panic!("Invalid balance factor for {}: {}", index, balance_factor); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ Ah, this confirms my above comment about the -2 <= height <= 2
thing. Is unreachable!()
more appropriate here? Not sure.
src/lib.rs
Outdated
|
||
fn is_balanced(&self, index: usize) -> bool { | ||
let balance_factor = self.balance_factor(&self.nodes[index]); | ||
balance_factor >= -1 && balance_factor <= 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you chain operators in Rust?
return -1 <= self.balance_factor(&self.nodes[index]) <= 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can't you could do:
return abs(self.balance_factor(&self.nodes[index])) < 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to be an open issue, I'll just use abs
src/lib.rs
Outdated
/// assert!(tree.iter().zip(vec![1, 2, 5]).all(|(x, y)| *x == y)); | ||
/// ``` | ||
pub fn iter(&'a self) -> SearchTreeIter<'a, T> { | ||
let mut start_node = None; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this initialisation code should be encapsulated inside the iterator's ::new()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just walks down the left side of the tree to find the first node that should fall out of the iterator, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I didn't implement it like that because it felt a bit awkward to pass both the root node and the tree Vec
to SearchTreeIter::new
, but I guess it makes makes sense to do it that way
src/lib.rs
Outdated
if let Some(right_child) = node.right_child { | ||
self.current_node = Some(right_child); | ||
node = &self.nodes[right_child]; | ||
while let Some(left_child) = node.left_child { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be a recurring pattern.
Lots of code wants to find the deepest leftmost node in a given sub-tree. Should that be a function?
src/lib.rs
Outdated
assert!(tree.is_balanced(node_index)); | ||
} | ||
} | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this extra scoping needed to silence the borrow checker?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it used to be needed with my older implementation of the test. It's no longer necessary, I'll remove it
This looks pretty good gabi! |
All tests passing for me :) |
@vext01 Thank you for your review! I've addressed your comments, let me know if there's anything else I need to change |
// left child of `right_index` must become the new right child of | ||
// `index`. | ||
// | ||
// A (index) (right_index) B |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like these ascii art diagrams :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah, thanks! Explaining the balancing algorithm would've been a lot harder without drawing
This looks good to me. If @ltratt is OK too, then we can squash. |
Fine with me. |
All done |
Is |
@ltratt |
Ah, yes, you're quite right -- |
OK, that makes me happier :) If you squash that, I can merge. |
@ltratt I've slightly changed the meaning of |
This sounds sensible to me. If @vext01 is OK, then I think we're ready for the final squash! |
Go ahead. |
OK, it's time to squash! |
Okay, done! |
Contains the initial implementation of the search tree.