tangle 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49ee5268eb1851a1087ba45efac9f00794a248dfce75aadab846f6617d91620e
4
- data.tar.gz: cc4d0d76f99b86c5494758311637bcb4610555afb58118e059833f62992ef492
3
+ metadata.gz: dc791cdb0b95ae60507e9052e96b8c0c44550a1cde4ac88a5ab9be6661102044
4
+ data.tar.gz: bf20ee14175b42f5ae9e4675ca50d2c76937e1cc09493e7420a78eaf8885aa79
5
5
  SHA512:
6
- metadata.gz: c785bb5d7b83a53db61c03ffff2b6a26c17659eb9f79868e4882c884d0e97339b8748d32778bd2e73bb5293b5dd3a9a9adfef22ff38ab9e0f2c1c660dc0dd09c
7
- data.tar.gz: f042503d2a4d65f6f9ed4cce56e6ba94623ef6e57e73b306a690edce5d2276aa01a7e211d83a2d2aec096a85def0923e4c8da350eb417a37101217764c84c18e
6
+ metadata.gz: 3fba37c004a711c3ed33f6966853724023b5c1dbbe61d596342fa91259a46b490153147f16b12e01f8f9b5cc66156f34197fd91ff357c50e765afb8c8571f564
7
+ data.tar.gz: 0c18db46c410650ba81c4e90c891b1bedb19d0eb3e92079d68c709055883ac12b231162cd8235cbdb81fa4b7ab1bc7f4ad3d2281c0944a5c35387b2a8dc609c4
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  .rspec_status
12
12
  /Gemfile.lock
13
13
  /vendor
14
+ coverage
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
1
4
  Metrics/BlockLength:
2
5
  Exclude:
3
6
  - 'spec/*_spec.rb'
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/setup'
2
4
  require 'bundler/gem_tasks'
3
5
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'tangle'
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tangle/version'
2
4
  require 'tangle/errors'
3
- require 'tangle/graph'
4
- require 'tangle/simple/graph'
5
+ require 'tangle/undirected/graph'
6
+ require 'tangle/undirected/simple/graph'
5
7
  require 'tangle/directed/graph'
6
8
  require 'tangle/directed/acyclic/graph'
7
9
 
@@ -20,8 +22,9 @@ require 'tangle/directed/acyclic/graph'
20
22
  # => Directed graph with no edge cycles
21
23
  #
22
24
  module Tangle
23
- MultiGraph = Tangle::Graph
24
- SimpleGraph = Tangle::Simple::Graph
25
+ Graph = Tangle::Undirected::Graph
26
+ MultiGraph = Tangle::Undirected::Graph
27
+ SimpleGraph = Tangle::Undirected::Simple::Graph
25
28
  DiGraph = Tangle::Directed::Graph
26
29
  DAG = Tangle::Directed::Acyclic::Graph
27
30
  end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'currify'
