Class: Sycamore::Path

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Enumerable
Defined in:
lib/sycamore/path.rb,
lib/sycamore/path_root.rb

Overview

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.

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.

Examples:

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

Direct Known Subclasses

Root

Defined Under Namespace

Classes: Root

Constant Summary collapse

ROOT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

API:

  • private

Root.instance

Instance Attribute Summary collapse

Construction collapse

Elements collapse

Equality collapse

Conversion collapse

Instance Attribute Details

#nodeObject (readonly)

Returns the value of attribute node.



28
29
30
# File 'lib/sycamore/path.rb', line 28

def node
  @node
end

#parentObject (readonly)

Returns the value of attribute parent.



28
29
30
# File 'lib/sycamore/path.rb', line 28

def parent
  @parent
end

Class Method Details

.of(path, nodes) ⇒ Path .of(nodes) ⇒ Path Also known as: []

Creates a new path.

Depending on whether the first argument is a Sycamore::Path, the new Path is #branched from this path or the root.

Overloads:

  • .of(path, nodes) ⇒ Path

    Returns the #branched path from the given path, with the given nodes expanded.

    Parameters:

    • the path from which should be #branched

    Returns:

    • the #branched path from the given path, with the given nodes expanded

  • .of(nodes) ⇒ Path

    Returns the #branched path from the root, with the given nodes.

    Parameters:

    Returns:

    • the #branched path from the root, with the given nodes



63
64
65
66
67
68
69
# 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

.rootObject

Returns the root of all Paths.

Returns:

  • the root of all Paths



44
45
46
# File 'lib/sycamore/path.rb', line 44

def self.root
  ROOT
end

Instance Method Details

#==(other) ⇒ Boolean

Returns if the other is an Enumerable with the same nodes in the same order.

Parameters:

Returns:

  • if the other is an Enumerable with the same nodes in the same order



226
227
228
229
230
# 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) ⇒ Path Also known as: +, /

Returns a new path based on this path, but with the given nodes extended.

Examples:

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

Parameters:

  • an arbitrary number of nodes

Returns:

Raises:

  • if one or more of the given nodes is an Enumerable



98
99
100
101
102
103
104
105
106
107
108
109
# 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

#each_node {|node| ... } ⇒ Object #each_nodeEnumerator<node> Also known as: each

Iterates over all nodes on this path.

Overloads:

  • #each_node {|node| ... } ⇒ Object

    Yields:

    • (node)

      each node

  • #each_nodeEnumerator<node>

    Returns:



162
163
164
165
166
167
168
169
# 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

#eql?(other) ⇒ Boolean

Returns if the other is a Path with the same nodes in the same order.

Parameters:

Returns:

  • if the other is a Path with the same nodes in the same order



215
216
217
218
219
220
# 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

#hashFixnum

Returns hash code for this path.

Returns:

  • hash code for this path



207
208
209
# File 'lib/sycamore/path.rb', line 207

def hash
  to_a.hash ^ self.class.hash
end

#inspectString

Returns a more verbose string representation of this path.

Returns:

  • a more verbose string representation of this path



261
262
263
# File 'lib/sycamore/path.rb', line 261

def inspect
  "#<Sycamore::Path[#{each_node.map(&:inspect).join(',')}]>"
end

#join(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.

Returns a string created by converting each node on this path to a string, separated by the given separator.

Examples:

Sycamore::Path[1,2,3].join       # => '/1/2/3'
Sycamore::Path[1,2,3].join('|')  # => '|1|2|3'

Parameters:

  • (defaults to: '/')

Returns:

  • a string created by converting each node on this path to a string, separated by the given separator



247
248
249
# File 'lib/sycamore/path.rb', line 247

def join(separator = '/')
  @parent.join(separator) + separator + node.to_s
end

#lengthInteger Also known as: size

Returns the number of nodes on this path.

Returns:

  • the number of nodes on this path



145
146
147
148
149
# File 'lib/sycamore/path.rb', line 145

def length
  i, parent = 1, self
  i += 1 until (parent = parent.parent).root?
  i
end

#present_in?(struct) ⇒ Boolean Also known as: in?

If a given structure contains this path.

Examples:

hash = {foo: {bar: :baz}}
Sycamore::Path[:foo, :bar].present_in? hash  # => true
Sycamore::Path[:foo, :bar].present_in? Tree[hash]  # => true

Parameters:

Returns:

  • if the given structure contains the nodes on this path



184
185
186
187
188
189
190
191
192
193
194
195
196
# 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

#root?Boolean

Returns if this is the root path.

Returns:

  • if this is the root path



138
139
140
# File 'lib/sycamore/path.rb', line 138

def root?
  false
end

#to_sString

Returns a compact string representation of this path.

Returns:

  • a compact string representation of this path



254
255
256
# File 'lib/sycamore/path.rb', line 254

def to_s
  "#<Path: #{join}>"
end

#up(distance = 1) ⇒ Path

Returns the n-th last parent path.

Examples:

path = Sycamore::Path[:foo, :bar, :baz]
path.up     # => Sycamore::Path[:foo, :bar]
path.up(2)  # => Sycamore::Path[:foo]
path.up(3)  # => Sycamore::Path[]

Parameters:

  • (defaults to: 1)

    the number of nodes to go up

Returns:

  • the n-th last parent path

Raises:



124
125
126
127
128
129
130
131
132
133
# 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