dag 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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