4
+ require_relative 'mixin'
5
+ require_relative 'base_graph_protected'
6
+ require_relative 'base_graph_private'
7
+
8
+ module Tangle
9
+ #
10
+ # Abstract base class for (un)directed graphs
11
+ #
12
+ class BaseGraph
13
+ include Tangle::Currify
14
+ include Tangle::Mixin::Initialize
15
+ include Tangle::BaseGraphProtected
16
+ include Tangle::BaseGraphPrivate
17
+
18
+ # Initialize a new graph, preloading it with vertices and edges
19
+ #
20
+ # Graph[+vertices+] => Graph
21
+ # Graph[+vertices+, +edges+) => Graph
22
+ #
23
+ # When +vertices+ is a hash, it contains initialization kwargs as
24
+ # values and vertex names as keys. When +vertices+ is an array of
25
+ # initialization kwargs, the vertices will be be anonymous.
26
+ #
27
+ # +edges+ can contain an array of exactly two, either names of vertices
28
+ # or vertices.
29
+ #
30
+ # Any kwarg supported by Graph.new is also allowed.
31
+ #
32
+ def self.[](vertices, edges = {}, **kwargs)
33
+ graph = new(**kwargs)
34
+ vertices.each { |vertex| graph.add_vertex(vertex) }
35
+ edges.each { |from, to| graph.add_edge(from, to) }
36
+ graph
37
+ end
38
+
39
+ # Initialize a new graph, optionally preloading it with vertices and edges
40
+ #
41
+ # Graph.new() => Graph
42
+ # Graph.new(mixins: [MixinModule, ...], ...) => Graph
43
+ #
44
+ # +mixins+ is an array of modules that can be mixed into the various
45
+ # classes that makes up a graph. Initialization of a Graph, Vertex or Edge
46
+ # looks for submodules in each mixin, with the same name and extends
47
+ # any created object. Defaults to [Tangle::Mixin::Connectedness].
48
+ #
49
+ # Any subclass of Graph should also subclass Edge to manage its unique
50
+ # constraints.
51
+ #
52
+ def initialize(currify: false, mixins: [], **kwargs)
53
+ @currify = currify
54
+ initialize_vertices
55
+ initialize_edges
56
+ initialize_mixins(mixins: mixins, **kwargs)
57
+ end
58
+
59
+ # Return a subgraph, optionally filtered by a vertex selector block
60
+ #
61
+ # subgraph => Graph
62
+ # subgraph { |vertex| ... } => Graph
63
+ #
64
+ # Unless a selector is provided, the subgraph contains the entire graph.
65
+ #
66
+ def subgraph(included = nil, &selector)
67
+ result = clone
68
+ result.select_vertices!(included) unless included.nil?
69
+ result.select_vertices!(&selector) if block_given?
70
+ result
71
+ end
72
+
73
+ def to_s
74
+ "#<#{self.class}: #{vertices.count} vertices, #{edges.count} edges>"
75
+ end
76
+ alias inspect to_s
77
+
78
+ # Fetch a vertex by its name
79
+ def fetch(name)
80
+ @vertices_by_name.fetch(name)
81
+ end
82
+
83
+ # Return a named vertex
84
+ def [](name)
85
+ @vertices_by_name[name]
86
+ end
87
+
88
+ # Return all vertices in the graph
89
+ def vertices
90
+ @vertices.keys
91
+ end
92
+
93
+ # Select vertices in the graph
94
+ def select(&selector)
95
+ @vertices.each_key.select(&selector)
96
+ end
97
+
98
+ # Add a vertex into the graph
99
+ #
100
+ # If a name: is given, or the vertex responds to :name,
101
+ # it will be registered by name in the graph
102
+ def add_vertex(vertex, name: nil)
103
+ name ||= callback(vertex, :name)
104
+ insert_vertex(vertex, name)
105
+ define_currified_methods(vertex, :vertex) if @currify
106
+ callback(vertex, :added_to_graph, self)
107
+ self
108
+ end
109
+ alias << add_vertex
110
+
111
+ # Remove a vertex from the graph
112
+ def remove_vertex(vertex)
113
+ @vertices[vertex].each do |edge|
114
+ remove_edge(edge) if edge.include?(vertex)
115
+ end
116
+ delete_vertex(vertex)
117
+ callback(vertex, :removed_from_graph, self)
118
+ end
119
+
120
+ # Get all edges.
121
+ #
122
+ # edges => Array
123
+ #
124
+ def edges(vertex = nil)
125
+ return @edges if vertex.nil?
126
+ @vertices.fetch(vertex)
127
+ end
128
+ currify :vertex, :edges
129
+
130
+ # Add a new edge to the graph
131
+ #
132
+ # add_edge(vtx1, vtx2, ...) => Edge
133
+ #
134
+ def add_edge(*vertices, **kvargs)
135
+ edge = new_edge(*vertices, mixins: @mixins, **kvargs)
136
+ insert_edge(edge)
137
+ vertices.each { |vertex| callback(vertex, :edge_added, edge) }
138
+ edge
139
+ end
140
+
141
+ # Remove an edge from the graph
142
+ def remove_edge(edge)
143
+ delete_edge(edge)
144
+ edge.each_vertex { |vertex| callback(vertex, :edge_removed, edge) }
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tangle
4
+ #
5
+ # Private methods of BaseGraph
6
+ #
7
+ module BaseGraphPrivate
8
+ private
9
+
10
+ def callback(receiver, method, *args)
11
+ receiver.send(method, *args) if receiver.respond_to?(method)
12
+ end
13
+
14
+ # Initialize vertex related attributes
15
+ def initialize_vertices
16
+ @vertices = {}
17
+ @vertices_by_name = {}
18
+ end
19
+
20
+ def initialize_edges
21
+ @edges = Set[]
22
+ end
23
+
24
+ # Yield each reachable vertex to a block, breadth first
25
+ def each_vertex_breadth_first(start_vertex, walk_method)
26
+ remaining = [start_vertex]
27
+ remaining.each_with_object([]) do |vertex, history|
28
+ history << vertex
29
+ yield vertex
30
+ send(walk_method, vertex).each do |other|
31
+ remaining << other unless history.include?(other)
32
+ end
33
+ end
34
+ end
35
+
36
+ def vertex_enumerator(start_vertex, walk_method)
37
+ Enumerator.new do |yielder|
38
+ each_vertex_breadth_first(start_vertex, walk_method) do |vertex|
39
+ yielder << vertex
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tangle
4
+ #
5
+ # Protected methods of BaseGraph
6
+ #
7
+ module BaseGraphProtected
8
+ protected
9
+
10
+ def select_vertices!(selected = nil)
11
+ vertices.each do |vertex|
12
+ delete_vertex(vertex) if block_given? && !yield(vertex)
13
+ next if selected.nil?
14
+ delete_vertex(vertex) unless selected.any? { |vtx| vtx.eql?(vertex) }
15
+ end
16
+ end
17
+
18
+ def insert_vertex(vertex, name = nil)
19
+ @vertices[vertex] = Set[]
20
+ @vertices_by_name[name] = vertex unless name.nil?
21
+ end
22
+
23
+ def delete_vertex(vertex)
24
+ @vertices[vertex].each do |edge|
25
+ delete_edge(edge) if edge.include?(vertex)
26
+ end
27
+ @vertices.delete(vertex)
28
+ @vertices_by_name.delete_if { |_, vtx| vtx.eql?(vertex) }
29
+ end
30
+
31
+ # Insert a prepared edge into the graph
32
+ #
33
+ def insert_edge(edge)
34
+ @edges << edge
35
+ edge.each_vertex { |vertex| @vertices.fetch(vertex) << edge }
36
+ end
37
+
38
+ def delete_edge(edge)
39
+ edge.each_vertex { |vertex| @vertices.fetch(vertex).delete(edge) }
40
+ @edges.delete(edge)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tangle
4
+ # Currification of instance methods, for adding callbacks to other objects
5
+ module Currify
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ # Class method extensions for currification of instance methods
11
+ module ClassMethods
12
+ # Return a list of currified methods for a given tag.
13
+ #
14
+ # :call-seq:
15
+ # self.class.currified_methods(tag) => Array of Symbol
16
+ def currified_methods(tag)
17
+ mine = @currified_methods&.[](tag) || []
18
+ return mine unless superclass.respond_to?(:currified_methods)
19
+ superclass.currified_methods(tag) + mine
20
+ end
21
+
22
+ private
23
+
24
+ # Add a symbol to the list of currified methods for a tag.
25
+ #
26
+ # :call-seq:
27
+ # class X
28
+ # currify :tag, :method
29
+ def currify(tag, method)
30
+ @currified_methods ||= {}
31
+ @currified_methods[tag] ||= []
32
+ @currified_methods[tag] << method
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def define_currified_methods(obj, tag)
39
+ self.class.currified_methods(tag)&.each do |name|
40
+ obj.instance_exec(name, method(name).curry) do |method_name, method|
41
+ define_singleton_method(method_name) do |*args|
42
+ method.call(self, *args)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tangle/directed/graph'
2
4
  require 'tangle/directed/acyclic/partial_order'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Tangle
