lib/tangle/base_graph.rb
# frozen_string_literal: true
require_relative 'currify'
require_relative 'mixin'
require_relative 'base_graph_protected'
require_relative 'base_graph_private'
module Tangle
#
# Abstract base class for (un)directed graphs
#
class BaseGraph
include Tangle::Currify
include Tangle::Mixin::Initialize
include Tangle::BaseGraphProtected
include Tangle::BaseGraphPrivate
# Initialize a new graph, preloading it with vertices and edges
#
# Graph[+vertices+] => Graph
# Graph[+vertices+, +edges+) => Graph
#
# When +vertices+ is a hash, it contains initialization kwargs as
# values and vertex names as keys. When +vertices+ is an array of
# initialization kwargs, the vertices will be be anonymous.
#
# +edges+ can contain an array of exactly two, either names of vertices
# or vertices.
#
# Any kwarg supported by Graph.new is also allowed.
#
def self.[](vertices, edges = {}, **kwargs)
graph = new(**kwargs)
vertices.each { |vertex| graph.add_vertex(vertex) }
edges.each { |from, to| graph.add_edge(from, to) }
graph
end
# Initialize a new graph, optionally preloading it with vertices and edges
#
# Graph.new() => Graph
# Graph.new(mixins: [MixinModule, ...], ...) => Graph
#
# +mixins+ is an array of modules that can be mixed into the various
# classes that makes up a graph. Initialization of a Graph, Vertex or Edge
# looks for submodules in each mixin, with the same name and extends
# any created object. Defaults to [Tangle::Mixin::Connectedness].
#
# Any subclass of Graph should also subclass Edge to manage its unique
# constraints.
#
def initialize(currify: false, mixins: [], **kwargs)
@currify = currify
initialize_vertices
initialize_edges
initialize_mixins(mixins: mixins, **kwargs)
end
# Return a subgraph, optionally filtered by a vertex selector block
#
# subgraph => Graph
# subgraph { |vertex| ... } => Graph
#
# Unless a selector is provided, the subgraph contains the entire graph.
#
def subgraph(included = nil, &selector)
result = clone
result.select_vertices!(included) unless included.nil?
result.select_vertices!(&selector) if block_given?
result
end
def clone
result = super
result.copy_vertices_and_edges(self)
result
end
def to_s
"#<#{self.class}: #{vertices.count} vertices, #{edges.count} edges>"
end
alias inspect to_s
# Fetch a vertex by its name
def fetch(name)
@vertices_by_name.fetch(name)
end
# Return a named vertex
def [](name)
@vertices_by_name[name]
end
# Return all vertices in the graph
def vertices
@vertices.keys
end
# Select vertices in the graph
def select(&selector)
@vertices.each_key.select(&selector)
end
# Add a vertex into the graph
#
# If a name: is given, or the vertex responds to :name,
# it will be registered by name in the graph
def add_vertex(vertex, name: nil)
name ||= callback(vertex, :name)
insert_vertex(vertex, name)
define_currified_methods(vertex, :vertex) if @currify
callback(vertex, :added_to_graph, self)
self
end
alias << add_vertex
# Remove a vertex from the graph
def remove_vertex(vertex)
@vertices[vertex].each do |edge|
remove_edge(edge) if edge.include?(vertex)
end
delete_vertex(vertex)
callback(vertex, :removed_from_graph, self)
end
# Get all edges.
#
# edges => Array
#
def edges(vertex = nil)
return @edges if vertex.nil?
@vertices.fetch(vertex)
end
currify :vertex, :edges
# Add a new edge to the graph
#
# add_edge(vtx1, vtx2, ...) => Edge
#
def add_edge(*vertices, **kvargs)
edge = new_edge(*vertices, mixins: @mixins, **kvargs)
insert_edge(edge)
vertices.each { |vertex| callback(vertex, :edge_added, edge) }
edge
end
# Remove an edge from the graph
def remove_edge(edge)
delete_edge(edge)
edge.each_vertex { |vertex| callback(vertex, :edge_removed, edge) }
end
end
end