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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5203984ea1bf3218c6ac6e2a84493af7feec46d2dda8f6005e8d24b5e81671db
4
- data.tar.gz: b39938c50a2a828963af60ee6877dae048a52a5a83fb6e5ccadc3f2727cc66d0
3
+ metadata.gz: 49703932d4a9666672412e9861b5a6a5e771375b1d7147ccff6a76da283d76ea
4
+ data.tar.gz: bd1681a13bead0ee180e01447bfdab7d9041f033e968840c6124d2e794688f86
5
5
  SHA512:
6
- metadata.gz: 5598f8494cee8dfdbbc6061ab2f00596fb70fd419872a8884182c21b0ad0c62c234c2ac766df1dadffa4cd6ce84b60b5a1b44f46cbc27782e162535252db89b2
7
- data.tar.gz: c34f2d721e72680ffca31108d1b608b868912bc8b631f9abd7ea866335503e287ae9031660a8d2c1e2781a6ce9276c22d302001339522ddf18224b665838695f
6
+ metadata.gz: c649bf8ef9250797945c2fc378a639118689d41c531a45c2b5be9e997d8a670c8e30bffd958c573f31a03b81885c85a3f5d1e24a202fe4dd0be08d3cf8fffc8c
7
+ data.tar.gz: b0d0fde21a7e2f38bd99e787695c2f07098c02163485f02ec0bc17bd7a0a295f49eef72258324fa9c3ff1b514ead63c75451d0cd8ab462fa6b60efeb274d9faa
data/README.md CHANGED
@@ -13,8 +13,6 @@ Tangle aims to untangle your graphs, by providing a set of classes for managing
13
13
 
14
14
  **Feature mixins**:
15
15
  * Connectedness
16
- * Ancestry
17
- * ~~Vertex ordering~~
18
16
  * ~~Coloring~~
19
17
  * ~~GraphViz~~
20
18
 
@@ -1,13 +1,24 @@
1
1
  require 'tangle/directed/graph'
2
- require 'tangle/directed/acyclic/edge'
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
- Edge = Tangle::Directed::Acyclic::Edge
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
@@ -6,28 +6,32 @@ module Tangle
6
6
  # An edge in a directed graph
7
7
  #
8
8
  class Edge < Tangle::Edge
9
- def parent?(vertex)
10
- @parent == vertex
9
+ attr_reader :head, :tail
10
+
11
+ def head?(vertex)
12
+ @head == vertex
11
13
  end
12
14
 
13
- def parent(_vertex = nil)
14
- @parent
15
+ def tail?(vertex)
16
+ @tail == vertex
15
17
  end
16
18
 
17
- def child?(vertex)
18
- @child == vertex
19
+ def each_vertex(&block)
20
+ [@tail, @head].each(&block)
19
21
  end
20
22
 
21
- def child(_vertex = nil)
22
- @child
23
+ def to_s
24
+ "{#{@tail}-->#{@head}}"
23
25
  end
26
+ alias inspect to_s
24
27
 
25
- protected
28
+ private
26
29
 
27
- def with_vertices(child, parent = child)
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 = [Tangle::Mixin::Ancestry].freeze
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, graph: nil, name: nil, **kwargs)
20
+ def initialize(vertex1, vertex2 = vertex1, name: nil, **kwargs)
20
21
  @name = name
21
- with_graph(graph)
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
- # Follow the edge from a vertex to the other end
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
- # Return the other vertex for a vertex of this edge
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
- # Clone an edge into another graph, replacing original vertices with
46
- # their already prepared duplicates in the other graph. Returns nil if any
47
- # of the vertices does not exist in the other graph.
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 eql?(other)
71
- @graph == other.graph && @vertices == other.vertices
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 with_graph(graph)
86
- @graph = graph
87
- self
44
+ def include?(vertex)
45
+ each_vertex.include?(vertex)
88
46
  end
89
47
 
90
- def with_vertices(vertex1, vertex2 = vertex1)
91
- @vertices = Set[vertex1, vertex2]
92
- self
48
+ def loop?
49
+ @loop
93
50
  end
94
51
 
95
52
  private
96
53
 
97
- def validate_edge
98
- raise GraphError unless @vertices.all? { |vertex| vertex.graph == @graph }
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/vertex'
2
+ require 'tangle/mixin/connectedness'
3
3
  require 'tangle/edge'
4
- require 'tangle/graph_private'
5
- require 'tangle/graph_protected'
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::GraphPrivate
13
- include Tangle::GraphProtected
12
+ include Tangle::GraphVertices
13
+ include Tangle::GraphEdges
14
14
  include Tangle::Mixin::Initialize
15
15
  Edge = Tangle::Edge
16
- DEFAULT_MIXINS = [Tangle::Mixin::Connectedness].freeze
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.add_vertices(vertices)
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(&selector)
130
- clone.with_vertices(vertices(&selector)).with_edges(edges)
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| vertex.connected?(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| !vertex.connected?(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
- return false if vertices.empty?
43
+ def connected?(*tested_vertices)
44
+ tested_vertices = vertices if tested_vertices.empty?
45
+ return false if tested_vertices.empty?
34
46
 
35
- vertices.combination(2).all? do |pair|
47
+ tested_vertices.combination(2).all? do |pair|
36
48
  this, that = pair.to_a
37
- this.connected?(that)
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
- protected
64
-
65
- def connected_excluding?(other, history)
66
- return true if other.adjacent?(self)
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 = nil, **kwargs)
13
- case klass = self.class.name[/[^:]+$/].to_sym
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(klass, mixins) unless mixins.nil?
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(klass, mixins)
25
- mixins.each do |mixin|
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
@@ -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
- Edge = Tangle::Simple::Edge
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.7.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-04-07 00:00:00.000000000 Z
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/graph_private.rb
110
- - lib/tangle/graph_protected.rb
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
@@ -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
@@ -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