2
4
  module Directed
3
5
  module Acyclic
@@ -1,4 +1,6 @@
1
- require 'tangle/edge'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../edge'
2
4
 
3
5
  module Tangle
4
6
  module Directed
@@ -31,7 +33,7 @@ module Tangle
31
33
  super
32
34
  @tail = tail
33
35
  @head = head
34
- @vertices = { tail => head }
36
+ @vertices = { tail => head }.freeze
35
37
  end
36
38
  end
37
39
  end
@@ -1,103 +1,125 @@
1
- require 'tangle/graph'
2
- require 'tangle/directed/edge'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_graph'
4
+ require_relative 'edge'
3
5
 
4
6
  module Tangle
5
7
  module Directed
6
8
  #
7
9
  # A directed graph
8
- class Graph < Tangle::Graph
9
- Edge = Tangle::Directed::Edge
10
- DEFAULT_MIXINS = [].freeze
11
-
10
+ class Graph < Tangle::BaseGraph
12
11
  # Return the incoming edges for +vertex+
13
12
  def in_edges(vertex)
14
13
  edges(vertex).select { |edge| edge.head?(vertex) }
15
14
  end
15
+ currify :vertex, :in_edges
16
16
 
17
17
  # Return the direct predecessors of +vertex+
18
18
  def direct_predecessors(vertex)
19
19
  in_edges(vertex).map(&:tail).to_set
20
20
  end
21
+ currify :vertex, :direct_predecessors
21
22
 
22
23
  # Is +other+ a direct predecessor of +vertex+?
23
24
  def direct_predecessor?(vertex, other)
