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.

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.



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

def node
  @node
end

#parentObject (readonly)

Returns the value of attribute parent.



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

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:

    • path (Path)

      the path from which should be #branched

    • nodes (nodes)

    Returns:

    • (Path)

      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:

    • nodes (nodes)

    Returns:



62
63
64
65
66
67
68
# File 'lib/sycamore/path.rb', line 62

def self.of(*args)
  if (parent = args.first).is_a? Path
    parent.branch(*args[1..])
  else
    root.branch(*args)
  end
end

.rootObject

Returns the root of all Paths.

Returns:

  • the root of all Paths



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

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:

  • other (Object)

Returns:

  • (Boolean)

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



225
226
227
228
229
# File 'lib/sycamore/path.rb', line 225

def ==(other)
  other.is_a?(Enumerable) and 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:

  • nodes (nodes)

    an arbitrary number of nodes

Returns:

Raises:

  • (InvalidNode)

    if one or more of the given nodes is an Enumerable



97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/sycamore/path.rb', line 97

def branch(*nodes)
  return branch(*nodes.first) if nodes.size == 1 && 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:

    • (Enumerator<node>)


161
162
163
164
165
166
167
168
# File 'lib/sycamore/path.rb', line 161

def each_node(&block)
  return enum_for(__callee__) unless block

  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:

  • other (Object)

Returns:

  • (Boolean)

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



214
215
216
217
218
219
# File 'lib/sycamore/path.rb', line 214

def eql?(other)
  other.is_a?(self.class) and
    length == other.length and begin
      i = other.each; all? { |node| node.eql? i.next }
    end
end

#hashFixnum

Returns hash code for this path.

Returns:

  • (Fixnum)

    hash code for this path



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

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

#inspectString

Returns a more verbose string representation of this path.

Returns:

  • (String)

    a more verbose string representation of this path



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

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:

  • separator (String) (defaults to: "/")

Returns:

  • (String)

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



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

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:

  • (Integer)

    the number of nodes on this path



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

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:

  • struct (Object)

Returns:

  • (Boolean)

    if the given structure contains the nodes on this path



183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/sycamore/path.rb', line 183

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:

  • (Boolean)

    if this is the root path



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

def root?
  false
end

#to_sString

Returns a compact string representation of this path.

Returns:

  • (String)

    a compact string representation of this path



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

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:

  • distance (Integer) (defaults to: 1)

    the number of nodes to go up

Returns:

  • (Path)

    the n-th last parent path

Raises:

  • (TypeError)


123
124
125
126
127
128
129
130
131
132
# File 'lib/sycamore/path.rb', line 123

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