dag 0.0.4 → 0.0.5

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0902242dbbc24972c53aff3dff22f001f7db4f9d
4
+ data.tar.gz: 42d773c5d7036e62f8727bdbbe6b63e8f97dcee1
5
+ SHA512:
6
+ metadata.gz: a15741d978e1d0e89a19af621342a786acabf96e28a820bb7582ad619e87641c052d6667553d4f3f2da8e68588073e9b4cc0750a83b8849b3537c44f2483fe78
7
+ data.tar.gz: a43973369d8803cf5948683c7e3a86b0fbc5d7cf624c565de8cc294d11b00139c38d1ac4118c2d921175ac45322ec4c8c0693931b36c26148eb95946989f4066
data/Gemfile CHANGED
@@ -1,9 +1,2 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
  gemspec
3
-
4
- group :test do
5
- gem 'rspec', '~> 2.12'
6
- gem 'rake', '~> 10.0.1'
7
- gem 'simplecov', :require => false
8
- end
9
-
data/Gemfile.lock CHANGED
@@ -1,32 +1,30 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dag (0.0.3)
4
+ dag (0.0.5)
5
5
 
6
6
  GEM
7
- remote: http://rubygems.org/
7
+ remote: https://rubygems.org/
8
8
  specs:
9
- diff-lcs (1.2.2)
10
- multi_json (1.7.2)
11
- rake (10.0.4)
12
- rspec (2.13.0)
13
- rspec-core (~> 2.13.0)
14
- rspec-expectations (~> 2.13.0)
15
- rspec-mocks (~> 2.13.0)
16
- rspec-core (2.13.1)
17
- rspec-expectations (2.13.0)
18
- diff-lcs (>= 1.1.3, < 2.0)
19
- rspec-mocks (2.13.0)
20
- simplecov (0.7.1)
21
- multi_json (~> 1.0)
22
- simplecov-html (~> 0.7.1)
23
- simplecov-html (0.7.1)
9
+ diff-lcs (1.2.5)
10
+ rake (10.4.2)
11
+ rspec (3.1.0)
12
+ rspec-core (~> 3.1.0)
13
+ rspec-expectations (~> 3.1.0)
14
+ rspec-mocks (~> 3.1.0)
15
+ rspec-core (3.1.7)
16
+ rspec-support (~> 3.1.0)
17
+ rspec-expectations (3.1.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.1.0)
20
+ rspec-mocks (3.1.3)
21
+ rspec-support (~> 3.1.0)
22
+ rspec-support (3.1.2)
24
23
 
25
24
  PLATFORMS
26
25
  ruby
27
26
 
28
27
  DEPENDENCIES
29
28
  dag!
30
- rake (~> 10.0.1)
31
- rspec (~> 2.12)
32
- simplecov
29
+ rake (~> 10)
30
+ rspec (~> 3)
data/lib/dag/vertex.rb CHANGED
@@ -39,7 +39,7 @@ class DAG
39
39
  #
40
40
  def has_path_to?(other)
41
41
  raise ArgumentError.new('You must supply a vertex in this DAG') unless
42
- other && Vertex === other && other.dag == self.dag
42
+ is_vertex_in_my_dag?(other)
43
43
  successors.include?(other) || successors.any? {|v| v.has_path_to?(other) }
44
44
  end
45
45
 
@@ -54,7 +54,7 @@ class DAG
54
54
  #
55
55
  def has_ancestor?(other)
56
56
  raise ArgumentError.new('You must supply a vertex in this DAG') unless
57
- other && Vertex === other && other.dag == self.dag
57
+ is_vertex_in_my_dag?(other)
58
58
  predecessors.include?(other) || predecessors.any? {|v| v.has_ancestor?(other) }
59
59
  end
60
60
 
@@ -71,6 +71,31 @@ class DAG
71
71
  @payload[key]
72
72
  end
73
73
 
74
+ def ancestors(result_set = Set.new)
75
+ predecessors.each do |v|
76
+ unless result_set.include? v
77
+ result_set.add(v)
78
+ v.ancestors(result_set)
79
+ end
80
+ end
81
+ return result_set
82
+ end
83
+
84
+ def descendants(result_set = Set.new)
85
+ successors.each do |v|
86
+ unless result_set.include? v
87
+ result_set.add(v)
88
+ v.descendants(result_set)
89
+ end
90
+ end
91
+ return result_set
92
+ end
93
+
94
+ private
95
+
96
+ def is_vertex_in_my_dag?(v)
97
+ v.kind_of?(Vertex) and v.dag == self.dag
98
+ end
74
99
  end
