Add a graph implementation and typed vertices
This commit is contained in:
parent
87848eec67
commit
0a26c040f7
334
plugins/commands/serve/mappers/internal/graph.rb
Normal file
334
plugins/commands/serve/mappers/internal/graph.rb
Normal file
@ -0,0 +1,334 @@
|
||||
module VagrantPlugins
|
||||
module CommandServe
|
||||
class Mappers
|
||||
module Internal
|
||||
class Graph
|
||||
autoload :Mappers, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/mappers").to_s
|
||||
autoload :Search, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/search").to_s
|
||||
autoload :Topological, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/topological").to_s
|
||||
autoload :Vertex, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/vertex").to_s
|
||||
autoload :WeightedVertex, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/weighted_vertex").to_s
|
||||
|
||||
# Default weight used for weighted vetices
|
||||
# when no weight is provided
|
||||
DEFAULT_WEIGHT = 1_000_000
|
||||
# Marker value used for lookups
|
||||
VERTEX_ID = :vertex_id
|
||||
|
||||
# @return [Hash<Object, Vertex>] list of vertices within graph
|
||||
attr_reader :vertex_map
|
||||
# @return [Hash<Object, Hash<Object, Symbol>>] incoming adjecency list
|
||||
attr_reader :adjecency_in
|
||||
# @return [Hash<Object, Array<WeightedVertex>] weighted incoming adjecency list
|
||||
attr_reader :adjecency_in_weight
|
||||
# @return [Hash<Object, Hash<Object, Symbol>>] outgoing adjecency list
|
||||
attr_reader :adjecency_out
|
||||
# @return [Hash<Object, Array<WeightedVertex>] weighted outgoing adjecency list
|
||||
attr_reader :adjecency_out_weight
|
||||
|
||||
# Create a new Graph
|
||||
def initialize
|
||||
@vertex_map = {}
|
||||
@adjecency_in = {}
|
||||
@adjecency_in_weight = {}
|
||||
@adjecency_out = {}
|
||||
@adjecency_out_weight = {}
|
||||
@m = Mutex.new
|
||||
end
|
||||
|
||||
# @return [Array<Vertex>] list of vertices
|
||||
def vertices
|
||||
@m.synchronize do
|
||||
vertex_map.values
|
||||
end
|
||||
end
|
||||
|
||||
# Create a copy of the current Graph
|
||||
#
|
||||
# @return [Graph]
|
||||
def copy
|
||||
@m.synchronize do
|
||||
self.class.new.tap do |g|
|
||||
# Copy our vertices list
|
||||
g.vertex_map.replace(vertex_map.dup)
|
||||
|
||||
# Copy incoming edges list
|
||||
new_in = Hash.new.tap do |nin|
|
||||
adjecency_in.each_pair do |k, v|
|
||||
nin[k] = v.dup
|
||||
end
|
||||
end
|
||||
g.adjecency_in.replace(new_in)
|
||||
|
||||
# Copy outgoing edges list
|
||||
new_out = Hash.new.tap do |nout|
|
||||
adjecency_out.each_pair do |k, v|
|
||||
nout[k] = v.dup
|
||||
end
|
||||
end
|
||||
g.adjecency_out.replace(new_out)
|
||||
|
||||
# Copy incoming edge weights
|
||||
in_w = Hash.new.tap do |win|
|
||||
adjecency_in_weight.each_pair do |k, v|
|
||||
win[k] = v.dup
|
||||
end
|
||||
end
|
||||
|
||||
# Copy outgoing edge weights
|
||||
g.adjecency_in_weight.replace(in_w)
|
||||
out_w = Hash.new.tap do |wout|
|
||||
adjecency_out_weight.each_pair do |k, v|
|
||||
wout[k] = v.dup
|
||||
end
|
||||
end
|
||||
g.adjecency_out_weight.replace(out_w)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Create a reversed copy of the current Graph
|
||||
#
|
||||
# @return [Graph]
|
||||
def reverse
|
||||
@m.synchronize do
|
||||
self.class.new.tap do |g|
|
||||
# Copy our vertices list
|
||||
g.vertex_map.replace(vertex_map.dup)
|
||||
|
||||
# Transfer incoming edges list to outgoing edges
|
||||
new_in = Hash.new.tap do |nin|
|
||||
adjecency_in.each_pair do |k, v|
|
||||
nin[k] = v.dup
|
||||
end
|
||||
end
|
||||
g.adjecency_out.replace(new_in)
|
||||
|
||||
# Transfer outgoing edges list to incoming edges
|
||||
new_out = Hash.new.tap do |nout|
|
||||
adjecency_out.each_pair do |k, v|
|
||||
nout[k] = v.dup
|
||||
end
|
||||
end
|
||||
g.adjecency_in.replace(new_out)
|
||||
|
||||
in_w = Hash.new.tap do |win|
|
||||
adjecency_in_weight.each_pair do |k, v|
|
||||
win[k] = v.dup
|
||||
end
|
||||
end
|
||||
# Set out vertices as in
|
||||
g.adjecency_out_weight.replace(in_w)
|
||||
out_w = Hash.new.tap do |wout|
|
||||
adjecency_out_weight.each_pair do |k, v|
|
||||
wout[k] = v.dup
|
||||
end
|
||||
end
|
||||
# Set in vertices as out
|
||||
g.adjecency_in_weight.replace(out_w)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# List of incoming vertices and their weights
|
||||
# to the given vertex
|
||||
#
|
||||
# @param v [Vertex]
|
||||
# @return [Hash<Vertex, Integer>]
|
||||
def weighted_edges_in(v)
|
||||
@m.synchronize do
|
||||
v = add_vertex(v)
|
||||
Hash.tap do |wei|
|
||||
Array(adjecency_in_weight[v.hash_code]).each do |vrt|
|
||||
wei[vertex_map[vrt.hash_code]] = vrt.weight
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# List of outgoing vertices and their weights
|
||||
# from the given vertex
|
||||
#
|
||||
# @param v [Vertex]
|
||||
# @return [Hash<Vertex, Integer>]
|
||||
def weighted_edges_out(v)
|
||||
@m.synchronize do
|
||||
v = add_vertex(v)
|
||||
Hash.tap do |wei|
|
||||
Array(adjecency_out_weight[v.hash_code]).each do |vrt|
|
||||
wei[vertex_map[vrt.hash_code]] = vrt.weight
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# List of incoming vertices to the given vertex
|
||||
#
|
||||
# @param v [Vertex]
|
||||
# @return [Array<Vertex>]
|
||||
def edges_in(v)
|
||||
@m.synchronize do
|
||||
v = add_vertex(v)
|
||||
adjecency_in_weight[v.hash_code].sort_by(&:weight).map do |vrt|
|
||||
vertex_map[vrt.hash_code]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# List of outgoing vertices from the given vertex
|
||||
#
|
||||
# @param v [Vertex]
|
||||
# @return [Array<Vertex>]
|
||||
def edges_out(v)
|
||||
@m.synchronize do
|
||||
v = add_vertex(v)
|
||||
adjecency_out_weight[v.hash_code].sort_by(&:weight).map do |vrt|
|
||||
vertex_map[vrt.hash_code]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Add a vertex to the graph. Vertices are
|
||||
# registered by the vertex's hash code value,
|
||||
# so the returned object may not be the same
|
||||
# object that was provided.
|
||||
#
|
||||
# @param v [Vertex]
|
||||
# @return [Vertex]
|
||||
def add(v)
|
||||
@m.synchronize do
|
||||
add_vertex(v)
|
||||
end
|
||||
end
|
||||
|
||||
# Remove a vertex from the graph
|
||||
#
|
||||
# @param v [Vertex]
|
||||
# @return [Vertex]
|
||||
def remove(v)
|
||||
@m.synchronize do
|
||||
v = add_vertex(v)
|
||||
vertex_map.delete(v.hash_code)
|
||||
|
||||
adjecency_out.delete(v.hash_code)
|
||||
adjecency_in.delete(v.hash_code)
|
||||
|
||||
adjecency_out_weight.delete(v.hash_code)
|
||||
adjecency_in_weight.delete(v.hash_code)
|
||||
|
||||
adjecency_in.each_pair do |_, invrt|
|
||||
invrt.delete(v.hash_code)
|
||||
end
|
||||
adjecency_in_weight.values.each do |list|
|
||||
list.delete_if { |vrt| vrt.hash_code == v.hash_code }
|
||||
end
|
||||
|
||||
adjecency_out.each_pair do |_, outvrt|
|
||||
outvrt.delete(v.hash_code)
|
||||
end
|
||||
adjecency_out_weight.values.each do |list|
|
||||
list.delete_if { |vrt| vrt.hash_code == v.hash_code }
|
||||
end
|
||||
|
||||
v
|
||||
end
|
||||
end
|
||||
|
||||
# Add an edge from one vertex to another
|
||||
#
|
||||
# @param from [Vertex]
|
||||
# @param to [Vertex]
|
||||
# @return [self]
|
||||
def add_edge(from, to)
|
||||
add_weighted_edge(from, to, DEFAULT_WEIGHT)
|
||||
self
|
||||
end
|
||||
|
||||
# Add a weighted edge from one vertex to another
|
||||
#
|
||||
# @param from [Vertex]
|
||||
# @param to [Vertex]
|
||||
# @param weight [Integer]
|
||||
# @return [self]
|
||||
def add_weighted_edge(from, to, weight)
|
||||
@m.synchronize do
|
||||
from = add_vertex(from)
|
||||
to = add_vertex(to)
|
||||
adjecency_out[from.hash_code][to.hash_code] = VERTEX_ID
|
||||
adjecency_out_weight[from.hash_code] << WeightedVertex.new(
|
||||
code: to.hash_code,
|
||||
weight: weight
|
||||
)
|
||||
adjecency_in[to.hash_code][from.hash_code] = VERTEX_ID
|
||||
adjecency_in_weight[to.hash_code] << WeightedVertex.new(
|
||||
code: from.hash_code,
|
||||
weight: weight
|
||||
)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# Remove an edge from one vertex to another
|
||||
#
|
||||
# @param from [Vertex]
|
||||
# @param to [Vertex]
|
||||
# @return [self]
|
||||
def remove_edge(from, to)
|
||||
@m.synchronize do
|
||||
from = add_vertex(from)
|
||||
to = add_vertex(to)
|
||||
adjecency_in[to.hash_code].delete(from.hash_code)
|
||||
adjecency_in_weight[to.hash_code].delete_if { |vrt|
|
||||
vrt.hash_code == from.hash_code }
|
||||
adjecency_out[from.hash_code].delete(to.hash_code)
|
||||
adjecency_out_weight[from.hash_code].delete_if { |vrt|
|
||||
vrt.hash_code == to.hash_code }
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# Finalize the graph for use. This will ensure all
|
||||
# edges are properly sorted if weighted.
|
||||
def finalize!
|
||||
# @m.synchronize do
|
||||
# adjecency_in_weight.each_pair do |code, list|
|
||||
# list.sort_by!(&:weight)
|
||||
# adjecency_in[code] = Hash[list.map{ |w| [w.hash_code, VERTEX_ID] }]
|
||||
# end
|
||||
# adjecency_out_weight.each_pair do |code, list|
|
||||
# list.sort_by!(&:weight)
|
||||
# adjecency_out[code] = Hash[list.map{ |w| [w.hash_code, VERTEX_ID] }]
|
||||
# end
|
||||
# end
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Adds a vertex to the graph and initializes
|
||||
# edge data structures. If a hash code for the
|
||||
# vertex has already been registered, the registered
|
||||
# vertex will be returned (which may be different than
|
||||
# the provided vertex)
|
||||
#
|
||||
# @param v [Vertex]
|
||||
# @return [Vertex]
|
||||
def add_vertex(v)
|
||||
if !v.is_a?(Vertex)
|
||||
raise TypeError,
|
||||
"Expected type `Vertex', got `#{v.class}'"
|
||||
end
|
||||
if vertex_map.key?(v.hash_code)
|
||||
return vertex_map[v.hash_code]
|
||||
end
|
||||
adjecency_in[v.hash_code] = {}
|
||||
adjecency_in_weight[v.hash_code] = []
|
||||
adjecency_out[v.hash_code] = {}
|
||||
adjecency_out_weight[v.hash_code] = []
|
||||
vertex_map[v.hash_code] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
52
plugins/commands/serve/mappers/internal/graph/vertex.rb
Normal file
52
plugins/commands/serve/mappers/internal/graph/vertex.rb
Normal file
@ -0,0 +1,52 @@
|
||||
require 'securerandom'
|
||||
|
||||
module VagrantPlugins
|
||||
module CommandServe
|
||||
class Mappers
|
||||
module Internal
|
||||
class Graph
|
||||
# Represents a vertex within the graph
|
||||
class Vertex
|
||||
autoload :Input, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/vertex/input").to_s
|
||||
autoload :Method, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/vertex/method").to_s
|
||||
autoload :Output, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/vertex/output").to_s
|
||||
autoload :Value, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/vertex/value").to_s
|
||||
# @return [Object] value of vertex
|
||||
attr_reader :value
|
||||
|
||||
# Create a new vertex
|
||||
#
|
||||
# @param value [Object]
|
||||
def initialize(value:)
|
||||
@value = value
|
||||
end
|
||||
|
||||
# Hash code of the vertex. Vertices
|
||||
# are unique within a graph based on
|
||||
# their hash code value. By default,
|
||||
# the `#object_id` is used
|
||||
def hash_code
|
||||
@code ||= SecureRandom.uuid
|
||||
end
|
||||
|
||||
# By default, only a single edge must
|
||||
# be fulfilled to allow the path through
|
||||
# the vertex
|
||||
def incoming_edges_required
|
||||
false
|
||||
end
|
||||
|
||||
# Executes the vertex if applicable
|
||||
def call(*_)
|
||||
value
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<Vertex:#{object_id} hash=#{hash_code} value=#{value}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,31 @@
|
||||
module VagrantPlugins
|
||||
module CommandServe
|
||||
class Mappers
|
||||
module Internal
|
||||
class Graph
|
||||
class Vertex
|
||||
# Vertex that represents an input value
|
||||
# for a method
|
||||
class Input < Vertex
|
||||
attr_reader :type
|
||||
|
||||
def initialize(type:)
|
||||
@type = type
|
||||
end
|
||||
|
||||
# When an input Vertex is called,
|
||||
# we simply set the value for use
|
||||
def call(arg)
|
||||
@value = arg
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<Vertex:Input:#{object_id} hash=#{hash_code} type=#{type} value=#{value}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,42 @@
|
||||
module VagrantPlugins
|
||||
module CommandServe
|
||||
class Mappers
|
||||
module Internal
|
||||
class Graph
|
||||
class Vertex
|
||||
# Vertex that represents a method
|
||||
class Method < Vertex
|
||||
def initialize(callable:)
|
||||
@callable = callable
|
||||
end
|
||||
|
||||
# Since this vertex is a method to
|
||||
# execute, and requires the defined
|
||||
# input arguments, all incoming edges
|
||||
# are required
|
||||
def incoming_edges_required
|
||||
true
|
||||
end
|
||||
|
||||
# When a method vertex is called,
|
||||
# we execute the mapper method and
|
||||
# store the value
|
||||
def call(*args)
|
||||
# If the callable is a mapper, setup the correct
|
||||
# arguments before calling
|
||||
if @callable.respond_to?(:determine_inputs)
|
||||
args = @callable.determine_inputs(*args)
|
||||
end
|
||||
@value = @callable.call(*args)
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<Vertex:Method:#{object_id} hash=#{hash_code} callable=#{@callable} value=#{value}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,31 @@
|
||||
module VagrantPlugins
|
||||
module CommandServe
|
||||
class Mappers
|
||||
module Internal
|
||||
class Graph
|
||||
class Vertex
|
||||
# Vertex that represents an output of
|
||||
# a method
|
||||
class Output < Vertex
|
||||
attr_reader :type
|
||||
|
||||
def initialize(type:)
|
||||
@type = type
|
||||
end
|
||||
|
||||
# When an output Vertex is called,
|
||||
# we simply set the value for use
|
||||
def call(arg)
|
||||
@value = arg
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<Vertex:Output:#{object_id} hash=#{hash_code} type=#{type} value=#{value}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,28 @@
|
||||
module VagrantPlugins
|
||||
module CommandServe
|
||||
class Mappers
|
||||
module Internal
|
||||
class Graph
|
||||
class Vertex
|
||||
# Vertex that represents a value
|
||||
class Value < Vertex
|
||||
# @return [Class] hash code for value
|
||||
def hash_code
|
||||
value.class
|
||||
end
|
||||
|
||||
# @return [Class] type of the value
|
||||
def type
|
||||
value.class
|
||||
end
|
||||
|
||||
def inspect
|
||||
"<Vertex:Value:#{object_id} hash=#{hash_code} type=#{type} value=#{value}>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,28 @@
|
||||
module VagrantPlugins
|
||||
module CommandServe
|
||||
class Mappers
|
||||
module Internal
|
||||
class Graph
|
||||
# Wrapper around a vertex and used within the
|
||||
# graph to allow weighting edges for path
|
||||
# preference. All vertices within a graph are
|
||||
# WeightedVertex instances. Paths with the
|
||||
# lowest weights are preferred.
|
||||
class WeightedVertex
|
||||
attr_reader :hash_code
|
||||
attr_reader :weight
|
||||
|
||||
def initialize(code:, weight:)
|
||||
@hash_code = code
|
||||
if !weight.is_a?(Integer)
|
||||
raise TypeError,
|
||||
"Expected `Integer' type for weight, got `#{weight.class}'"
|
||||
end
|
||||
@weight = weight
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user