24
25
  direct_predecessors(vertex).include?(other)
25
26
  end
27
+ currify :vertex, :direct_predecessor?
26
28
 
27
29
  # Return a breadth first enumerator for all predecessors
28
30
  def predecessors(vertex)
29
31
  vertex_enumerator(vertex, :direct_predecessors)
30
32
  end
33
+ currify :vertex, :predecessors
31
34
 
32
35
  # Is +other+ a predecessor of +vertex+?
33
36
  def predecessor?(vertex, other)
34
37
  predecessors(vertex).any? { |vtx| other.eql?(vtx) }
35
38
  end
39
+ currify :vertex, :predecessor?
36
40
 
37
41
  # Return a subgraph with all predecessors of a +vertex+
38
42
  def predecessor_subgraph(vertex, &selector)
39
43
  subgraph(predecessors(vertex), &selector)
40
44
  end
45
+ currify :vertex, :predecessor_subgraph
41
46
 
42
47
  # Return the outgoing edges for +vertex+
43
48
  def out_edges(vertex)
44
49
  edges(vertex).select { |edge| edge.tail?(vertex) }
45
50
  end
51
+ currify :vertex, :out_edges
46
52
 
47
53
  # Return the direct successors of +vertex+
48
54
  def direct_successors(vertex)
49
55
  out_edges(vertex).map(&:head).to_set
50
56
  end
57
+ currify :vertex, :direct_successors
51
58
 
52
59
  # Is +other+ a direct successor of +vertex+?
53
60
  def direct_successor?(vertex, other)
54
61
  direct_successors(vertex).include?(other)
55
62
  end
63
+ currify :vertex, :direct_successor?
56
64
 
57
65
  # Return a breadth first enumerator for all successors
58
66
  def successors(vertex)
59
67
  vertex_enumerator(vertex, :direct_successors)
60
68
  end
69
+ currify :vertex, :successors
61
70
 
62
71
  # Is +other+ a successor of +vertex+?
63
72
  def successor?(vertex, other)
64
73
  successors(vertex).any? { |vtx| other.eql?(vtx) }
65
74
  end
75
+ currify :vertex, :successor?
66
76
 
67
77
  # Return a subgraph with all successors of a +vertex+
68
78
  def successor_subgraph(vertex, &selector)
69
79
  subgraph(successors(vertex), &selector)
70
80
  end
81
+ currify :vertex, :successor_subgraph
71
82
 
72
83
  # Return the in degree for +vertex+
73
84
  def in_degree(vertex)
74
85
  in_edges(vertex).count
75
86
  end
87
+ currify :vertex, :in_degree
76
88
 
77
89
  # Return the out degree for +vertex+
78
90
  def out_degree(vertex)
79
91
  out_edges(vertex).count
80
92
  end
93
+ currify :vertex, :out_degree
81
94
 
82
95
  # Is +vertex+ a sink in the graph?
83
96
  def sink?(vertex)
84
97
  out_degree(vertex).zero?
85
98
  end
99
+ currify :vertex, :sink?
86
100
 
87
101
  # Is +vertex+ a source in the graph?
88
102
  def source?(vertex)
89
103
  in_degree(vertex).zero?
90
104
  end
105
+ currify :vertex, :source?
91
106
 
92
107
  # Is +vertex+ internal in the graph?
93
108
  def internal?(vertex)
94
109
  !(sink?(vertex) || source?(vertex))
95
110
  end
111
+ currify :vertex, :internal?
96
112
 
97
113
  # Is the graph balanced?
98
114
  def balanced?
99
115
  vertices.all? { |vertex| in_degree(vertex) == out_degree(vertex) }
100
116
  end
117
+
118
+ private
119
+
120
+ def new_edge(*args)
121
+ Edge.new(*args)
122
+ end
101
123
  end
102
124
  end
103
125
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
- require 'tangle/errors'
4
+ require_relative 'mixin'
3
5
 
4
6
  module Tangle
5
7
  #
6
- # An edge in a graph, connecting two vertices
8
+ # An edge in an undirected graph, connecting two vertices
7
9
  #
8
10
  class Edge
9
11
  include Tangle::Mixin::Initialize
@@ -31,16 +33,6 @@ module Tangle
31
33
  @vertices.fetch(from_vertex)
32
34
  end
33
35
 
34
- def to_s
35
- vertex1, vertex2 = @vertices.keys
36
- "{#{vertex1}<->#{vertex2}}"
37
- end
38
- alias inspect to_s
39
-
40
- def each_vertex(&block)
41
- @vertices.each_key(&block)
42
- end
43
-
44
36
  def include?(vertex)