75
100
 
76
101
  end
data/lib/dag.rb CHANGED
@@ -2,7 +2,7 @@ require_relative 'dag/vertex'
2
2
 
3
3
  class DAG
4
4
 
5
- Edge = Struct.new(:origin, :destination)
5
+ Edge = Struct.new(:origin, :destination, :properties)
6
6
 
7
7
  attr_reader :vertices, :edges
8
8
 
@@ -28,13 +28,64 @@ class DAG
28
28
  def add_edge(attrs)
29
29
  origin = attrs[:origin] || attrs[:source] || attrs[:from] || attrs[:start]
30
30
  destination = attrs[:destination] || attrs[:sink] || attrs[:to] || attrs[:end]
31
+ properties = attrs[:properties] || {}
31
32
  raise ArgumentError.new('Origin must be a vertex in this DAG') unless
32
- origin && Vertex === origin && origin.dag == self
33
+ is_my_vertex?(origin)
33
34
  raise ArgumentError.new('Destination must be a vertex in this DAG') unless
34
- destination && Vertex === destination && destination.dag == self
35
+ is_my_vertex?(destination)
35
36
  raise ArgumentError.new('A DAG must not have cycles') if origin == destination
36
37
  raise ArgumentError.new('A DAG must not have cycles') if destination.has_path_to?(origin)
37
- Edge.new(origin, destination).tap {|e| @edges << e }
38
+ Edge.new(origin, destination, properties).tap {|e| @edges << e }
39
+ end
40
+
41
+ def subgraph(predecessors_of = [], successors_of = [])
42
+
43
+
44
+ (predecessors_of + successors_of).each { |v|
45
+ raise ArgumentError.new('You must supply a vertex in this DAG') unless
46
+ is_my_vertex?(v)
47
+ }
48
+
49
+ result = DAG.new({mixin: @mixin})
50
+ vertex_mapping = {}
51
+
52
+ # Get the set of predecessors verticies and add a copy to the result
53
+ predecessors_set = Set.new(predecessors_of)
54
+ predecessors_of.each { |v| v.ancestors(predecessors_set) }
55
+
56
+ predecessors_set.each do |v|
57
+ vertex_mapping[v] = result.add_vertex(payload=v.payload)
58
+ end
59
+
60
+ # Get the set of successor vertices and add a copy to the result
61
+ successors_set = Set.new(successors_of)
62
+ successors_of.each { |v| v.descendants(successors_set) }
63
+
64
+ successors_set.each do |v|
65
+ vertex_mapping[v] = result.add_vertex(payload=v.payload) unless vertex_mapping.include? v
66
+ end
67
+
68
+ # get the unique edges
69
+ edge_set = (
70
+ predecessors_set.flat_map(&:incoming_edges) +
71
+ successors_set.flat_map(&:outgoing_edges)
72
+ ).uniq
73
+
74
+ # Add them to the result via the vertex mapping
75
+ edge_set.each do |e|
76
+ result.add_edge(
77
+ from: vertex_mapping[e.origin],
78
+ to: vertex_mapping[e.destination],
79
+ properties: e.properties)
80
+ end
81
+
82
+ return result
83
+ end
84
+
85
+ private
86
+
87
+ def is_my_vertex?(v)
88
+ v.kind_of?(Vertex) and v.dag == self
38
89
  end
39
90
 
40
91
  end
@@ -31,18 +31,18 @@ describe DAG::Vertex do
31
31
  subject { dag.add_vertex(name: 'Fred', size: 34) }
32
32
 
33
33
  it 'allows the payload to be accessed' do
34
- subject[:name].should == 'Fred'
35
- subject[:size].should == 34
36
- subject.payload.should == {name: 'Fred', size: 34}
34
+ expect(subject[:name]).to eq('Fred')
35
+ expect(subject[:size]).to eq(34)
36
+ expect(subject.payload).to eq({name: 'Fred', size: 34})
37
37
  end
38
38
 
39
39
  it 'returns nil for missing payload key' do
40
- subject[56].should be_nil
40
+ expect(subject[56]).to be_nil
41
41
  end
42
42
 
43
43
  it 'allows the payload to be changed' do
44
44
  subject.payload[:another] = 'ha'
45
- subject[:another].should == 'ha'
45
+ expect(subject[:another]).to eq('ha')
46
46
  end
47
47
  end
48
48
 
@@ -52,25 +52,30 @@ describe DAG::Vertex do
52
52
  dag.add_edge from: v2, to: subject
