notCalle/ruby-tangle

View on GitHub
lib/tangle/base_graph.rb

Summary

Maintainability
A
0 mins
Test Coverage
# 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