class Sycamore::TreePath

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

node[R]
parent[R]

Public Class Methods

[](*args)
Alias for: of
of(*args) click to toggle source

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
Also aliased as: []
root() click to toggle source

@return the root of all Paths

# File lib/sycamore/path.rb, line 44
def self.root
  ROOT
end

Private Class Methods

new(parent, node) click to toggle source

@private

# File lib/sycamore/path.rb, line 37
def initialize(parent, node)
  @parent, @node = parent, node
end

Public Instance Methods

+(*nodes)
Alias for: branch
/(*nodes)
Alias for: branch
==(other) click to toggle source

@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
branch(*nodes) click to toggle source

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
Also aliased as: +, /
each(&block)
Alias for: each_node
each_node() { |node| ... } click to toggle source

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
Also aliased as: each
eql?(other) click to toggle source

@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
hash() click to toggle source

@return [Fixnum] hash code for this path

# File lib/sycamore/path.rb, line 207
def hash
  to_a.hash ^ self.class.hash
end
in?(struct)
Alias for: present_in?
inspect() click to toggle source

@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
join(separator = '/') click to toggle source

@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
length() click to toggle source

@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
Also aliased as: size
present_in?(struct) click to toggle source

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
Also aliased as: in?
root?() click to toggle source

@return [Boolean] if this is the root path

# File lib/sycamore/path.rb, line 138
def root?
  false
end
size()
Alias for: length
to_s() click to toggle source

@return [String] a compact string representation of this path

# File lib/sycamore/path.rb, line 254
def to_s
  "#<Path: #{join}>"
end
up(distance = 1) click to toggle source

@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