53
53
  end
54
54
 
55
- its(:predecessors) { should =~ [v1, v2] }
56
- its(:successors) { should == [] }
55
+ it 'has the correct predecessors' do
56
+ expect(subject.predecessors).to eq([v1, v2])
57
+ end
58
+
59
+ it 'has no successors' do
60
+ expect(subject.successors).to be_empty
61
+ end
57
62
 
58
63
  it 'has no paths to its predecessors' do
59
- subject.has_path_to?(v1).should be_false
60
- subject.has_path_to?(v2).should be_false
64
+ expect(subject.has_path_to?(v1)).to be_falsey
65
+ expect(subject.has_path_to?(v2)).to be_falsey
61
66
  end
62
67
 
63
68
  context 'with multiple paths' do
64
69
  it 'lists each predecessor only once' do
65
70
  dag.add_edge from: v1, to: subject
66
- subject.predecessors.should == [v1, v2]
71
+ expect(subject.predecessors).to eq([v1, v2])
67
72
  end
68
73
  end
69
74
 
70
75
  it 'has the correct ancestors' do
71
- subject.has_ancestor?(v1).should be_true
72
- subject.has_ancestor?(v2).should be_true
73
- subject.has_ancestor?(v3).should be_false
76
+ expect(subject.has_ancestor?(v1)).to be_truthy
77
+ expect(subject.has_ancestor?(v2)).to be_truthy
78
+ expect(subject.has_ancestor?(v3)).to be_falsey
74
79
  end
75
80
  end
76
81
 
@@ -80,24 +85,29 @@ describe DAG::Vertex do
80
85
  dag.add_edge from: subject, to: v2
81
86
  end
82
87
 
83
- its(:predecessors) { should == [] }
84
- its(:successors) { should =~ [v1, v2] }
88
+ it 'has no predecessors' do
89
+ expect(subject.predecessors).to be_empty
90
+ end
91
+
92
+ it 'has the correct successors' do
93
+ expect(subject.successors).to eq([v1, v2])
94
+ end
85
95
 
86
96
  it 'has paths to its successors' do
87
- subject.has_path_to?(v1).should be_true
88
- subject.has_path_to?(v2).should be_true
97
+ expect(subject.has_path_to?(v1)).to be_truthy
98
+ expect(subject.has_path_to?(v2)).to be_truthy
89
99
  end
90
100
 
91
101
  context 'with multiple paths' do
92
102
  it 'lists each successor only once' do
93
103
  dag.add_edge from: subject, to: v1
94
- subject.successors.should == [v1, v2]
104
+ expect(subject.successors).to eq([v1, v2])
95
105
  end
96
106
  end
97
107
 
98
108
  it 'has no ancestors' do
99
- subject.has_ancestor?(v1).should be_false
100
- subject.has_ancestor?(v2).should be_false
109
+ expect(subject.has_ancestor?(v1)).to be_falsey
110
+ expect(subject.has_ancestor?(v2)).to be_falsey
101
111
  end
102
112
  end
103
113
 
@@ -108,17 +118,29 @@ describe DAG::Vertex do
108
118
  end
109
119
 
110
120
  it 'has a deep path to v2' do
111
- subject.has_path_to?(v2).should be_true
121
+ expect(subject.has_path_to?(v2)).to be_truthy
112
122
  end
113
123
 
114
124
  it 'has no path to v3' do
115
- subject.has_path_to?(v3).should be_false
125
+ expect(subject.has_path_to?(v3)).to be_falsey
116
126
  end
117
127
 
118
128
  it 'recognises that it is an ancestor of v2' do
119
- v2.has_ancestor?(subject).should be_true
129
+ expect(v2.has_ancestor?(subject)).to be_truthy
130
+ end
131
+
132
+ it 'is known to all descendants' do
133
+ expect(v2.ancestors).to eq(Set.new([v1, subject]))
134
+ end
135
+
136
+ it 'knows has no ancestors' do
137
+ expect(subject.ancestors).to eq(Set.new())
120
138
  end
139
+
140
+ it 'knows has all descendants' do
141
+ expect(subject.descendants).to eq(Set.new([v1, v2]))
142
+ end
143
+
121
144
  end
122
145
 
123
146
  end
124
-
data/spec/lib/dag_spec.rb CHANGED
@@ -2,21 +2,21 @@ require 'spec_helper'
2
2
 
3
3
  describe DAG do
4
4
 
