2022-04-25 12:24:44 -05:00

335 lines
11 KiB
Ruby

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