45
37
  each_vertex.include?(vertex)
46
38
  end
@@ -51,9 +43,8 @@ module Tangle
51
43
 
52
44
  private
53
45
 
54
- def initialize_vertices(vertex1, vertex2 = vertex1)
46
+ def initialize_vertices(vertex1, vertex2)
55
47
  @loop = vertex1 == vertex2
56
- @vertices = { vertex1 => vertex2, vertex2 => vertex1 }.freeze
57
48
  end
58
49
  end
59
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Tangle
2
4
  #
3
5
  # LoopError is raised when a looped edge is disallowed.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Tangle
2
4
  module Mixin
3
5
  #
@@ -10,7 +12,6 @@ module Tangle
10
12
 
11
13
  def initialize_mixins(mixins: nil, **kwargs)
12
14
  @mixins = mixins
13
-
14
15
  extend_with_mixins unless @mixins.nil?
15
16
  initialize_kwargs(**kwargs) unless kwargs.empty?
16
17
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Tangle
2
4
  module Mixin
3
5
  # Tangle mixin for loading a directory structure as a graph
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require_relative '../edge'
5
+
6
+ module Tangle
7
+ module Undirected
8
+ #
9
+ # An edge in an undirected graph, connecting two vertices
10
+ #
11
+ class Edge < Tangle::Edge
12
+ def each_vertex(&block)
13
+ @vertices.each_key(&block)
14
+ end
15
+
16
+ def to_s
17
+ vertex1, vertex2 = @vertices.keys
18
+ "{#{vertex1}<->#{vertex2}}"
19
+ end
20
+ alias inspect to_s
21
+
22
+ private
23
+
24
+ def initialize_vertices(vertex1, vertex2 = vertex1)
25
+ super
26
+ @vertices = { vertex1 => vertex2, vertex2 => vertex1 }.freeze
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_graph'
4
+ require_relative 'edge'
5
+
6
+ module Tangle
7
+ module Undirected
8
+ #
9
+ # Undirected graph
10
+ #
11
+ class Graph < Tangle::BaseGraph
12
+ # Two vertices are adjacent if there is an edge between them
13
+ def adjacent?(vertex, other)
14
+ edges(vertex).any? { |edge| edge[vertex] == other }
15
+ end
16
+
17
+ # Return the set of adjacent vertices
18
+ def adjacent(vertex)
19
+ Set.new(edges(vertex).map { |edge| edge.walk(vertex) })
20
+ end
21
+
22
+ # Get the largest connected subgraph for a vertex.
23
+ # Also aliased as :component and :connected_component
24
+ #
25
+ # connected_subgraph(vertex) => Graph
26
+ #
27
+ def connected_subgraph(vertex)
28
+ subgraph { |other| connected_vertices?(vertex, other) }
29
+ end
30
+ alias component connected_subgraph
31
+ alias connected_component connected_subgraph
32
+
33
+ # Get the largest subgraph that is not connected to a vertex, or what's
34
+ # left after removing the connected subgraph.
35
+ #
36
+ def disconnected_subgraph(vertex)
37
+ subgraph { |other| !connected_vertices?(vertex, other) }
38
+ end
39
+
40
+ # A graph is connected if all vertices are connected to all vertices
41
+ # An empty graph is disconnected.
42
+ #
43
+ def connected?(*tested_vertices)
44
+ tested_vertices = vertices if tested_vertices.empty?
45
+ return false if tested_vertices.empty?
46
+
47
+ tested_vertices.combination(2).all? do |pair|
48
+ this, that = pair.to_a
49
+ reachable(this).any? { |other| other == that }
50
+ end
51
+ end
52
+
53
+ # A graph is disconnected if any vertex is not connected to all other.
54
+ # An empty graph is disconnected.
55
+ #
56
+ def disconnected?(*tested_vertices)
57
+ !connected?(*tested_vertices)
58
+ end
59
+
60
+ # Return a breadth-first Enumerator for all reachable vertices,
61
+ # by transitive adjacency.
62
+ def reachable(start_vertex)
63
+ vertex_enumerator(start_vertex, :adjacent)
64
+ end
65
+
66
+ private
67
+
68
+ def new_edge(*args)
69
+ Edge.new(*args)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../graph'
4
+
5
+ module Tangle
6
+ module Undirected
7
+ module Simple
8
+ #
9
+ # A simple graph, without loops and multiple edges
10
+ class Graph < Tangle::Undirected::Graph
11
+ protected
12
+
13
+ def insert_edge(edge)
14
+ raise LoopError if edge.loop?
15
+ raise MultiEdgeError if adjacent?(*edge.each_vertex)
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'git-version-bump'
2
4
 