5
- context 'when new' do
6
- its(:vertices) { should be_empty }
7
- its(:edges) { should be_empty }
5
+ it 'when new' do
6
+ expect(subject.vertices).to be_empty
7
+ expect(subject.edges).to be_empty
8
8
  end
9
9
 
10
10
  context 'with one vertex' do
11
11
  it 'has only that vertex' do
12
12
  v = subject.add_vertex
13
- subject.vertices.should == [v]
13
+ expect(subject.vertices).to eq([v])
14
14
  end
15
15
 
16
16
  it 'has no edges' do
17
17
  v = subject.add_vertex
18
- v.outgoing_edges.should be_empty
19
- v.incoming_edges.should be_empty
18
+ expect(v.outgoing_edges).to be_empty
19
+ expect(v.incoming_edges).to be_empty
20
20
  end
21
21
  end
22
22
 
@@ -31,11 +31,11 @@ describe DAG do
31
31
  end
32
32
 
33
33
  it 'mixes the module into evey vertex' do
34
- (Thing === v).should be_true
34
+ expect((Thing === v)).to be_truthy
35
35
  end
36
36
 
37
37
  it 'allows the module to access the payload' do
38
- v.my_name.should == 'Fred'
38
+ expect(v.my_name).to eq('Fred')
39
39
  end
40
40
  end
41
41
 
@@ -47,21 +47,30 @@ describe DAG do
47
47
  let!(:e1) { subject.add_edge(origin: v1, destination: v2) }
48
48
 
49
49
  it 'leaves the origin vertex' do
50
- v1.outgoing_edges.should == [e1]
50
+ expect(v1.outgoing_edges).to eq([e1])
51
51
  end
52
52
 
53
53
  it 'arrives at the destination vertex' do
54
- v2.incoming_edges.should == [e1]
54
+ expect(v2.incoming_edges).to eq([e1])
55
55
  end
56
56
 
57
57
  it 'adds no other edges' do
58
- v1.incoming_edges.should be_empty
59
- v2.outgoing_edges.should be_empty
58
+ expect(v1.incoming_edges).to be_empty
59
+ expect(v2.outgoing_edges).to be_empty
60
+ end
61
+
62
+ it 'it has no properties' do
63
+ expect(e1.properties).to be_empty
60
64
  end
61
65
 
62
66
  it 'allows multiple edges between a pair of vertices' do
63
67
  expect { subject.add_edge(origin: v1, destination: v2) }.to_not raise_error
64
68
  end
69
+
70
+ it 'can specify properties' do
71
+ e2 = subject.add_edge(origin: v1, destination: v2, properties: {foo: 'bar'})
72
+ expect(e2.properties[:foo]).to eq('bar')
73
+ end
65
74
  end
66
75
 
67
76
  context 'when invalid' do
@@ -105,20 +114,82 @@ describe DAG do
105
114
  let!(:e1) { subject.add_edge(origin: v1, destination: v2) }
106
115
 
107
116
  it 'allows :source and :sink' do
108
- subject.add_edge(source: v1, sink: v2).should == e1
117
+ expect(subject.add_edge(source: v1, sink: v2)).to eq(e1)
109
118
  end
110
119
 
111
120
  it 'allows :from and :to' do
112
- subject.add_edge(from: v1, to: v2).should == e1
121
+ expect(subject.add_edge(from: v1, to: v2)).to eq(e1)
113
122
  end
114
123
 
115
124
  it 'allows :start and :end' do
116
- subject.add_edge(start: v1, end: v2).should == e1
125
+ expect(subject.add_edge(start: v1, end: v2)).to eq(e1)
117
126
  end
118
127
 
119
128
  end
120
129
 
121
130
  end
122
131
 
123
- end
132
+ context 'given a dag' do
133
+ subject { DAG.new(mixin: Thing) }
134
+ module Thing
135
+ def my_name
136
+ payload[:name]
137
+ end
138
+ end
139
+
140
+ let(:joe) { subject.add_vertex(name: "joe") }
141
+ let(:bob) { subject.add_vertex(name: "bob") }
142
+ let(:jane) { subject.add_vertex(name: "jane") }
143
+ let!(:e1) { subject.add_edge(origin: joe, destination: bob, properties: {name: "father of"} ) }
144
+ let!(:e2) { subject.add_edge(origin: joe, destination: jane) }
145
+ let!(:e3) { subject.add_edge(origin: bob, destination: jane) }
146
+
147
+ describe '.subgraph' do
148
+ it 'returns a graph' do
149
+ expect(subject.subgraph()).to be_an_instance_of(DAG)
150
+ end
151
+
152
+ it 'of joe and his ancestors' do
153
+ subgraph = subject.subgraph([joe,],[])
154
+ expect(subgraph.vertices.length).to eq(1)
155
+ expect(subgraph.vertices[0].my_name).to eq("joe")
156
+ expect(subgraph.edges).to be_empty
157
+ end
124
158
 
