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