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 +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
|