dag 0.0.1

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.
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ -Ilib
3
+
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
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 ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dag (0.0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
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)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ dag!
30
+ rake (~> 10.0.1)
31
+ rspec (~> 2.12)
32
+ simplecov
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # DAG
2
+
3
+ Very simple directed acyclic graphs for Ruby.
4
+
5
+ ## Installation
6
+
7
+ Install the gem
8
+
9
+ ```
10
+ gem install dag
11
+ ```
12
+
13
+ Or add it to your Gemfile and run `bundle`.
14
+
15
+ ``` ruby
16
+ gem 'dag'
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ dag = DAG.new
23
+
24
+ v1 = dag.add_vertex
25
+ v2 = dag.add_vertex
26
+ v3 = dag.add_vertex
27
+
28
+ dag.add_edge from: v1, to: v2
29
+ dag.add_edge from: v2, to: v3
30
+
31
+ v1.has_path_to?(v3) # => true
32
+ v3.has_path_to?(v1) # => false
33
+
34
+ dag.add_edge from: v3, to: v1 # => ArgumentError: A DAG must not have cycles
35
+
36
+ dag.add_edge from: v1, to: v2
37
+ dag.add_edge from: v1, to: v3
38
+ v1.successors # => [v2, v3]
39
+ ```
40
+
41
+ See the specs for more detailed usage scenarios.
42
+
43
+ ## Compatibility
44
+
45
+ Tested with Ruby 1.9.x, JRuby, Rubinius.
46
+ See the [build status](https://travis-ci.org/kevinrutherford/dag)
47
+ for details.
48
+
49
+ ## License
50
+
51
+ (The MIT License)
52
+
53
+ Copyright (c) 2013 Kevin Rutherford
54
+
55
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
56
+ this software and associated documentation files (the 'Software'), to deal in
57
+ the Software without restriction, including without limitation the rights to
58
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
59
+ of the Software, and to permit persons to whom the Software is furnished to do
60
+ so, subject to the following conditions:
61
+
62
+ The above copyright notice and this permission notice shall be included in all
63
+ copies or substantial portions of the Software.
64
+
65
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
66
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
67
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
68
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
69
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
70
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
71
+ SOFTWARE.
72
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new('spec')
4
+
5
+ task :default => :spec
6
+
data/lib/dag/vertex.rb ADDED
@@ -0,0 +1,34 @@
1
+ class DAG
2
+
3
+ class Vertex
4
+ attr_reader :dag
5
+
6
+ def initialize(dag)
7
+ @dag = dag
8
+ end
9
+
10
+ def outgoing_edges
11
+ @dag.edges.select {|e| e.origin == self}
12
+ end
13
+
14
+ def incoming_edges
15
+ @dag.edges.select {|e| e.destination == self}
16
+ end
17
+
18
+ def predecessors
19
+ incoming_edges.map(&:origin).uniq
20
+ end
21
+
22
+ def successors
23
+ outgoing_edges.map(&:destination).uniq
24
+ end
25
+
26
+ def has_path_to?(other)
27
+ raise ArgumentError.new('You must supply a vertex in this DAG') unless
28
+ other && Vertex === other && other.dag == self.dag
29
+ successors.include?(other) || successors.any? {|v| v.has_path_to?(other) }
30
+ end
31
+ end
32
+
33
+ end
34
+
data/lib/dag.rb ADDED
@@ -0,0 +1,31 @@
1
+ require_relative 'dag/vertex'
2
+
3
+ class DAG
4
+
5
+ Edge = Struct.new(:origin, :destination)
6
+
7
+ attr_reader :vertices, :edges
8
+
9
+ def initialize
10
+ @vertices = []
11
+ @edges = []
12
+ end
13
+
14
+ def add_vertex
15
+ Vertex.new(self).tap {|v| @vertices << v }
16
+ end
17
+
18
+ def add_edge(attrs)
19
+ origin = attrs[:origin] || attrs[:source] || attrs[:from] || attrs[:start]
20
+ destination = attrs[:destination] || attrs[:sink] || attrs[:to] || attrs[:end]
21
+ raise ArgumentError.new('Origin must be a vertex in this DAG') unless
22
+ origin && Vertex === origin && origin.dag == self
23
+ raise ArgumentError.new('Destination must be a vertex in this DAG') unless
24
+ destination && Vertex === destination && destination.dag == self
25
+ raise ArgumentError.new('A DAG must not have cycles') if origin == destination
26
+ raise ArgumentError.new('A DAG must not have cycles') if destination.has_path_to?(origin)
27
+ Edge.new(origin, destination).tap {|e| @edges << e }
28
+ end
29
+
30
+ end
31
+
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe DAG::Vertex do
4
+ let(:dag) { DAG.new }
5
+ subject { dag.add_vertex }
6
+ let(:v1) { dag.add_vertex }
7
+ let(:v2) { dag.add_vertex }
8
+
9
+ describe 'an instance' do
10
+ it 'cannot have a path to a non-vertex' do
11
+ expect { subject.has_path_to?(23) }.to raise_error(ArgumentError)
12
+ end
13
+
14
+ it 'cannot have a path to a vertex in a different DAG' do
15
+ expect { subject.has_path_to?(DAG.new.add_vertex) }.to raise_error(ArgumentError)
16
+ end
17
+ end
18
+
19
+ context 'with predecessors' do
20
+ before do
21
+ dag.add_edge from: v1, to: subject
22
+ dag.add_edge from: v2, to: subject
23
+ end
24
+
25
+ its(:predecessors) { should =~ [v1, v2] }
26
+ its(:successors) { should == [] }
27
+
28
+ it 'has no paths to its predecessors' do
29
+ subject.has_path_to?(v1).should be_false
30
+ subject.has_path_to?(v2).should be_false
31
+ end
32
+
33
+ context 'with multiple paths' do
34
+ it 'lists each predecessor only once' do
35
+ dag.add_edge from: v1, to: subject
36
+ subject.predecessors.should == [v1, v2]
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'with successors' do
42
+ before do
43
+ dag.add_edge from: subject, to: v1
44
+ dag.add_edge from: subject, to: v2
45
+ end
46
+
47
+ its(:predecessors) { should == [] }
48
+ its(:successors) { should =~ [v1, v2] }
49
+
50
+ it 'has paths to its successors' do
51
+ subject.has_path_to?(v1).should be_true
52
+ subject.has_path_to?(v2).should be_true
53
+ end
54
+
55
+ context 'with multiple paths' do
56
+ it 'lists each successor only once' do
57
+ dag.add_edge from: subject, to: v1
58
+ subject.successors.should == [v1, v2]
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'in a deep DAG' do
64
+ let(:v3) { dag.add_vertex }
65
+
66
+ before do
67
+ dag.add_edge from: subject, to: v1
68
+ dag.add_edge from: v1, to: v2
69
+ end
70
+
71
+ it 'has a deep path to v2' do
72
+ subject.has_path_to?(v2).should be_true
73
+ end
74
+
75
+ it 'has no path to v3' do
76
+ subject.has_path_to?(v3).should be_false
77
+ end
78
+ end
79
+
80
+ end
81
+
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe DAG do
4
+
5
+ context 'when new' do
6
+ it 'starts with no vertices' do
7
+ subject.vertices.should be_empty
8
+ end
9
+ end
10
+
11
+ context 'with one vertex' do
12
+ it 'has only that vertex' do
13
+ v = subject.add_vertex
14
+ subject.vertices.should == [v]
15
+ end
16
+
17
+ it 'has no edges' do
18
+ v = subject.add_vertex
19
+ v.outgoing_edges.should be_empty
20
+ v.incoming_edges.should be_empty
21
+ end
22
+ end
23
+
24
+ context 'creating an edge' do
25
+ let(:v1) { subject.add_vertex }
26
+
27
+ context 'when valid' do
28
+ let(:v2) { subject.add_vertex }
29
+ let!(:e1) { subject.add_edge(origin: v1, destination: v2) }
30
+
31
+ it 'leaves the origin vertex' do
32
+ v1.outgoing_edges.should == [e1]
33
+ end
34
+
35
+ it 'arrives at the destination vertex' do
36
+ v2.incoming_edges.should == [e1]
37
+ end
38
+
39
+ it 'adds no other edges' do
40
+ v1.incoming_edges.should be_empty
41
+ v2.outgoing_edges.should be_empty
42
+ end
43
+
44
+ it 'allows multiple edges between a pair of vertices' do
45
+ expect { subject.add_edge(origin: v1, destination: v2) }.to_not raise_error
46
+ end
47
+ end
48
+
49
+ context 'when invalid' do
50
+ it 'requires an origin vertex' do
51
+ expect { subject.add_edge(to: v1) }.to raise_error(ArgumentError)
52
+ end
53
+
54
+ it 'requires a destination vertex' do
55
+ expect { subject.add_edge(from: v1) }.to raise_error(ArgumentError)
56
+ end
57
+
58
+ it 'requires the endpoints to be vertices' do
59
+ expect { subject.add_edge(from: v1, to: 23) }.to raise_error(ArgumentError)
60
+ expect { subject.add_edge(from: 45, to: v1) }.to raise_error(ArgumentError)
61
+ end
62
+
63
+ it 'requires the endpoints to be in the same DAG' do
64
+ v2 = DAG.new.add_vertex
65
+ expect { subject.add_edge(from: v1, to: v2) }.to raise_error(ArgumentError)
66
+ expect { subject.add_edge(from: v2, to: v1) }.to raise_error(ArgumentError)
67
+ end
68
+
69
+ it 'rejects an edge that would create a loop' do
70
+ v2 = subject.add_vertex
71
+ v3 = subject.add_vertex
72
+ v4 = subject.add_vertex
73
+ subject.add_edge from: v1, to: v2
74
+ subject.add_edge from: v2, to: v3
75
+ subject.add_edge from: v3, to: v4
76
+ expect { subject.add_edge from: v4, to: v1 }.to raise_error(ArgumentError)
77
+ end
78
+
79
+ it 'rejects an edge from a vertex to itself' do
80
+ expect { subject.add_edge from: v1, to: v1 }.to raise_error(ArgumentError)
81
+ end
82
+ end
83
+
84
+ context 'with different keywords' do
85
+ let(:v1) { subject.add_vertex }
86
+ let(:v2) { subject.add_vertex }
87
+ let!(:e1) { subject.add_edge(origin: v1, destination: v2) }
88
+
89
+ it 'allows :source and :sink' do
90
+ subject.add_edge(source: v1, sink: v2).should == e1
91
+ end
92
+
93
+ it 'allows :from and :to' do
94
+ subject.add_edge(from: v1, to: v2).should == e1
95
+ end
96
+
97
+ it 'allows :start and :end' do
98
+ subject.add_edge(start: v1, end: v2).should == e1
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+
105
+ context 'vertex attributes'
106
+
107
+ context 'edge attributes'
108
+
109
+ end
110
+
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'simplecov'
4
+
5
+ SimpleCov.start
6
+
7
+ require 'dag'
8
+
9
+
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dag
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kevin Rutherford
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 10.0.1
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 10.0.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.12'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !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'
62
+ description: Directed acyclic graphs
63
+ email: kevin@rutherford-software.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - .rspec
69
+ - Gemfile
70
+ - Gemfile.lock
71
+ - README.md
72
+ - Rakefile
73
+ - lib/dag.rb
74
+ - lib/dag/vertex.rb
75
+ - spec/lib/dag/vertex_spec.rb
76
+ - spec/lib/dag_spec.rb
77
+ - spec/spec_helper.rb
78
+ homepage: http://github.com/kevinrutherford/dag
79
+ licenses: []
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 1.8.24
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Directed acyclic graphs
102
+ test_files:
103
+ - spec/lib/dag/vertex_spec.rb
104
+ - spec/lib/dag_spec.rb
105
+ - spec/spec_helper.rb