159
+ it 'of joe and his descendants' do
160
+ subgraph = subject.subgraph([],[joe,])
161
+ expect(Set.new(subgraph.vertices.map(&:my_name))).to eq(Set.new(["joe","bob","jane"]))
162
+ expect(subgraph.edges.length).to eq(3)
163
+ end
164
+
165
+ it 'of Jane and her ancestors' do
166
+ subgraph = subject.subgraph([jane,],[])
167
+ expect(Set.new(subgraph.vertices.map(&:my_name))).to eq(Set.new(["joe","bob","jane"]))
168
+ expect(subgraph.edges.length).to eq(3)
169
+ end
170
+
171
+ it 'of jane and her descendants' do
172
+ subgraph = subject.subgraph([],[jane,])
173
+ expect(Set.new(subgraph.vertices.map(&:my_name))).to eq(Set.new(["jane"]))
174
+ expect(subgraph.edges).to be_empty
175
+ end
176
+
177
+ it 'of bob and his descendants' do
178
+ subgraph = subject.subgraph([],[bob,])
179
+ expect(Set.new(subgraph.vertices.map(&:my_name))).to eq(Set.new(["bob","jane"]))
180
+ expect(subgraph.edges.length).to eq(1)
181
+ end
182
+
183
+ it 'there is something incestuous going on here' do
184
+ subgraph = subject.subgraph([bob,],[bob,])
185
+ expect(Set.new(subgraph.vertices.map(&:my_name))).to eq(Set.new(["bob","jane","joe"]))
186
+ expect(subgraph.edges.length).to eq(2)
187
+ expect(subgraph.edges[0].properties).to eq({name: "father of"})
188
+ expect(subgraph.edges[1].properties).to eq({})
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+
195
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,3 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
- require 'simplecov'
4
-
5
- SimpleCov.start
6
-
7
3
  require 'dag'
8
-
9
-
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
5
- prerelease:
4
+ version: 0.0.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - Kevin Rutherford
@@ -14,59 +13,39 @@ dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rake
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
- version: 10.0.1
19
+ version: '10'
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: 10.0.1
26
+ version: '10'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rspec
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ~>
31
+ - - "~>"
36
32
  - !ruby/object:Gem::Version
37
- version: '2.12'
33
+ version: '3'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ~>
38
+ - - "~>"
44
39
  - !ruby/object:Gem::Version
45
- version: '2.12'
46
- - !ruby/object:Gem::Dependency
47
- name: simplecov
48
- requirement: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ! '>='
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: '0'
40
+ version: '3'
62
41
  description: Directed acyclic graphs
63
42
  email: kevin@rutherford-software.com
64
43
  executables: []
65
44
  extensions: []
66
45
  extra_rdoc_files: []
67
46
  files:
68
- - .rspec
69
- - .yardopts
47
+ - ".rspec"
48
+ - ".yardopts"
70
49
  - Gemfile
71
50
  - Gemfile.lock
72
51
  - README.md
@@ -78,27 +57,26 @@ files:
78
57
  - spec/spec_helper.rb
79
58
  homepage: http://github.com/kevinrutherford/dag
80
59
  licenses: []
60
+ metadata: {}
81
61
  post_install_message:
82
62
  rdoc_options: []
83
63
  require_paths:
84
64
  - lib
85
65
  required_ruby_version: !ruby/object:Gem::Requirement
86
- none: false
87
66
  requirements:
88
- - - ! '>='
67
+ - - ">="
89
68
  - !ruby/object:Gem::Version
90
69
  version: '0'
91
70
  required_rubygems_version: !ruby/object:Gem::Requirement
92
- none: false
93
71
  requirements:
94
- - - ! '>='
72
+ - - ">="
95
73
  - !ruby/object:Gem::Version
96
74
  version: '0'
97
75
  requirements: []
98
76
  rubyforge_project:
99
- rubygems_version: 1.8.24
77
+ rubygems_version: 2.2.2
100
78
  signing_key:
101
- specification_version: 3
79
+ specification_version: 4
102
80
  summary: Directed acyclic graphs
103
81
  test_files:
104
82
  - spec/lib/dag/vertex_spec.rb