tangle 0.7.0 → 0.8.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/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
|