tangle 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +2 -0
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/lib/tangle.rb +7 -4
- data/lib/tangle/base_graph.rb +147 -0
- data/lib/tangle/base_graph_private.rb +44 -0
- data/lib/tangle/base_graph_protected.rb +43 -0
- data/lib/tangle/currify.rb +48 -0
- data/lib/tangle/directed/acyclic/graph.rb +2 -0
- data/lib/tangle/directed/acyclic/partial_order.rb +2 -0
- data/lib/tangle/directed/edge.rb +4 -2
- data/lib/tangle/directed/graph.rb +28 -6
- data/lib/tangle/edge.rb +5 -14
- data/lib/tangle/errors.rb +2 -0
- data/lib/tangle/mixin.rb +2 -1
- data/lib/tangle/mixin/directory.rb +2 -0
- data/lib/tangle/undirected/edge.rb +30 -0
- data/lib/tangle/undirected/graph.rb +73 -0
- data/lib/tangle/undirected/simple/graph.rb +21 -0
- data/lib/tangle/version.rb +2 -0
- data/tangle.gemspec +5 -0
- metadata +25 -9
- data/lib/tangle/graph.rb +0 -83
- data/lib/tangle/graph_edges.rb +0 -52
- data/lib/tangle/graph_vertices.rb +0 -98
- data/lib/tangle/mixin/connectedness.rb +0 -68
- data/lib/tangle/simple/graph.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc791cdb0b95ae60507e9052e96b8c0c44550a1cde4ac88a5ab9be6661102044
|
4
|
+
data.tar.gz: bf20ee14175b42f5ae9e4675ca50d2c76937e1cc09493e7420a78eaf8885aa79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3fba37c004a711c3ed33f6966853724023b5c1dbbe61d596342fa91259a46b490153147f16b12e01f8f9b5cc66156f34197fd91ff357c50e765afb8c8571f564
|
7
|
+
data.tar.gz: 0c18db46c410650ba81c4e90c891b1bedb19d0eb3e92079d68c709055883ac12b231162cd8235cbdb81fa4b7ab1bc7f4ad3d2281c0944a5c35387b2a8dc609c4
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/lib/tangle.rb
CHANGED
@@ -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
|
-
|
24
|
-
|
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
|
data/lib/tangle/directed/edge.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
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
|
-
|
2
|
-
|
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::
|
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
|
data/lib/tangle/edge.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
2
|
-
|
4
|
+
require_relative 'mixin'
|
3
5
|
|
4
6
|
module Tangle
|
5
7
|
#
|
6
|
-
# An edge in
|
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
|
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
|
data/lib/tangle/errors.rb
CHANGED
data/lib/tangle/mixin.rb
CHANGED
@@ -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
|
@@ -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
|
data/lib/tangle/version.rb
CHANGED
data/tangle.gemspec
CHANGED
@@ -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.
|
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-
|
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/
|
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: '
|
159
|
+
version: '2.3'
|
144
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
161
|
requirements:
|
146
162
|
- - ">="
|
data/lib/tangle/graph.rb
DELETED
@@ -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
|
data/lib/tangle/graph_edges.rb
DELETED
@@ -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
|
data/lib/tangle/simple/graph.rb
DELETED
@@ -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
|