basic_trie 2.1.0

A simple Trie implementation in Rust
Documentation
use std::cmp::Ordering;
use std::ops;

use crate::child_storage::ChildStorage;
#[cfg(feature = "serde")]
use serde_crate::{Deserialize, Serialize};
#[cfg(feature = "unicode")]
use unicode_segmentation::UnicodeSegmentation;

/// Singular trie node that represents its children and a marker for word ending.
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate")
)]
#[derive(Default, Debug)]
pub struct TrieDatalessNode {
    #[cfg_attr(feature = "serde", serde(rename = "c"))]
    pub(crate) children: ChildStorage<TrieDatalessNode>,
    #[cfg_attr(feature = "serde", serde(rename = "we"))]
    word_end: bool,
}

impl TrieDatalessNode {
    /// Returns a new instance of a TrieNode.
    pub(crate) fn new() -> Self {
        TrieDatalessNode {
            children: Default::default(),
            word_end: false,
        }
    }

    /// Recursive function for inserting found words from the given node and
    /// given starting substring.
    pub(crate) fn find_words(&self, substring: &mut String, found_words: &mut Vec<String>) {
        if self.is_associated() {
            found_words.push(substring.to_string());
        }

        for (&character, node) in self.children.iter() {
            substring.push(character);
            node.find_words(substring, found_words);
            substring.pop();
        }
    }

    /// The recursive function for finding a vector of shortest and longest words in the TrieNode consists of:
    /// - the DFS tree traversal part for getting to every child node;
    /// - matching lengths of found words in combination with the passed ordering.
    pub(crate) fn words_min_max(
        &self,
        substring: &mut String,
        current_visual_len: usize,
        found_words: &mut Vec<String>,
        current_best_len: &mut Option<usize>,
        #[cfg(feature = "unicode")] is_path_pure_ascii: bool,
        ord: Ordering,
    ) {
        if self.is_associated() {
            match current_best_len {
                Some(best_len) => match current_visual_len.cmp(best_len) {
                    o if o == ord => {
                        *best_len = current_visual_len;
                        found_words.clear();
                        found_words.push(substring.clone());
                    }
                    Ordering::Equal => {
                        found_words.push(substring.clone());
                    }
                    _ => {}
                },
                None => {
                    *current_best_len = Some(current_visual_len);
                    found_words.push(substring.clone());
                }
            }
        }

        for (&character, node) in self.children.iter() {
            substring.push(character);

            #[cfg(feature = "unicode")]
            let (next_visual_len, next_is_ascii) = if is_path_pure_ascii && character.is_ascii() {
                (current_visual_len + 1, true)
            } else {
                (substring.graphemes(true).count(), false)
            };

            #[cfg(not(feature = "unicode"))]
            let next_visual_len = current_visual_len + 1;

            node.words_min_max(
                substring,
                next_visual_len,
                found_words,
                current_best_len,
                #[cfg(feature = "unicode")]
                next_is_ascii,
                ord,
            );
            substring.pop();
        }
    }

    /// Recursive function that drops all children maps
    /// regardless of having multiple words branching from them or not.
    /// Counts the number of words removed.
    pub(crate) fn remove_all_words(&mut self) -> usize {
        let num_removed = self
            .children
            .values_mut()
            .map(|child| child.remove_all_words())
            .sum::<usize>()
            + self.is_associated() as usize;

        self.clear_children();

        num_removed
    }

    /// Recursive function that counts the number of words from a starting node.
    pub(crate) fn count_words(&self) -> usize {
        self.children
            .values()
            .map(|child| child.count_words())
            .sum::<usize>()
            + self.is_associated() as usize
    }

    /// Recursive function for removing and freeing memory of a word that is not needed anymore.
    /// The algorithm first finds the last node of a word given in the form of a character iterator,
    /// then it frees the maps and unwinds to the first node that should not be deleted.
    /// The first node that should not be deleted is either:
    /// - the root node
    /// - the node that has multiple words branching from it
    /// - the node that represents an end to some word with the same prefix
    /// The last node's data is propagated all the way to the final return
    /// with the help of auxiliary 'RemoveData<D>' struct.
    pub(crate) fn remove_one_word(&mut self, mut characters: impl Iterator<Item = char>) -> bool {
        let next_character = match characters.next() {
            None => {
                self.disassociate();
                return false;
            }
            Some(char) => char,
        };

        let next_node = self.children.get_mut(next_character).unwrap();
        let must_keep = next_node.remove_one_word(characters);

        if self.children.len() > 1 || must_keep {
            return true;
        }
        self.clear_children();

        self.is_associated()
    }

    /// Function marks the node as an end of a word.
    pub(crate) fn associate(&mut self) {
        self.word_end = true;
    }

    /// Function unmarks the node as an end of a word.
    pub(crate) fn disassociate(&mut self) {
        self.word_end = false;
    }

    pub(crate) fn is_associated(&self) -> bool {
        self.word_end
    }

    /// Function removes all children of a node.
    pub(crate) fn clear_children(&mut self) {
        self.children = Default::default();
    }
}

impl ops::AddAssign for TrieDatalessNode {
    /// Overriding the += operator on nodes.
    /// Function adds two nodes based on the principle:
    /// for every child node and character in the 'rhs' node:
    /// - if the self node doesn't have that character in its children map,
    /// simply move the pointer to the self's children map without any extra cost;
    /// - if the self node has that character, the node of that character (self's child)
    /// is added with the 'rhs's' node.
    /// An edge case exists when the 'rhs's' node has an association but self's node doesn't.
    /// That association is handled based on the result of 'rhc_next_node.word_end'.
    /// On true, the self node vector is initialized with the 'rhc' node vector.
    fn add_assign(&mut self, rhs: Self) {
        for (char, rhs_next_node) in rhs.children.into_iter() {
            // Does self contain the character?
            match self.children.remove(char) {
                // The whole node is removed, as owned, operated on and returned in self's children.
                Some(mut self_next_node) => {
                    // Edge case: associate self node if the other node is also associated
                    // Example: when adding 'word' to 'word1', 'd' on 'word' needs to be associated
                    if rhs_next_node.word_end {
                        self_next_node.word_end = true;
                    }

                    self_next_node += rhs_next_node;
                    self.children.insert_direct(char, self_next_node);
                }
                // Self doesn't contain the character, no conflict arises.
                // The whole 'rhs' node is just moved from 'rhs' into self.
                None => {
                    self.children.insert_direct(char, rhs_next_node);
                }
            }
        }
    }
}

impl PartialEq for TrieDatalessNode {
    fn eq(&self, other: &Self) -> bool {
        // If keys aren't equal, nodes aren't equal.
        if !self.children.has_same_keys(&other.children) {
            return false;
        }

        // If the node on one trie is a word end, and on the other it isn't, two nodes aren't equal.
        if self.word_end != other.word_end {
            return false;
        }

        // Every child node that has the same key (character) must be equal.
        self.children
            .iter()
            .map(|(char, self_child)| (self_child, other.children.get(*char).unwrap()))
            .all(|(self_child, other_child)| other_child == self_child)
    }
}