Skip to content
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

feat(store): implement AvlTree::remove #141

Merged
merged 26 commits into from
May 9, 2024
Merged

Conversation

rnbguy
Copy link
Member

@rnbguy rnbguy commented Oct 19, 2023

Closes #140

A short summary of how AvlTree::remove is implemented,

  • Find the sub-tree with the provided key.
  • Remove the top node of this sub-tree.
  • To remove a top node of a tree, we replaced it with a new node.
    • If the current node has no children, the new node is None.
    • If the current node has only one child, the new node is the child.
    • If the current node has both children.
    • If left child is shorter: the new node is the leftmost node in the right subtree.
      Also, the current node's children are set to the new node's children.
    • If right child is shorter, vice versa.

This runs in O(log(n)) as the recursive calls are always made on a subtree.

@rnbguy
Copy link
Member Author

rnbguy commented Oct 19, 2023

I noticed that the current implementation uses &mut old_value to pass around return values. It may make sense to use this if this mutable reference is modified multiple times. But it is only set once in a search. Can we switch to return rather?

For example, I want to refactor the following

fn insert_rec(node_ref: &mut NodeRef<K, V>, key: K, value: V, old_value: &mut Option<V>) {
if let Some(node) = node_ref {
match node.key.cmp(&key) {
Ordering::Greater => AvlTree::insert_rec(&mut node.left, key, value, old_value),
Ordering::Less => AvlTree::insert_rec(&mut node.right, key, value, old_value),
Ordering::Equal => *old_value = Some(node.set_value(value)),
}
node.update();
AvlTree::balance_node(node_ref);
} else {
*node_ref = as_node_ref(key, value);
}
}

to

    fn insert_rec(node_ref: &mut NodeRef<K, V>, key: K, value: V) -> Option<V> {
        if let Some(node) = node_ref {
            let old_value = match node.key.cmp(&key) {
                Ordering::Greater => AvlTree::insert_rec(&mut node.left, key, value),
                Ordering::Less => AvlTree::insert_rec(&mut node.right, key, value),
                Ordering::Equal => Some(node.set_value(value)),
            };
            node.update();
            AvlTree::balance_node(node_ref);
            old_value
        } else {
            *node_ref = as_node_ref(key, value);
            None
        }
    }

@rnbguy rnbguy requested a review from ancazamfir April 25, 2024 11:35
@rnbguy rnbguy changed the title Implement AvlTree::remove feat(store): implement AvlTree::remove Apr 25, 2024
Copy link
Contributor

@ancazamfir ancazamfir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a first pass on this, found the multiple recursive functions and the mem::replace()s make the code a bit unclear, making it harder to say there are no issues with the code :)

assert!(std::mem::replace(&mut leftmost_node.left, node.left.take()).is_none());
}
std::mem::replace(node_ref, leftmost_node_ref)
} else if node.left.is_some() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess here, since there is no right child, we could have just linked the nodes parent to the left child. Would require changes in remove_rec though. But wouldn't need the remove_leftmost.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was very helpful ! 😄 It ended up not requiring remove_rightmost method.

6058561

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up keeping the same code (with a slight modification) - but handling the no-sibling cases separately.

661d2d5

Comment on lines 102 to 103
// Remove the leftmost node in the right subtree and replace the current.
let mut leftmost_node_ref = AvlTree::remove_leftmost(&mut node.right);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside this remove_leftmost function we also take the leftmost's right child and put it in the leftmost's parent left child.

Copy link
Member Author

@rnbguy rnbguy May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure if this was a note. Note that, removed nodes are always a leaf. This is an invariant. Their left and right nodes are set to None.

So, what I am doing here is:

  1. point leftmost_node.left to where node.left points.
  2. point leftmost_node.right to where node.right points. (since, I called, node.{right,left}.take(), node is now a leaf too.)
  3. replace node with leftmost_node and return the old replaced value (which is a leaf) as the removed value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry it was a note, that remove_leftmost had a misleading name for me in the beginning.

Comment on lines 108 to 110
assert!(
std::mem::replace(&mut leftmost_node.right, node.right.take()).is_none()
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same as:

leftmost_node.right = node.right.take();

Why do we need mem::replace?

Copy link
Member Author

@rnbguy rnbguy May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right. I just wanted to assert the condition in one line. 8958a20

basecoin/store/src/avl/tree.rs Show resolved Hide resolved
}
}

#[test]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be good to have a test where we remove one key and make sure the others are still present and the tree is balanced.

Copy link
Member Author

@rnbguy rnbguy May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. I am now testing balancing factor and present keys after each insert and delete.

c8cfa04

@rnbguy rnbguy force-pushed the rano/impl-avltree-remove branch from dc91865 to e1fc66f Compare May 2, 2024 13:10
@rnbguy rnbguy force-pushed the rano/impl-avltree-remove branch from e1fc66f to c8cfa04 Compare May 2, 2024 13:11
@rnbguy
Copy link
Member Author

rnbguy commented May 4, 2024

This PR should remove/deprecate GrowingStore. Same for RevertibleStore via #160.

Copy link
Contributor

@ancazamfir ancazamfir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@rnbguy rnbguy merged commit 2dd5b95 into main May 9, 2024
10 checks passed
@rnbguy rnbguy deleted the rano/impl-avltree-remove branch May 9, 2024 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement AvlTree::remove to complete InMemoryStore::delete
2 participants