# frozen_string_literal: true module CoreExtensions # N-ary tree data structure. # Each node of the tree contains an +Object+ (the node value) and can be accessed from its parent # node using an identifier +Object+. A node can be identified uniquely by the succession of # identifiers that lead from the tree root to itself. class Tree # The value of the current tree node. attr_accessor :value # The child nodes of the current tree node. # @return [Hash] attr_reader :children_nodes # Creates a new tree with a root element and no child nodes. # @param node_value [Object] The value associated to the root element def initialize(node_value = nil) @value = node_value @children_nodes = {} end # Queries # Check if the current Tree node has children. def children? !@children_nodes.empty? end # Tests whether the current tree node has a specific child. # @param child_id [Object] The unique identifier of the child. # @return [Boolean] True if the child node identified by +child_id+ exists, False otherwise. def child_exists?(child_id) child_node(child_id) true rescue KeyError false end # Retrieves the child of the current tree node. # @param child_id [Object] The unique identifier of the child. # @return [Object] The node value of the child identified by +child_id+. # @raise [KeyError] If the child node identified by +child_id+ does not exist. def child_node(child_id) @children_nodes.fetch(child_id) end # Returns a hash representation of the current tree node and all its children, recursively # (depth-first). # @param separator [String] The separator to be used between node identifiers. # @return [Hash] def to_h(separator = '/', id_prefix = '') hash = {} id_prefix.empty? ? hash[separator] = @value : hash[id_prefix] = @value @children_nodes.each do |id, node| hash.merge!(node.to_h(separator, id_prefix + separator + id.to_s)) end hash end # Returns the string representation of the current tree node and all its children, recursively # (depth-first). # @return [String] def to_s(offset = '') str = offset + "VALUE: #{@value}\n" @children_nodes.each do |id, node| str += offset + " * ID: '#{id}'\n" str += node.to_s("#{offset} ") end str end # Retrieves the value associated to a child node of the current tree node, inserting it first if # it does not exist. # @param child_id [Object] The unique identifier for the child node to retrieve or insert. # @param child_value [Object] The value associated to the child node to insert. Ignored if a # child node identified by the given +child_id+ already exists. # @return [Object] The value associated to the child node identified by the given +child_id+. def add_child_node(child_id, child_value = nil) @children_nodes[child_id] = Tree.new(child_value) if @children_nodes[child_id].nil? @children_nodes[child_id] end end end