class Sycamore::Path
A compact, immutable representation of Tree
paths, i.e. node sequences.
This class is optimized for its usage in {Tree#each_path}, where it can efficiently represent the whole tree as a set of paths by sharing the parent paths. It is not intended to be instantiated by the user.
@example
tree = Tree[foo: [:bar, :baz]] path1, path2 = tree.paths.to_a path1 == Sycamore::Path[:foo, :bar] # => true path2 == Sycamore::Path[:foo, :baz] # => true path1.parent.equal? path2.parent # => true
@todo Measure the performance and memory consumption in comparison with a
pure Array-based implementation (where tree nodes are duplicated), esp. in the most common use case of property-value structures.
Constants
- ROOT
Attributes
Public Class Methods
Creates a new path.
Depending on whether the first argument is a {Path}, the new Path
is {#branch}ed from this path or the {root}.
@overload of(path, nodes)
@param path [Path] the path from which should be {#branch}ed @param nodes [nodes] @return [Path] the {#branch}ed path from the given path, with the given nodes expanded
@overload of(nodes)
@param nodes [nodes] @return [Path] the {#branch}ed path from the {root}, with the given nodes
# File lib/sycamore/path.rb, line 63 def self.of(*args) if (parent = args.first).is_a? Path parent.branch(*args[1..-1]) else root.branch(*args) end end
@return the root of all Paths
# File lib/sycamore/path.rb, line 44 def self.root ROOT end
Private Class Methods
@private
# File lib/sycamore/path.rb, line 37 def initialize(parent, node) @parent, @node = parent, node end
Public Instance Methods
@return [Boolean] if the other is an Enumerable with the same nodes in the same order @param other [Object]
# File lib/sycamore/path.rb, line 226 def ==(other) other.is_a?(Enumerable) and self.length == other.length and begin i = other.each ; all? { |node| node == i.next } end end
Returns a new path based on this path, but with the given nodes extended.
@param nodes [nodes] an arbitrary number of nodes @return [Path]
@raise [InvalidNode] if one or more of the given nodes is an Enumerable
@example
path = Sycamore::Path[:foo, :bar] path.branch(:baz, :qux) == Sycamore::Path[:foo, :bar, :baz, :qux] # => true path / :baz / :qux == Sycamore::Path[:foo, :bar, :baz, :qux] # => true
# File lib/sycamore/path.rb, line 98 def branch(*nodes) return branch(*nodes.first) if nodes.size == 1 and nodes.first.is_a? Enumerable parent = self nodes.each do |node| raise InvalidNode, "#{node} in Path #{nodes.inspect} is not a valid tree node" if node.is_a? Enumerable parent = Path.__send__(:new, parent, node) end parent end
Iterates over all nodes on this path.
@overload each_node
@yield [node] each node
@overload each_node
@return [Enumerator<node>]
# File lib/sycamore/path.rb, line 162 def each_node(&block) return enum_for(__callee__) unless block_given? if @parent @parent.each_node(&block) yield @node end end
@return [Boolean] if the other is a Path
with the same nodes in the same order @param other [Object]
# File lib/sycamore/path.rb, line 215 def eql?(other) other.is_a?(self.class) and self.length == other.length and begin i = other.each ; all? { |node| node.eql? i.next } end end
@return [Fixnum] hash code for this path
# File lib/sycamore/path.rb, line 207 def hash to_a.hash ^ self.class.hash end
@return [String] a more verbose string representation of this path
# File lib/sycamore/path.rb, line 261 def inspect "#<Sycamore::Path[#{each_node.map(&:inspect).join(',')}]>" end
@return [String] a string created by converting each node on this path to a string, separated by the given separator @param separator [String]
@note Since the root path with no node is at the beginning of each path,
the returned string always begins with the given separator.
@example
Sycamore::Path[1,2,3].join # => '/1/2/3' Sycamore::Path[1,2,3].join('|') # => '|1|2|3'
# File lib/sycamore/path.rb, line 247 def join(separator = '/') @parent.join(separator) + separator + node.to_s end
@return [Integer] the number of nodes on this path
# File lib/sycamore/path.rb, line 145 def length i, parent = 1, self i += 1 until (parent = parent.parent).root? i end
If a given structure contains this path.
@param struct [Object] @return [Boolean] if the given structure contains the nodes on this path
@example
hash = {foo: {bar: :baz}} Sycamore::Path[:foo, :bar].present_in? hash # => true Sycamore::Path[:foo, :bar].present_in? Tree[hash] # => true
# File lib/sycamore/path.rb, line 184 def present_in?(struct) each do |node| case when struct.is_a?(Enumerable) return false unless struct.include? node struct = (Tree.like?(struct) ? struct[node] : Nothing ) else return false unless struct.eql? node struct = Nothing end end true end
@return [Boolean] if this is the root path
# File lib/sycamore/path.rb, line 138 def root? false end
@return [String] a compact string representation of this path
# File lib/sycamore/path.rb, line 254 def to_s "#<Path: #{join}>" end
@return [Path] the n-th last parent path @param distance [Integer] the number of nodes to go up
@example
path = Sycamore::Path[:foo, :bar, :baz] path.up # => Sycamore::Path[:foo, :bar] path.up(2) # => Sycamore::Path[:foo] path.up(3) # => Sycamore::Path[]
# File lib/sycamore/path.rb, line 124 def up(distance = 1) raise TypeError, "expected an integer, but got #{distance.inspect}" unless distance.is_a? Integer case distance when 1 then @parent when 0 then self else parent.up(distance - 1) end end