3
5
  module Tangle
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
@@ -21,6 +22,9 @@ Gem::Specification.new do |spec|
21
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
23
  spec.require_paths = ['lib']
23
24
 
25
+ spec.platform = Gem::Platform::RUBY
26
+ spec.required_ruby_version = '~> 2.3'
27
+
24
28
  spec.add_dependency 'git-version-bump', '~> 0.15'
25
29
 
26
30
  spec.add_development_dependency 'bundler', '~> 1.16'
@@ -28,4 +32,5 @@ Gem::Specification.new do |spec|
28
32
  spec.add_development_dependency 'rake', '~> 10.0'
29
33
  spec.add_development_dependency 'rspec', '~> 3.0'
30
34
  spec.add_development_dependency 'rubocop', '~> 0.52'
35
+ spec.add_development_dependency 'simplecov', '~> 0.16'
31
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tangle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Calle Englund
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-25 00:00:00.000000000 Z
11
+ date: 2018-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: git-version-bump
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0.52'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.16'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.16'
97
111
  description:
98
112
  email:
99
113
  - calle@discord.bofh.se
@@ -113,19 +127,21 @@ files:
113
127
  - bin/console
114
128
  - bin/setup
115
129
  - lib/tangle.rb
130
+ - lib/tangle/base_graph.rb
131
+ - lib/tangle/base_graph_private.rb
132
+ - lib/tangle/base_graph_protected.rb
133
+ - lib/tangle/currify.rb
116
134
  - lib/tangle/directed/acyclic/graph.rb
117
135
  - lib/tangle/directed/acyclic/partial_order.rb
118
136
  - lib/tangle/directed/edge.rb
119
137
  - lib/tangle/directed/graph.rb
120
138
  - lib/tangle/edge.rb
121
139
  - lib/tangle/errors.rb
122
- - lib/tangle/graph.rb
123
- - lib/tangle/graph_edges.rb
124
- - lib/tangle/graph_vertices.rb
125
140
  - lib/tangle/mixin.rb
126
- - lib/tangle/mixin/connectedness.rb
127
141
  - lib/tangle/mixin/directory.rb
128
- - lib/tangle/simple/graph.rb
142
+ - lib/tangle/undirected/edge.rb
143
+ - lib/tangle/undirected/graph.rb
144
+ - lib/tangle/undirected/simple/graph.rb
129
145
  - lib/tangle/version.rb
130
146
  - tangle.gemspec
131
147
  homepage: https://github.com/notcalle/ruby-tangle
@@ -138,9 +154,9 @@ require_paths:
138
154
  - lib
139
155
  required_ruby_version: !ruby/object:Gem::Requirement
140
156
  requirements:
141
- - - ">="
157
+ - - "~>"
142
158
  - !ruby/object:Gem::Version
143
- version: '0'
159
+ version: '2.3'
144
160
  required_rubygems_version: !ruby/object:Gem::Requirement
145
161
  requirements:
146
162
  - - ">="
