tangle 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -2
- data/lib/tangle/directed/acyclic/graph.rb +14 -3
- data/lib/tangle/directed/acyclic/partial_order.rb +37 -0
- data/lib/tangle/directed/edge.rb +16 -12
- data/lib/tangle/directed/graph.rb +91 -1
- data/lib/tangle/edge.rb +21 -63
- data/lib/tangle/graph.rb +17 -76
- data/lib/tangle/graph_edges.rb +49 -0
- data/lib/tangle/graph_vertices.rb +53 -0
- data/lib/tangle/mixin/connectedness.rb +24 -31
- data/lib/tangle/mixin.rb +8 -13
- data/lib/tangle/simple/graph.rb +7 -2
- metadata +5 -8
- data/lib/tangle/directed/acyclic/edge.rb +0 -20
- data/lib/tangle/graph_private.rb +0 -31
- data/lib/tangle/graph_protected.rb +0 -49
- data/lib/tangle/mixin/ancestry.rb +0 -78
- data/lib/tangle/simple/edge.rb +0 -18
- data/lib/tangle/vertex.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49703932d4a9666672412e9861b5a6a5e771375b1d7147ccff6a76da283d76ea
|
4
|
+
data.tar.gz: bd1681a13bead0ee180e01447bfdab7d9041f033e968840c6124d2e794688f86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c649bf8ef9250797945c2fc378a639118689d41c531a45c2b5be9e997d8a670c8e30bffd958c573f31a03b81885c85a3f5d1e24a202fe4dd0be08d3cf8fffc8c
|
7
|
+
data.tar.gz: b0d0fde21a7e2f38bd99e787695c2f07098c02163485f02ec0bc17bd7a0a295f49eef72258324fa9c3ff1b514ead63c75451d0cd8ab462fa6b60efeb274d9faa
|
data/README.md
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
require 'tangle/directed/graph'
|
2
|
-
require 'tangle/directed/acyclic/
|
2
|
+
require 'tangle/directed/acyclic/partial_order'
|
3
3
|
|
4
4
|
module Tangle
|
5
5
|
module Directed
|
6
6
|
module Acyclic
|
7
|
-
#
|
8
7
|
# A directed acyclic graph
|
9
8
|
class Graph < Tangle::Directed::Graph
|
10
|
-
|
9
|
+
# Return a topological ordering of a set of vertices, or all
|
10
|
+
# vertices in the graph.
|
11
|
+
def topological_ordering(*vertices)
|
12
|
+
PartialOrder[self, *vertices].sort!.map(&:vertex)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def insert_edge(edge)
|
18
|
+
raise CyclicError if successor?(edge.head, edge.tail) ||
|
19
|
+
predecessor?(edge.tail, edge.head)
|
20
|
+
super
|
21
|
+
end
|
11
22
|
end
|
12
23
|
end
|
13
24
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Tangle
|
2
|
+
module Directed
|
3
|
+
module Acyclic
|
4
|
+
# Implement a Partial Order for vertices in a DAG.
|
5
|
+
class PartialOrder
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Wrap a set of vertices, or all vertices, in a graph
|
9
|
+
# in a parial ordering, such that the elements in the
|
10
|
+
# returned set are comparable by u <= v iff v is an
|
11
|
+
# ancestor of u.
|
12
|
+
def self.[](graph, *vertices)
|
13
|
+
vertices = graph.vertices if vertices.empty?
|
14
|
+
vertices.map { |vertex| new(graph, vertex) }
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :vertex
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
attr_reader :graph
|
22
|
+
|
23
|
+
def initialize(graph, vertex)
|
24
|
+
@graph = graph
|
25
|
+
@vertex = vertex
|
26
|
+
end
|
27
|
+
|
28
|
+
def <=>(other)
|
29
|
+
raise RuntimeError unless graph == other.graph
|
30
|
+
return 0 if vertex == other.vertex
|
31
|
+
return -1 if graph.successor?(vertex, other.vertex)
|
32
|
+
1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/tangle/directed/edge.rb
CHANGED
@@ -6,28 +6,32 @@ module Tangle
|
|
6
6
|
# An edge in a directed graph
|
7
7
|
#
|
8
8
|
class Edge < Tangle::Edge
|
9
|
-
|
10
|
-
|
9
|
+
attr_reader :head, :tail
|
10
|
+
|
11
|
+
def head?(vertex)
|
12
|
+
@head == vertex
|
11
13
|
end
|
12
14
|
|
13
|
-
def
|
14
|
-
@
|
15
|
+
def tail?(vertex)
|
16
|
+
@tail == vertex
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
@
|
19
|
+
def each_vertex(&block)
|
20
|
+
[@tail, @head].each(&block)
|
19
21
|
end
|
20
22
|
|
21
|
-
def
|
22
|
-
@
|
23
|
+
def to_s
|
24
|
+
"{#{@tail}-->#{@head}}"
|
23
25
|
end
|
26
|
+
alias inspect to_s
|
24
27
|
|
25
|
-
|
28
|
+
private
|
26
29
|
|
27
|
-
def
|
28
|
-
@child = child
|
29
|
-
@parent = parent
|
30
|
+
def initialize_vertices(tail, head = tail)
|
30
31
|
super
|
32
|
+
@tail = tail
|
33
|
+
@head = head
|
34
|
+
@vertices = { tail => head }
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
@@ -7,7 +7,97 @@ module Tangle
|
|
7
7
|
# A directed graph
|
8
8
|
class Graph < Tangle::Graph
|
9
9
|
Edge = Tangle::Directed::Edge
|
10
|
-
DEFAULT_MIXINS = [
|
10
|
+
DEFAULT_MIXINS = [].freeze
|
11
|
+
|
12
|
+
# Return the incoming edges for +vertex+
|
13
|
+
def in_edges(vertex)
|
14
|
+
edges(vertex).select { |edge| edge.head?(vertex) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return the direct predecessors of +vertex+
|
18
|
+
def direct_predecessors(vertex)
|
19
|
+
Set.new(in_edges(vertex).map(&:tail))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Is +other+ a direct predecessor of +vertex+?
|
23
|
+
def direct_predecessor?(vertex, other)
|
24
|
+
direct_predecessors(vertex).include?(other)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return a breadth first enumerator for all predecessors
|
28
|
+
def predecessors(vertex)
|
29
|
+
vertex_enumerator(vertex, :direct_predecessors)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Is +other+ a predecessor of +vertex+?
|
33
|
+
def predecessor?(vertex, other)
|
34
|
+
predecessors(vertex).include?(other)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return a subgraph with all predecessors of a +vertex+
|
38
|
+
def predecessor_subgraph(vertex, &selector)
|
39
|
+
subgraph(predecessors(vertex), &selector)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the outgoing edges for +vertex+
|
43
|
+
def out_edges(vertex)
|
44
|
+
edges(vertex).select { |edge| edge.tail?(vertex) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the direct successors of +vertex+
|
48
|
+
def direct_successors(vertex)
|
49
|
+
Set.new(out_edges(vertex).map(&:head))
|
50
|
+
end
|
51
|
+
|
52
|
+
# Is +other+ a direct successor of +vertex+?
|
53
|
+
def direct_successor?(vertex, other)
|
54
|
+
direct_successors(vertex).include?(other)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return a breadth first enumerator for all successors
|
58
|
+
def successors(vertex)
|
59
|
+
vertex_enumerator(vertex, :direct_successors)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Is +other+ a successor of +vertex+?
|
63
|
+
def successor?(vertex, other)
|
64
|
+
successors(vertex).include?(other)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return a subgraph with all successors of a +vertex+
|
68
|
+
def successor_subgraph(vertex, &selector)
|
69
|
+
subgraph(successors(vertex), &selector)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return the in degree for +vertex+
|
73
|
+
def in_degree(vertex)
|
74
|
+
in_edges(vertex).count
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return the out degree for +vertex+
|
78
|
+
def out_degree(vertex)
|
79
|
+
out_edges(vertex).count
|
80
|
+
end
|
81
|
+
|
82
|
+
# Is +vertex+ a sink in the graph?
|
83
|
+
def sink?(vertex)
|
84
|
+
out_degree(vertex).zero?
|
85
|
+
end
|
86
|
+
|
87
|
+
# Is +vertex+ a source in the graph?
|
88
|
+
def source?(vertex)
|
89
|
+
in_degree(vertex).zero?
|
90
|
+
end
|
91
|
+
|
92
|
+
# Is +vertex+ internal in the graph?
|
93
|
+
def internal?(vertex)
|
94
|
+
!(sink?(vertex) || source?(vertex))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Is the graph balanced?
|
98
|
+
def balanced?
|
99
|
+
vertices.all? { |vertex| in_degree(vertex) == out_degree(vertex) }
|
100
|
+
end
|
11
101
|
end
|
12
102
|
end
|
13
103
|
end
|
data/lib/tangle/edge.rb
CHANGED
@@ -6,9 +6,10 @@ module Tangle
|
|
6
6
|
# An edge in a graph, connecting two vertices
|
7
7
|
#
|
8
8
|
class Edge
|
9
|
-
extend Forwardable
|
10
9
|
include Tangle::Mixin::Initialize
|
11
10
|
|
11
|
+
attr_reader :name
|
12
|
+
|
12
13
|
# Create a new edge between vertices
|
13
14
|
#
|
14
15
|
# Edge.new(vtx1) => Edge (loop)
|
@@ -16,86 +17,43 @@ module Tangle
|
|
16
17
|
#
|
17
18
|
# End users should probably use Graph#add_edge instead.
|
18
19
|
#
|
19
|
-
def initialize(vertex1, vertex2 = vertex1,
|
20
|
+
def initialize(vertex1, vertex2 = vertex1, name: nil, **kwargs)
|
20
21
|
@name = name
|
21
|
-
|
22
|
-
with_vertices(vertex1, vertex2)
|
23
|
-
|
22
|
+
initialize_vertices(vertex1, vertex2)
|
24
23
|
initialize_mixins(**kwargs)
|
25
|
-
|
26
|
-
validate_edge
|
27
24
|
end
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
# walk(vertex) => Vertex
|
32
|
-
#
|
33
|
-
def walk(from_vertex, selector: :other)
|
34
|
-
send(selector, from_vertex)
|
26
|
+
def [](from_vertex)
|
27
|
+
@vertices[from_vertex]
|
35
28
|
end
|
36
29
|
|
37
|
-
|
38
|
-
|
39
|
-
def other(for_vertex)
|
40
|
-
raise RuntimeError unless @vertices.include?(for_vertex)
|
41
|
-
|
42
|
-
@vertices.find { |other| other != for_vertex } || for_vertex
|
30
|
+
def walk(from_vertex)
|
31
|
+
@vertices.fetch(from_vertex)
|
43
32
|
end
|
44
33
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# End users should probably use Graph#subgraph instead.
|
49
|
-
#
|
50
|
-
# clone_into(graph) => Edge or nil
|
51
|
-
#
|
52
|
-
# Raises an ArgumentError if graph would remain the same.
|
53
|
-
#
|
54
|
-
def clone_into(graph)
|
55
|
-
raise ArgumentError if graph == @graph
|
56
|
-
|
57
|
-
vertices = @vertices.map do |vertex|
|
58
|
-
graph.get_vertex(vertex.vertex_id)
|
59
|
-
end
|
60
|
-
|
61
|
-
clone.with_graph(graph).with_vertices(*vertices)
|
62
|
-
rescue KeyError
|
63
|
-
nil
|
64
|
-
end
|
65
|
-
|
66
|
-
def inspect
|
67
|
-
"#<#{self.class}: #{@vertices}>"
|
34
|
+
def to_s
|
35
|
+
vertex1, vertex2 = @vertices.keys
|
36
|
+
"{#{vertex1}<->#{vertex2}}"
|
68
37
|
end
|
38
|
+
alias inspect to_s
|
69
39
|
|
70
|
-
def
|
71
|
-
@
|
40
|
+
def each_vertex(&block)
|
41
|
+
@vertices.each_key(&block)
|
72
42
|
end
|
73
|
-
alias == eql?
|
74
|
-
alias === eql?
|
75
|
-
alias equal? eql?
|
76
|
-
|
77
|
-
attr_reader :name
|
78
|
-
attr_reader :graph
|
79
|
-
attr_reader :vertices
|
80
|
-
|
81
|
-
def_delegators :@vertices, :include?, :hash
|
82
|
-
|
83
|
-
protected
|
84
43
|
|
85
|
-
def
|
86
|
-
|
87
|
-
self
|
44
|
+
def include?(vertex)
|
45
|
+
each_vertex.include?(vertex)
|
88
46
|
end
|
89
47
|
|
90
|
-
def
|
91
|
-
@
|
92
|
-
self
|
48
|
+
def loop?
|
49
|
+
@loop
|
93
50
|
end
|
94
51
|
|
95
52
|
private
|
96
53
|
|
97
|
-
def
|
98
|
-
|
54
|
+
def initialize_vertices(vertex1, vertex2 = vertex1)
|
55
|
+
@loop = vertex1 == vertex2
|
56
|
+
@vertices = { vertex1 => vertex2, vertex2 => vertex1 }.freeze
|
99
57
|
end
|
100
58
|
end
|
101
59
|
end
|
data/lib/tangle/graph.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
require 'tangle/mixin'
|
2
|
-
require 'tangle/
|
2
|
+
require 'tangle/mixin/connectedness'
|
3
3
|
require 'tangle/edge'
|
4
|
-
require 'tangle/
|
5
|
-
require 'tangle/
|
4
|
+
require 'tangle/graph_vertices'
|
5
|
+
require 'tangle/graph_edges'
|
6
6
|
|
7
7
|
module Tangle
|
8
8
|
#
|
9
9
|
# Base class for all kinds of graphs
|
10
10
|
#
|
11
11
|
class Graph
|
12
|
-
include Tangle::
|
13
|
-
include Tangle::
|
12
|
+
include Tangle::GraphVertices
|
13
|
+
include Tangle::GraphEdges
|
14
14
|
include Tangle::Mixin::Initialize
|
15
15
|
Edge = Tangle::Edge
|
16
|
-
DEFAULT_MIXINS =
|
16
|
+
DEFAULT_MIXINS = Tangle::Mixin::Connectedness::MIXINS
|
17
17
|
|
18
18
|
# Initialize a new graph, preloading it with vertices and edges
|
19
19
|
#
|
@@ -31,7 +31,7 @@ module Tangle
|
|
31
31
|
#
|
32
32
|
def self.[](vertices, edges = {}, **kwargs)
|
33
33
|
graph = new(**kwargs)
|
34
|
-
graph.
|
34
|
+
vertices.each { |vertex| graph.add_vertex(vertex) }
|
35
35
|
edges.each { |from, to| graph.add_edge(from, to) }
|
36
36
|
graph
|
37
37
|
end
|
@@ -52,71 +52,7 @@ module Tangle
|
|
52
52
|
def initialize(mixins: self.class::DEFAULT_MIXINS, **kwargs)
|
53
53
|
initialize_vertices
|
54
54
|
initialize_edges
|
55
|
-
initialize_mixins(mixins, **kwargs)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Get all edges.
|
59
|
-
#
|
60
|
-
# edges => Array
|
61
|
-
#
|
62
|
-
def edges(vertex: nil, &selector)
|
63
|
-
edges = vertex.nil? ? @edges : @edges_by_vertex[vertex]
|
64
|
-
if block_given?
|
65
|
-
edges.select(&selector)
|
66
|
-
else
|
67
|
-
edges.to_a
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Add a new edge to the graph
|
72
|
-
#
|
73
|
-
# add_edge(vtx1, vtx2, ...) => Edge
|
74
|
-
#
|
75
|
-
def add_edge(*vertices, **kvargs)
|
76
|
-
vertices = vertices.map { |v| get_vertex(v) }
|
77
|
-
insert_edge(self.class::Edge.new(*vertices, graph: self, **kvargs))
|
78
|
-
end
|
79
|
-
|
80
|
-
# Get all vertices.
|
81
|
-
#
|
82
|
-
# vertices => Array
|
83
|
-
#
|
84
|
-
def vertices
|
85
|
-
if block_given?
|
86
|
-
@vertices_by_id.select { |_, vertex| yield(vertex) }
|
87
|
-
else
|
88
|
-
@vertices_by_id
|
89
|
-
end.values
|
90
|
-
end
|
91
|
-
|
92
|
-
# Add a new vertex to the graph
|
93
|
-
#
|
94
|
-
# add_vertex(...) => Vertex
|
95
|
-
#
|
96
|
-
# Optional named arguments:
|
97
|
-
# name: unique name or label for vertex
|
98
|
-
#
|
99
|
-
def add_vertex(**kvargs)
|
100
|
-
insert_vertex(Vertex.new(graph: self, **kvargs))
|
101
|
-
end
|
102
|
-
|
103
|
-
def add_vertices(vertices)
|
104
|
-
case vertices
|
105
|
-
when Hash
|
106
|
-
vertices.each { |name, kwargs| add_vertex(name: name, **kwargs) }
|
107
|
-
else
|
108
|
-
vertices.each { |kwargs| add_vertex(**kwargs) }
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def get_vertex(name_or_vertex)
|
113
|
-
case name_or_vertex
|
114
|
-
when Vertex
|
115
|
-
name_or_vertex
|
116
|
-
else
|
117
|
-
@vertices_by_name[name_or_vertex] ||
|
118
|
-
@vertices_by_id.fetch(name_or_vertex)
|
119
|
-
end
|
55
|
+
initialize_mixins(mixins: mixins, **kwargs)
|
120
56
|
end
|
121
57
|
|
122
58
|
# Return a subgraph, optionally filtered by a vertex selector block
|
@@ -126,12 +62,17 @@ module Tangle
|
|
126
62
|
#
|
127
63
|
# Unless a selector is provided, the subgraph contains the entire graph.
|
128
64
|
#
|
129
|
-
def subgraph(
|
130
|
-
|
65
|
+
def subgraph(included = nil)
|
66
|
+
included ||= vertices
|
67
|
+
result = clone
|
68
|
+
vertices.each do |vertex|
|
69
|
+
result.remove_vertex(vertex) unless included.include?(vertex)
|
70
|
+
next unless block_given?
|
71
|
+
result.remove_vertex(vertex) unless yield(vertex)
|
72
|
+
end
|
73
|
+
result
|
131
74
|
end
|
132
75
|
|
133
|
-
attr_reader :mixins
|
134
|
-
|
135
76
|
def to_s
|
136
77
|
"#<#{self.class}: #{vertices.count} vertices, #{edges.count} edges>"
|
137
78
|
end
|
@@ -0,0 +1,49 @@
|
|
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
|
+
insert_edge(self.class::Edge.new(*vertices, mixins: @mixins, **kvargs))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Remove an edge from the graph
|
24
|
+
def remove_edge(edge)
|
25
|
+
edge.each_vertex do |vertex|
|
26
|
+
@vertices.fetch(vertex).delete(edge)
|
27
|
+
end
|
28
|
+
@edges.delete(edge)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# Insert a prepared edge into the graph
|
34
|
+
#
|
35
|
+
def insert_edge(edge)
|
36
|
+
@edges << edge
|
37
|
+
edge.each_vertex do |vertex|
|
38
|
+
@vertices.fetch(vertex) << edge
|
39
|
+
end
|
40
|
+
edge
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def initialize_edges
|
46
|
+
@edges = Set[]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Tangle
|
4
|
+
# Vertex related methods in a graph
|
5
|
+
module GraphVertices
|
6
|
+
# Return all vertices in the graph
|
7
|
+
def vertices
|
8
|
+
@vertices.keys
|
9
|
+
end
|
10
|
+
|
11
|
+
# Add a vertex into the graph
|
12
|
+
def add_vertex(vertex)
|
13
|
+
@vertices[vertex] = Set[]
|
14
|
+
self
|
15
|
+
end
|
16
|
+
alias << add_vertex
|
17
|
+
|
18
|
+
# Remove a vertex from the graph
|
19
|
+
def remove_vertex(vertex)
|
20
|
+
@vertices[vertex].each do |edge|
|
21
|
+
remove_edge(edge) if edge.include?(vertex)
|
22
|
+
end
|
23
|
+
@vertices.delete(vertex)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Initialize vertex related attributes
|
29
|
+
def initialize_vertices
|
30
|
+
@vertices = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Yield each reachable vertex to a block, breadth first
|
34
|
+
def each_vertex_breadth_first(start_vertex, walk_method)
|
35
|
+
remaining = [start_vertex]
|
36
|
+
remaining.each_with_object([]) do |vertex, history|
|
37
|
+
history << vertex
|
38
|
+
yield vertex
|
39
|
+
send(walk_method, vertex).each do |other|
|
40
|
+
remaining << other unless history.include?(other)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def vertex_enumerator(start_vertex, walk_method)
|
46
|
+
Enumerator.new do |yielder|
|
47
|
+
each_vertex_breadth_first(start_vertex, walk_method) do |vertex|
|
48
|
+
yielder << vertex
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -4,17 +4,28 @@ module Tangle
|
|
4
4
|
# Mixins for adding connectedness features
|
5
5
|
#
|
6
6
|
module Connectedness
|
7
|
+
MIXINS = [Tangle::Mixin::Connectedness].freeze
|
7
8
|
#
|
8
9
|
# Mixin for adding connectedness to a graph
|
9
10
|
#
|
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
|
+
|
11
22
|
# Get the largest connected subgraph for a vertex.
|
12
23
|
# Also aliased as :component and :connected_component
|
13
24
|
#
|
14
25
|
# connected_subgraph(vertex) => Graph
|
15
26
|
#
|
16
27
|
def connected_subgraph(vertex)
|
17
|
-
subgraph { |other|
|
28
|
+
subgraph { |other| connected_vertices?(vertex, other) }
|
18
29
|
end
|
19
30
|
alias component connected_subgraph
|
20
31
|
alias connected_component connected_subgraph
|
@@ -23,51 +34,33 @@ module Tangle
|
|
23
34
|
# left after removing the connected subgraph.
|
24
35
|
#
|
25
36
|
def disconnected_subgraph(vertex)
|
26
|
-
subgraph { |other| !
|
37
|
+
subgraph { |other| !connected_vertices?(vertex, other) }
|
27
38
|
end
|
28
39
|
|
29
40
|
# A graph is connected if all vertices are connected to all vertices
|
30
41
|
# An empty graph is disconnected.
|
31
42
|
#
|
32
|
-
def connected?
|
33
|
-
|
43
|
+
def connected?(*tested_vertices)
|
44
|
+
tested_vertices = vertices if tested_vertices.empty?
|
45
|
+
return false if tested_vertices.empty?
|
34
46
|
|
35
|
-
|
47
|
+
tested_vertices.combination(2).all? do |pair|
|
36
48
|
this, that = pair.to_a
|
37
|
-
this.
|
49
|
+
reachable(this).any? { |other| other == that }
|
38
50
|
end
|
39
51
|
end
|
40
52
|
|
41
53
|
# A graph is disconnected if any vertex is not connected to all other.
|
42
54
|
# An empty graph is disconnected.
|
43
55
|
#
|
44
|
-
def disconnected?
|
45
|
-
!connected?
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
#
|
50
|
-
# Mixin for adding connectedness to a vertex
|
51
|
-
#
|
52
|
-
module Vertex
|
53
|
-
# Two vertices are connected if there is a path between them,
|
54
|
-
# and a vertex is connected to itself.
|
55
|
-
#
|
56
|
-
def connected?(other)
|
57
|
-
raise GraphError unless @graph == other.graph
|
58
|
-
return true if self == other
|
59
|
-
|
60
|
-
connected_excluding?(other, Set[self])
|
56
|
+
def disconnected?(*tested_vertices)
|
57
|
+
!connected?(*tested_vertices)
|
61
58
|
end
|
62
59
|
|
63
|
-
|
64
|
-
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
(neighbours - history).any? do |vertex|
|
69
|
-
vertex.connected_excluding?(other, history << self)
|
70
|
-
end
|
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)
|
71
64
|
end
|
72
65
|
end
|
73
66
|
end
|
data/lib/tangle/mixin.rb
CHANGED
@@ -1,28 +1,23 @@
|
|
1
|
-
require 'tangle/mixin/ancestry'
|
2
|
-
require 'tangle/mixin/connectedness'
|
3
|
-
|
4
1
|
module Tangle
|
5
2
|
module Mixin
|
6
3
|
#
|
7
4
|
# Mixin to initialize the dynamic mixin system
|
8
5
|
#
|
9
6
|
module Initialize
|
7
|
+
attr_reader :mixins
|
8
|
+
|
10
9
|
private
|
11
10
|
|
12
|
-
def initialize_mixins(mixins
|
13
|
-
|
14
|
-
when :Graph
|
15
|
-
@mixins = mixins
|
16
|
-
else
|
17
|
-
mixins = @graph.mixins unless @graph.nil?
|
18
|
-
end
|
11
|
+
def initialize_mixins(mixins: nil, **kwargs)
|
12
|
+
@mixins = mixins
|
19
13
|
|
20
|
-
extend_with_mixins
|
14
|
+
extend_with_mixins unless @mixins.nil?
|
21
15
|
initialize_kwargs(**kwargs) unless kwargs.empty?
|
22
16
|
end
|
23
17
|
|
24
|
-
def extend_with_mixins
|
25
|
-
|
18
|
+
def extend_with_mixins
|
19
|
+
klass = self.class.name[/[^:]+$/].to_sym
|
20
|
+
@mixins.each do |mixin|
|
26
21
|
extend(mixin.const_get(klass)) if mixin.const_defined?(klass)
|
27
22
|
end
|
28
23
|
end
|
data/lib/tangle/simple/graph.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'tangle/graph'
|
2
|
-
require 'tangle/simple/edge'
|
3
2
|
|
4
3
|
module Tangle
|
5
4
|
module Simple
|
6
5
|
#
|
7
6
|
# A simple graph, without loops and multiple edges
|
8
7
|
class Graph < Tangle::Graph
|
9
|
-
|
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
|
10
15
|
end
|
11
16
|
end
|
12
17
|
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.8.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-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git-version-bump
|
@@ -99,22 +99,19 @@ files:
|
|
99
99
|
- bin/console
|
100
100
|
- bin/setup
|
101
101
|
- lib/tangle.rb
|
102
|
-
- lib/tangle/directed/acyclic/edge.rb
|
103
102
|
- lib/tangle/directed/acyclic/graph.rb
|
103
|
+
- lib/tangle/directed/acyclic/partial_order.rb
|
104
104
|
- lib/tangle/directed/edge.rb
|
105
105
|
- lib/tangle/directed/graph.rb
|
106
106
|
- lib/tangle/edge.rb
|
107
107
|
- lib/tangle/errors.rb
|
108
108
|
- lib/tangle/graph.rb
|
109
|
-
- lib/tangle/
|
110
|
-
- lib/tangle/
|
109
|
+
- lib/tangle/graph_edges.rb
|
110
|
+
- lib/tangle/graph_vertices.rb
|
111
111
|
- lib/tangle/mixin.rb
|
112
|
-
- lib/tangle/mixin/ancestry.rb
|
113
112
|
- lib/tangle/mixin/connectedness.rb
|
114
|
-
- lib/tangle/simple/edge.rb
|
115
113
|
- lib/tangle/simple/graph.rb
|
116
114
|
- lib/tangle/version.rb
|
117
|
-
- lib/tangle/vertex.rb
|
118
115
|
- tangle.gemspec
|
119
116
|
homepage: https://github.com/notcalle/ruby-tangle
|
120
117
|
licenses:
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'tangle/directed/edge'
|
2
|
-
|
3
|
-
module Tangle
|
4
|
-
module Directed
|
5
|
-
module Acyclic
|
6
|
-
#
|
7
|
-
# An edge in a directed acyclic graph
|
8
|
-
#
|
9
|
-
class Edge < Tangle::Directed::Edge
|
10
|
-
private
|
11
|
-
|
12
|
-
def validate_edge
|
13
|
-
super
|
14
|
-
raise CyclicError if @parent.ancestor?(@child) ||
|
15
|
-
@child.descendant?(@parent)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
data/lib/tangle/graph_private.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Tangle
|
2
|
-
#
|
3
|
-
# The private bits of Graph
|
4
|
-
#
|
5
|
-
module GraphPrivate
|
6
|
-
private
|
7
|
-
|
8
|
-
def initialize_vertices
|
9
|
-
@vertices_by_id = {}
|
10
|
-
@vertices_by_name = {}
|
11
|
-
@edges_by_vertex = {}
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize_edges
|
15
|
-
@edges = []
|
16
|
-
end
|
17
|
-
|
18
|
-
def clone_vertices_into(graph, &selector)
|
19
|
-
vertices(&selector).each do |vertex|
|
20
|
-
graph.insert_vertex(vertex.clone_into(graph))
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def clone_edges_into(graph)
|
25
|
-
edges.each do |edge|
|
26
|
-
new_edge = edge.clone_into(graph)
|
27
|
-
graph.insert_edge(new_edge) unless new_edge.nil?
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
module Tangle
|
2
|
-
#
|
3
|
-
# The protected bits of Graph
|
4
|
-
#
|
5
|
-
module GraphProtected
|
6
|
-
protected
|
7
|
-
|
8
|
-
# Insert a prepared vertex into the graph
|
9
|
-
#
|
10
|
-
def insert_vertex(vertex)
|
11
|
-
raise ArgumentError unless vertex.graph.eql?(self)
|
12
|
-
|
13
|
-
@edges_by_vertex[vertex] = []
|
14
|
-
@vertices_by_name[vertex.name] = vertex unless vertex.name.nil?
|
15
|
-
@vertices_by_id[vertex.vertex_id] = vertex
|
16
|
-
end
|
17
|
-
|
18
|
-
# Insert a prepared edge into the graph
|
19
|
-
#
|
20
|
-
def insert_edge(edge)
|
21
|
-
raise ArgumentError unless edge.graph.eql?(self)
|
22
|
-
|
23
|
-
@edges << edge
|
24
|
-
edge.vertices.each do |vertex|
|
25
|
-
@edges_by_vertex[vertex] << edge
|
26
|
-
end
|
27
|
-
edge
|
28
|
-
end
|
29
|
-
|
30
|
-
def with_vertices(vertices = [])
|
31
|
-
initialize_vertices
|
32
|
-
|
33
|
-
vertices.each do |vertex|
|
34
|
-
insert_vertex(vertex.clone_into(self))
|
35
|
-
end
|
36
|
-
self
|
37
|
-
end
|
38
|
-
|
39
|
-
def with_edges(edges = [])
|
40
|
-
initialize_edges
|
41
|
-
|
42
|
-
edges.each do |edge|
|
43
|
-
new_edge = edge.clone_into(self)
|
44
|
-
insert_edge(new_edge) unless new_edge.nil?
|
45
|
-
end
|
46
|
-
self
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
require 'tangle/mixin/connectedness'
|
2
|
-
|
3
|
-
module Tangle
|
4
|
-
module Mixin
|
5
|
-
#
|
6
|
-
# Mixin for adding ancestry features
|
7
|
-
#
|
8
|
-
module Ancestry
|
9
|
-
#
|
10
|
-
# Mixins for adding ancestry relations to a digraph
|
11
|
-
#
|
12
|
-
module Graph
|
13
|
-
include Tangle::Mixin::Connectedness::Graph
|
14
|
-
|
15
|
-
def ancestor_subgraph(vertex, &selector)
|
16
|
-
vertex = get_vertex(vertex) unless vertex.is_a? Vertex
|
17
|
-
clone.with_vertices(vertex.ancestors(&selector)).with_edges(edges)
|
18
|
-
end
|
19
|
-
|
20
|
-
def descendant_subgraph(vertex, &selector)
|
21
|
-
vertex = get_vertex(vertex) unless vertex.is_a? Vertex
|
22
|
-
clone.with_vertices(vertex.descendants(&selector)).with_edges(edges)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
#
|
27
|
-
# Mixins for adding ancestry relations to vertices in a digraph
|
28
|
-
#
|
29
|
-
module Vertex
|
30
|
-
include Tangle::Mixin::Connectedness::Vertex
|
31
|
-
|
32
|
-
def parent_edges
|
33
|
-
@graph.edges(vertex: self) { |edge| edge.child?(self) }
|
34
|
-
end
|
35
|
-
|
36
|
-
def parents
|
37
|
-
neighbours(parent_edges)
|
38
|
-
end
|
39
|
-
|
40
|
-
def ancestors
|
41
|
-
result = [self] + parents.flat_map(&:ancestors)
|
42
|
-
return result unless block_given?
|
43
|
-
result.select(&:yield)
|
44
|
-
end
|
45
|
-
|
46
|
-
def parent?(other)
|
47
|
-
@graph.edges.any? { |edge| edge.child?(self) && edge.parent?(other) }
|
48
|
-
end
|
49
|
-
|
50
|
-
def ancestor?(other)
|
51
|
-
other == self || parents.any? { |parent| parent.ancestor?(other) }
|
52
|
-
end
|
53
|
-
|
54
|
-
def child_edges
|
55
|
-
@graph.edges(vertex: self) { |edge| edge.parent?(self) }
|
56
|
-
end
|
57
|
-
|
58
|
-
def children
|
59
|
-
neighbours(child_edges)
|
60
|
-
end
|
61
|
-
|
62
|
-
def child?(other)
|
63
|
-
@graph.edges.any? { |edge| edge.parent?(self) && edge.child?(other) }
|
64
|
-
end
|
65
|
-
|
66
|
-
def descendants
|
67
|
-
result = [self] + children.flat_map(&:descendants)
|
68
|
-
return result unless block_given?
|
69
|
-
result.select(&:yield)
|
70
|
-
end
|
71
|
-
|
72
|
-
def descendant?(other)
|
73
|
-
other == self || children.any? { |child| child.descendant?(other) }
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
data/lib/tangle/simple/edge.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'tangle/edge'
|
2
|
-
|
3
|
-
module Tangle
|
4
|
-
module Simple
|
5
|
-
#
|
6
|
-
# An edge in a simple graph, with no loops or multiedges
|
7
|
-
#
|
8
|
-
class Edge < Tangle::Edge
|
9
|
-
private
|
10
|
-
|
11
|
-
def validate_edge
|
12
|
-
super
|
13
|
-
raise LoopError unless @vertices.count == 2
|
14
|
-
raise MultiEdgeError if @graph.edges.include? self
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/tangle/vertex.rb
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
require 'pp'
|
3
|
-
require 'tangle/mixin'
|
4
|
-
|
5
|
-
module Tangle
|
6
|
-
#
|
7
|
-
# A named vertex in a graph
|
8
|
-
#
|
9
|
-
class Vertex
|
10
|
-
include Tangle::Mixin::Initialize
|
11
|
-
|
12
|
-
# Create a new vertex
|
13
|
-
#
|
14
|
-
# Vertex.new(...) => Vertex
|
15
|
-
#
|
16
|
-
# Named arguments:
|
17
|
-
# graph: a Graph or nil for an orphaned vertex
|
18
|
-
# name: anything that's hashable and unique within the graph
|
19
|
-
#
|
20
|
-
def initialize(graph: nil,
|
21
|
-
name: nil,
|
22
|
-
vertex_id: object_id,
|
23
|
-
**kwargs)
|
24
|
-
@graph = graph
|
25
|
-
@name = name
|
26
|
-
@vertex_id = vertex_id
|
27
|
-
|
28
|
-
initialize_mixins(**kwargs)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Clone a vertex in a new graph, keeping all other contained attributes
|
32
|
-
# End users should probably use Graph#subgraph instead.
|
33
|
-
#
|
34
|
-
# clone_into(new_graph) => Vertex
|
35
|
-
#
|
36
|
-
# Raises an ArgumentError if graph would remain the same.
|
37
|
-
#
|
38
|
-
def clone_into(graph)
|
39
|
-
raise ArgumentError if graph == @graph
|
40
|
-
|
41
|
-
clone.with_graph(graph)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Return all edges that touch this vertex
|
45
|
-
#
|
46
|
-
def edges
|
47
|
-
return [] if @graph.nil?
|
48
|
-
|
49
|
-
@graph.edges(vertex: self)
|
50
|
-
end
|
51
|
-
|
52
|
-
# Return the set of adjacent vertices
|
53
|
-
#
|
54
|
-
def neighbours(included = edges)
|
55
|
-
Set.new(included.map { |edge| edge.walk(self) })
|
56
|
-
end
|
57
|
-
|
58
|
-
# If two vertices have the same vertex_id, they have the same value
|
59
|
-
#
|
60
|
-
def ==(other)
|
61
|
-
@vertex_id == other.vertex_id
|
62
|
-
end
|
63
|
-
|
64
|
-
# If two vertices have the same vertex_id, they have the same value
|
65
|
-
#
|
66
|
-
def !=(other)
|
67
|
-
@vertex_id != other.vertex_id
|
68
|
-
end
|
69
|
-
|
70
|
-
# If two vertices have the same object_id, they are identical
|
71
|
-
#
|
72
|
-
def eql?(other)
|
73
|
-
@object_id == other.object_id
|
74
|
-
end
|
75
|
-
|
76
|
-
# Two vertices are adjacent if there is an edge between them
|
77
|
-
#
|
78
|
-
def adjacent?(other)
|
79
|
-
raise GraphError unless @graph == other.graph
|
80
|
-
edges.any? { |edge| edge.walk(self) == other }
|
81
|
-
end
|
82
|
-
|
83
|
-
attr_reader :graph
|
84
|
-
attr_reader :name
|
85
|
-
attr_reader :vertex_id
|
86
|
-
|
87
|
-
def to_s
|
88
|
-
values = {
|
89
|
-
class: self.class,
|
90
|
-
ident: name.nil? ? format('0x%x', vertex_id) : "'#{name}'",
|
91
|
-
n_edges: edges.count
|
92
|
-
}
|
93
|
-
format('#<%<class>s:%<ident>s: %<n_edges>d edges>', values)
|
94
|
-
end
|
95
|
-
alias inspect to_s
|
96
|
-
|
97
|
-
protected
|
98
|
-
|
99
|
-
def with_graph(graph)
|
100
|
-
@graph = graph
|
101
|
-
self
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|