3
\$\begingroup\$

I'm learning rust (coming from C++) and playing around with different small algorithms to understand the ownership & borrowing concepts better. Currently, I'm having difficulties finding the idiomatic way to reuse a Vector after iterating over it in a for-loop. This is the (very verbose) code I currently have:

fn build_trie(paths: &Vec<String>) -> TreeNode {
        let mut root = TreeNode::new('\0');
        for path in paths {
            // start at the root node
            let mut current_node = &mut root;
            for ch in path.as_bytes() {
                let ch = *ch as char;
                println!("Current char: {}", ch);
                let length: i32 = current_node.children.len() as i32;
                let mut found_child = false;
                // for each child of the current node, check if the current character matches
                for i in 0..length as usize {
                    // found a match, descend into the tree
                    if current_node.children[i].get_value() == ch {
                        println!("Found matching char: {}", ch);
                        found_child = true; // avoid adding a new child later
                        current_node.children[i].increment_count();
                        current_node = &mut current_node.children[i];
                        break;
                    }
                    found_child = false;
                }
                // no matching child found, add a new child
                // and descend into the tree
                if !found_child {
                    let new_node = TreeNode::new(ch);
                    current_node.children.push(new_node);
                    current_node = current_node.children.last_mut().unwrap();
                }
            }
        }
        root
    }

While this does seem to work, I wanted to replace the for i in 0..length header with for child in current_node.children.iter_mut(). The problem is that this does a mutable borrow of current_node.children which also happens in the last if-statement, which obviously isn't allowed twice. I have the feeling that I'm missing some simple detail. I did a lot of googling but couldn't find anything that answered my question.

PS: I'm not sure if this question is for Code Review or StackOverflow. But since it might be opinion-based (and those get closed immediately...) I thought I'd try here first.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

welcome to the Rust community.

Rust's borrow checker analyzes the control flow of your code. However, it does not take into account the state of your variables (current_node and found_child in your example). That would be something like symbolic execution.

Instead, the borrow checker is pessimistic and it checks your if !found_child for conflicts with the part that sets found_child = true.

I suggest you use an iterator to find a child that the current character matches, and then work with indices to manipulate the trie. This way, you still get the performance benefits of iterators:

#[derive(Debug)]
struct TreeNode {
    ch: char,
    count: u32,
    children: Vec<TreeNode>,
}

impl TreeNode {
    fn new(ch: char) -> Self {
        Self {
            ch,
            count: 0,
            children: vec![],
        }
    }

    fn get_value(&self) -> char { self.ch }

    fn increment_count(&mut self) { self.count += 1; }
}

fn build_trie(paths: &Vec<String>) -> TreeNode {
    let mut root = TreeNode::new('\0');
    for path in paths {
        // start at the root node
        let mut current_node = &mut root;
        for ch in path.as_bytes() {
            let ch = *ch as char;
            println!("Current char: {}", ch);
            // for each child of the current node, check if the current character matches
            let maybe_found = current_node.children.iter_mut().position(|child|
                child.get_value() == ch
            );
            match maybe_found {
                Some(index) => {
                    println!("Found matching char: {}", ch);
                    current_node.children[index].increment_count();
                    current_node = &mut current_node.children[index];
                }
                None => {
                    let new_node = TreeNode::new(ch);
                    current_node.children.push(new_node);
                    current_node = current_node.children.last_mut().unwrap();
                }
            }
        }
    }
    root
}

fn main() {
    let paths = vec!["abc".to_string(), "abd".to_string()];
    let trie = build_trie(&paths);
    println!("{:?}", trie);
}
\$\endgroup\$
1
  • \$\begingroup\$ That makes sense. I think I have to learn to make use of a more functional programming style. Thank you very much! \$\endgroup\$ Commented Nov 28, 2021 at 20:48

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.