@@ -1,83 +0,0 @@
1
- require 'tangle/mixin'
2
- require 'tangle/mixin/connectedness'
3
- require 'tangle/edge'
4
- require 'tangle/graph_vertices'
5
- require 'tangle/graph_edges'
6
-
7
- module Tangle
8
- #
9
- # Base class for all kinds of graphs
10
- #
11
- class Graph
12
- include Tangle::GraphVertices
13
- include Tangle::GraphEdges
14
- include Tangle::Mixin::Initialize
15
- Edge = Tangle::Edge
16
- DEFAULT_MIXINS = Tangle::Mixin::Connectedness::MIXINS
17
-
18
- # Initialize a new graph, preloading it with vertices and edges
19
- #
20
- # Graph[+vertices+] => Graph
21
- # Graph[+vertices+, +edges+) => Graph
22
- #
23
- # When +vertices+ is a hash, it contains initialization kwargs as
24
- # values and vertex names as keys. When +vertices+ is an array of
25
- # initialization kwargs, the vertices will be be anonymous.
26
- #
27
- # +edges+ can contain an array of exactly two, either names of vertices
28
- # or vertices.
29
- #
30
- # Any kwarg supported by Graph.new is also allowed.
31
- #
32
- def self.[](vertices, edges = {}, **kwargs)
33
- graph = new(**kwargs)
34
- vertices.each { |vertex| graph.add_vertex(vertex) }
35
- edges.each { |from, to| graph.add_edge(from, to) }
36
- graph
37
- end
38
-
39
- # Initialize a new graph, optionally preloading it with vertices and edges
40
- #
41
- # Graph.new() => Graph
42
- # Graph.new(mixins: [MixinModule, ...], ...) => Graph
43
- #
44
- # +mixins+ is an array of modules that can be mixed into the various
45
- # classes that makes up a graph. Initialization of a Graph, Vertex or Edge
46
- # looks for submodules in each mixin, with the same name and extends
47
- # any created object. Defaults to [Tangle::Mixin::Connectedness].
48
- #
49
- # Any subclass of Graph should also subclass Edge to manage its unique
50
- # constraints.
51
- #
52
- def initialize(mixins: self.class::DEFAULT_MIXINS, **kwargs)
53
- initialize_vertices
54
- initialize_edges
55
- initialize_mixins(mixins: mixins, **kwargs)
56
- end
57
-
58
- # Return a subgraph, optionally filtered by a vertex selector block
59
- #
60
- # subgraph => Graph
61
- # subgraph { |vertex| ... } => Graph
62
- #
63
- # Unless a selector is provided, the subgraph contains the entire graph.
64
- #
65
- def subgraph(included = nil, &selector)
66
- result = clone
67
- result.select_vertices!(included) unless included.nil?
68
- result.select_vertices!(&selector) if block_given?
69
- result
70
- end
71
-
72
- def to_s
73
- "#<#{self.class}: #{vertices.count} vertices, #{edges.count} edges>"
74
- end
75
- alias inspect to_s
76
-
77
- private
78
-
79
- def callback(receiver, method, *args)
80
- receiver.send(method, *args) if receiver.respond_to?(method)
81
- end
82
- end
83
- end
@@ -1,52 +0,0 @@
1
- require 'set'
2
-
3
- module Tangle
4
- # Edge related methods in a graph
5
- module GraphEdges
6
- # Get all edges.
7
- #
8
- # edges => Array
9
- #
10
- def edges(vertex = nil)
11
- return @edges if vertex.nil?
12
- @vertices.fetch(vertex)
13
- end
14
-
15
- # Add a new edge to the graph
16
- #
17
- # add_edge(vtx1, vtx2, ...) => Edge
18
- #
19
- def add_edge(*vertices, **kvargs)
20
- edge = self.class::Edge.new(*vertices, mixins: @mixins, **kvargs)
21
- insert_edge(edge)
22
- vertices.each { |vertex| callback(vertex, :edge_added, edge) }
23
- edge
24
- end
25
-
26
- # Remove an edge from the graph
27
- def remove_edge(edge)
28
- delete_edge(edge)
29
- edge.each_vertex { |vertex| callback(vertex, :edge_removed, edge) }
30
- end
31
-
32
- protected
33
-
34
- # Insert a prepared edge into the graph
35
- #
36
- def insert_edge(edge)
37
- @edges << edge
38
- edge.each_vertex { |vertex| @vertices.fetch(vertex) << edge }
39
- end
40
-
41
- def delete_edge(edge)
42
- edge.each_vertex { |vertex| @vertices.fetch(vertex).delete(edge) }
43
- @edges.delete(edge)
44
- end
45
-
46
- private
47
-
48
- def initialize_edges
49
- @edges = Set[]
50
- end
51
- end
52
- end
@@ -1,98 +0,0 @@
1
- require 'set'
2
-
3
- module Tangle
4
- # Vertex related methods in a graph
5
- module GraphVertices
6
- # Fetch a vertex by its name
7
- def fetch(name)
8
- @vertices_by_name.fetch(name)
9
- end
10
-
11
- # Return a named vertex
12
- def [](name)
13
- @vertices_by_name[name]
14
- end
15
-
16
- # Return all vertices in the graph
17
- def vertices
18
- @vertices.keys
19
- end
20
-
21
- # Select vertices in the graph
22
- def select(&selector)
23
- @vertices.each_key.select(&selector)
24
- end
25
-
26
- # Add a vertex into the graph
27
- #
28
- # If a name: is given, or the vertex responds to :name,
29
- # it will be registered by name in the graph
30
- def add_vertex(vertex, name: nil)
31
- name ||= callback(vertex, :name)
32
- insert_vertex(vertex, name)
33
- callback(vertex, :added_to_graph, self)
34
- self
35
- end
36
- alias << add_vertex
37
-
38
- # Remove a vertex from the graph
39
- def remove_vertex(vertex)
40
- @vertices[vertex].each do |edge|
41
- remove_edge(edge) if edge.include?(vertex)
42
- end
43
- delete_vertex(vertex)
44
- callback(vertex, :removed_from_graph, self)
45
- end
46
-
47
- protected
48
-
49
- def select_vertices!(selected = nil)
50
- vertices.each do |vertex|
51
- delete_vertex(vertex) if block_given? && !yield(vertex)
52
- next if selected.nil?
53
- delete_vertex(vertex) unless selected.any? { |vtx| vtx.eql?(vertex) }
54
- end
55
- end
56
-
57
- def insert_vertex(vertex, name = nil)
58
- @vertices[vertex] = Set[]
59
- @vertices_by_name[name] = vertex unless name.nil?
60
- end
61
-
62
- def delete_vertex(vertex)
63
- @vertices[vertex].each do |edge|
64
- delete_edge(edge) if edge.include?(vertex)
65
- end
66
- @vertices.delete(vertex)
67
- @vertices_by_name.delete_if { |_, vtx| vtx.eql?(vertex) }
68
- end
69
-
70
- private
71
-
72
- # Initialize vertex related attributes
73
- def initialize_vertices
74
- @vertices = {}
75
- @vertices_by_name = {}
76
- end
77
-
78
- # Yield each reachable vertex to a block, breadth first
79
- def each_vertex_breadth_first(start_vertex, walk_method)
80
- remaining = [start_vertex]
81
- remaining.each_with_object([]) do |vertex, history|
82
- history << vertex
83
- yield vertex
84
- send(walk_method, vertex).each do |other|
85
- remaining << other unless history.include?(other)
86
- end
87
- end
88
- end
89
-
90
- def vertex_enumerator(start_vertex, walk_method)
91
- Enumerator.new do |yielder|
92
- each_vertex_breadth_first(start_vertex, walk_method) do |vertex|
93
- yielder << vertex
94
- end
95
- end
96
- end
97
- end
98
- end
@@ -1,68 +0,0 @@
1
- module Tangle
2
- module Mixin
3
- #
4
- # Mixins for adding connectedness features
5
- #
6
- module Connectedness
7
- MIXINS = [Tangle::Mixin::Connectedness].freeze
8
- #
9
- # Mixin for adding connectedness to a graph
10
- #
11
- module Graph
12
- # Two vertices are adjacent if there is an edge between them
13
- def adjacent?(vertex, other)
14
- edges(vertex).any? { |edge| edge[vertex] == other }
15
- end
16
-
17
- # Return the set of adjacent vertices
18
- def adjacent(vertex)
19
- Set.new(edges(vertex).map { |edge| edge.walk(vertex) })
20
- end
21
-
22
- # Get the largest connected subgraph for a vertex.
23
- # Also aliased as :component and :connected_component
24
- #
25
- # connected_subgraph(vertex) => Graph
26
- #
27
- def connected_subgraph(vertex)
28
- subgraph { |other| connected_vertices?(vertex, other) }
29
- end
30
- alias component connected_subgraph
31
- alias connected_component connected_subgraph
32
-
33
- # Get the largest subgraph that is not connected to a vertex, or what's
34
- # left after removing the connected subgraph.
35
- #
36
- def disconnected_subgraph(vertex)
37
- subgraph { |other| !connected_vertices?(vertex, other) }
38
- end
39
-
40
- # A graph is connected if all vertices are connected to all vertices
41
- # An empty graph is disconnected.
42
- #
43
- def connected?(*tested_vertices)
44
- tested_vertices = vertices if tested_vertices.empty?
45
- return false if tested_vertices.empty?
46
-
47
- tested_vertices.combination(2).all? do |pair|
48
- this, that = pair.to_a
49
- reachable(this).any? { |other| other == that }
50
- end
51
- end
52
-
53
- # A graph is disconnected if any vertex is not connected to all other.
54
- # An empty graph is disconnected.
55
- #
56
- def disconnected?(*tested_vertices)
57
- !connected?(*tested_vertices)
58
- end
59
-
60
- # Return a breadth-first Enumerator for all reachable vertices,
61
- # by transitive adjacency.
62
- def reachable(start_vertex)
63
- vertex_enumerator(start_vertex, :adjacent)
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,17 +0,0 @@
1
- require 'tangle/graph'
2
-
3
- module Tangle
4
- module Simple
5
- #
6
- # A simple graph, without loops and multiple edges
7
- class Graph < Tangle::Graph
8
- protected
9
-
10
- def insert_edge(edge)
11
- raise LoopError if edge.loop?
12
- raise MultiEdgeError if adjacent?(*edge.each_vertex)
13
- super
14
- end
15
- end
16
- end
17
- end