abuelo 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/CHANGELOG.md +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +115 -9
- data/Rakefile +2 -2
- data/abuelo.gemspec +8 -8
- data/lib/abuelo.rb +1 -1
- data/lib/abuelo/edge.rb +19 -21
- data/lib/abuelo/exceptions/exceptions.rb +1 -3
- data/lib/abuelo/graph.rb +116 -21
- data/lib/abuelo/node.rb +15 -8
- data/lib/abuelo/version.rb +2 -2
- data/spec/edge_spec.rb +7 -8
- data/spec/graph_spec.rb +319 -74
- data/spec/node_spec.rb +18 -1
- data/spec/spec_helper.rb +2 -2
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fec3a84e6365d1dabdbb0a97dfaae5c5f616dfe
|
4
|
+
data.tar.gz: d687748513f8e7aabb0eae7fbfc186949dc61334
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edca527570d0e4775ebaf20458dfdfb393c3ba54e0d250eaccae09d9476d68e249c62dd7620458824d4030b0d1956e4eb09b7da4d30d7704240116d20d8b2c3f
|
7
|
+
data.tar.gz: d7bc96b07f9ded38385e0a24a1201edc4f7ce6788596c79bbf3ee7ce99c580e274c916ad972a98e2e12940f47c2f9195e8615ec3cc2e7f2ae06756c106ef71ab
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# Abuelo
|
2
|
-
|
2
|
+
[![Build Status](https://travis-ci.org/dirkholzapfel/abuelo.svg?branch=master)](https://travis-ci.org/dirkholzapfel/abuelo)
|
3
|
+
|
4
|
+
Abuelo is a graph theory library written in Ruby that allows you to build a representation of a graph.
|
5
|
+
|
6
|
+
A graph consists of nodes (= vertices, points) and edges (= lines, arcs). The graph may be undirected or directed. For the sake of simplicity Abuelo sticks with the same vocabulary (nodes, edges) for directed and undirected graphs in contrast to theoretical graph theory.
|
7
|
+
|
8
|
+
Abuelo supports Ruby >= 2.0.0
|
3
9
|
|
4
10
|
## Examples
|
11
|
+
### Undirected graph
|
5
12
|
```ruby
|
6
13
|
graph = Abuelo::Graph.new
|
7
14
|
|
@@ -17,15 +24,109 @@ graph.add_node(node_1)
|
|
17
24
|
.add_edge(edge_1)
|
18
25
|
.add_edge(edge_2)
|
19
26
|
|
20
|
-
graph.
|
21
|
-
graph.
|
22
|
-
graph.
|
23
|
-
graph.
|
24
|
-
graph.
|
25
|
-
graph.
|
26
|
-
graph.
|
27
|
+
graph.order # => 3
|
28
|
+
graph.size # => 2
|
29
|
+
graph.nodes # => [node_1, node_2, node_3]
|
30
|
+
graph.has_node?(node_1) # => true
|
31
|
+
graph.has_node_with_name?('foo') # => false
|
32
|
+
graph.find_node_by_name('node 1') # => node_1
|
33
|
+
graph.edges # => [[edge_1, edge_1.symmetric], [edge_2, edge_2.symmetric]]
|
34
|
+
graph.has_edge?(edge_1) # => true
|
35
|
+
graph.has_edge?(edge_1.symmetric) # => true
|
36
|
+
graph.find_edge(node_1, node_2) # => edge_1
|
37
|
+
graph.find_edge(node_2, node_1) # => edge_1.symmetric
|
38
|
+
graph.edges_for_node(node_2) # => [edge_1.symmetric, edge_2]
|
39
|
+
|
40
|
+
node_1.edges # => [edge_1]
|
41
|
+
node_1.neighbours # => [node_2]
|
27
42
|
```
|
28
43
|
|
44
|
+
### Directed graph
|
45
|
+
```ruby
|
46
|
+
graph = Abuelo::Graph.new(directed: true)
|
47
|
+
|
48
|
+
node_1 = Abuelo::Node.new('node 1')
|
49
|
+
node_2 = Abuelo::Node.new('node 2')
|
50
|
+
node_3 = Abuelo::Node.new('node 3')
|
51
|
+
edge_1 = Abuelo::Edge.new(node_1, node_2, 42)
|
52
|
+
edge_2 = Abuelo::Edge.new(node_2, node_3, 23)
|
53
|
+
|
54
|
+
graph.add_node(node_1)
|
55
|
+
.add_node(node_2)
|
56
|
+
.add_node(node_3)
|
57
|
+
.add_edge(edge_1)
|
58
|
+
.add_edge(edge_2)
|
59
|
+
|
60
|
+
graph.order # => 3
|
61
|
+
graph.size # => 2
|
62
|
+
graph.nodes # => [node_1, node_2, node_3]
|
63
|
+
graph.has_node?(node_1) # => true
|
64
|
+
graph.has_node_with_name?('foo') # => false
|
65
|
+
graph.find_node_by_name('node 1') # => node_1
|
66
|
+
graph.edges # => [edge_1, edge_2]
|
67
|
+
graph.has_edge?(edge_1) # => true
|
68
|
+
graph.has_edge?(edge_1.symmetric) # => false
|
69
|
+
graph.find_edge(node_1, node_2) # => edge_1
|
70
|
+
graph.find_edge(node_2, node_1) # => nil
|
71
|
+
graph.edges_for_node(node_2) # => [edge_2]
|
72
|
+
|
73
|
+
node_1.edges # => [edge_1]
|
74
|
+
node_1.neighbours # => [node_2]
|
75
|
+
```
|
76
|
+
|
77
|
+
### Initialize a graph with an adjacency matrix
|
78
|
+
The above, object oriented way to build graphs is the recommended way to work with this library.
|
79
|
+
But you can also build a graph with an [adjacency matrix](https://en.wikipedia.org/wiki/Adjacency_matrix).
|
80
|
+
That is a nice shortcut used in some tests and may be a good alternative if you use Abuelo in the console to play around.
|
81
|
+
|
82
|
+
A zero indicates there is no edge between the nodes, an Integer indicates that there is an edge with the given weight between the nodes. The nodes are automatically named, starting with "node 1".
|
83
|
+
The above example can be built like this:
|
84
|
+
|
85
|
+
#### Undirected graphs
|
86
|
+
Be aware that you have to provide all symmetric edges in an adjacency matrix for an undirected graph - the lib does not add them automatically as it happens with the `.add_edge` method.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
adjacency_matrix = <<-matrix
|
90
|
+
0 42 0
|
91
|
+
42 0 23
|
92
|
+
0 23 0
|
93
|
+
matrix
|
94
|
+
|
95
|
+
# The above matrix corresponds to this internal representation
|
96
|
+
#
|
97
|
+
# | node 1 | node 2 | node 3 |
|
98
|
+
# ------------------------------------
|
99
|
+
# node 1 | 0 | 42 | 0 |
|
100
|
+
# node 2 | 42 | 0 | 23 |
|
101
|
+
# node 3 | 0 | 23 | 0 |
|
102
|
+
|
103
|
+
graph = Abuelo::Graph.new(adjacency_matrix: adjacency_matrix)
|
104
|
+
node_1 = graph.find_node_by_name('node 1')
|
105
|
+
node_2 = graph.find_node_by_name('node 2')
|
106
|
+
graph.find_edge(node_1, node_2).weight # => 42
|
107
|
+
```
|
108
|
+
#### Directed graphs
|
109
|
+
```ruby
|
110
|
+
adjacency_matrix = <<-matrix
|
111
|
+
0 42 0
|
112
|
+
0 0 23
|
113
|
+
0 0 0
|
114
|
+
matrix
|
115
|
+
|
116
|
+
# The above matrix corresponds to this internal representation
|
117
|
+
#
|
118
|
+
# | node 1 | node 2 | node 3 |
|
119
|
+
# ------------------------------------
|
120
|
+
# node 1 | 0 | 42 | 0 |
|
121
|
+
# node 2 | 0 | 0 | 23 |
|
122
|
+
# node 3 | 0 | 0 | 0 |
|
123
|
+
|
124
|
+
graph = Abuelo::Graph.new(adjacency_matrix: adjacency_matrix, directed: true)
|
125
|
+
```
|
126
|
+
|
127
|
+
## Documentation
|
128
|
+
[YARD](http://yardoc.org) documentation is available at [rubydoc](http://www.rubydoc.info/gems/abuelo).
|
129
|
+
|
29
130
|
## Future
|
30
131
|
* Implement graph algorithms
|
31
132
|
* [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm)
|
@@ -49,4 +150,9 @@ Dirk Holzapfel
|
|
49
150
|
|
50
151
|
[cachezero.net](http://cachezero.net)
|
51
152
|
|
52
|
-
[
|
153
|
+
[Abuelo](http://www.ronabuelopanama.com)
|
154
|
+
|
155
|
+
### Contributors
|
156
|
+
[dirkholzapfel](https://github.com/dirkholzapfel),
|
157
|
+
[mbirman](https://github.com/mbirman),
|
158
|
+
[sergey-kintsel](https://github.com/sergey-kintsel)
|
data/Rakefile
CHANGED
data/abuelo.gemspec
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
2
|
require 'abuelo/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |spec|
|
5
5
|
spec.name = 'abuelo'
|
6
6
|
spec.version = Abuelo::VERSION
|
7
7
|
spec.date = '2016-01-10'
|
8
|
-
spec.summary =
|
9
|
-
spec.description =
|
10
|
-
spec.authors = [
|
11
|
-
spec.email = '
|
8
|
+
spec.summary = 'Abuelo'
|
9
|
+
spec.description = 'Abuelo is a graph theory library.'
|
10
|
+
spec.authors = ['Dirk Holzapfel']
|
11
|
+
spec.email = 'cache.zero@mailbox.org'
|
12
12
|
spec.homepage =
|
13
13
|
'http://github.com/dirkholzapfel/abuelo'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split("\n")
|
17
17
|
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
-
spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
-
spec.require_paths = [
|
18
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
# Development
|
22
22
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
@@ -25,4 +25,4 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
# Testing
|
27
27
|
spec.add_development_dependency 'rspec'
|
28
|
-
end
|
28
|
+
end
|
data/lib/abuelo.rb
CHANGED
data/lib/abuelo/edge.rb
CHANGED
@@ -3,16 +3,15 @@ module Abuelo
|
|
3
3
|
# Class Edge provides a representation of an edge.
|
4
4
|
# An edge connects two nodes(start-node and end-node) and has a weight.
|
5
5
|
#
|
6
|
-
# @author Dirk Holzapfel <
|
6
|
+
# @author Dirk Holzapfel <cache.zero@mailbox.org>
|
7
7
|
#
|
8
8
|
class Edge
|
9
|
-
|
10
9
|
# @return [Abuelo::Node] start-node
|
11
10
|
attr_reader :node_1
|
12
11
|
|
13
12
|
# @return [Abuelo::Node] end-node
|
14
13
|
attr_reader :node_2
|
15
|
-
|
14
|
+
|
16
15
|
# @return [Numeric] weight
|
17
16
|
attr_reader :weight
|
18
17
|
|
@@ -22,8 +21,8 @@ module Abuelo
|
|
22
21
|
# @param [Abuelo::Node] node_1 start-node
|
23
22
|
# @param [Abuelo::Node] node_2 end-node
|
24
23
|
# @param [Numeric] weight of the edge
|
25
|
-
#
|
26
|
-
def initialize(node_1, node_2, weight =
|
24
|
+
#
|
25
|
+
def initialize(node_1, node_2, weight = 1)
|
27
26
|
@node_1 = node_1
|
28
27
|
@node_2 = node_2
|
29
28
|
@weight = weight
|
@@ -31,41 +30,40 @@ module Abuelo
|
|
31
30
|
|
32
31
|
#
|
33
32
|
# @return [Abuelo::Edge] a new edge with same weight but reversed start- and end-node.
|
34
|
-
#
|
35
|
-
def
|
33
|
+
#
|
34
|
+
def symmetric
|
36
35
|
Abuelo::Edge.new(node_2, node_1, weight)
|
37
36
|
end
|
38
37
|
|
39
38
|
#
|
40
39
|
# Comparison based on weight.
|
41
40
|
#
|
42
|
-
# @param [Abuelo::Edge]
|
41
|
+
# @param [Abuelo::Edge] other
|
42
|
+
#
|
43
|
+
# @return [-1, 0, +1]
|
43
44
|
#
|
44
|
-
|
45
|
-
|
46
|
-
def <=>(other_edge)
|
47
|
-
self.weight <=> other_edge.weight
|
45
|
+
def <=>(other)
|
46
|
+
weight <=> other.weight
|
48
47
|
end
|
49
48
|
|
50
49
|
#
|
51
50
|
# Equality check.
|
52
51
|
#
|
53
|
-
# @param [Abuelo::Edge]
|
52
|
+
# @param [Abuelo::Edge] other
|
54
53
|
#
|
55
54
|
# @return [Boolean] true if start-, end-node and weight of both edges are equal
|
56
|
-
#
|
57
|
-
def ==(
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
#
|
56
|
+
def ==(other)
|
57
|
+
node_1 == other.node_1 &&
|
58
|
+
node_2 == other.node_2 &&
|
59
|
+
weight == other.weight
|
61
60
|
end
|
62
61
|
|
63
62
|
#
|
64
63
|
# @return [String] human readable representation of edge
|
65
|
-
#
|
64
|
+
#
|
66
65
|
def to_s
|
67
|
-
"#{node_1
|
66
|
+
"#{node_1} -> #{node_2} with weight #{weight}"
|
68
67
|
end
|
69
|
-
|
70
68
|
end
|
71
69
|
end
|
data/lib/abuelo/graph.rb
CHANGED
@@ -1,17 +1,64 @@
|
|
1
1
|
module Abuelo
|
2
2
|
#
|
3
3
|
# Class Graph provides a representation of a directed graph.
|
4
|
+
#
|
4
5
|
# A graph consists of nodes (= vertices, points) and edges (= lines, arcs).
|
6
|
+
# The graph may be undirected or directed. For the sake of simplicity Abuelo sticks
|
7
|
+
# with the same vocabulary (nodes, edges) for directed and undirected graphs in
|
8
|
+
# contrast to theoretical graph theory.
|
5
9
|
#
|
6
|
-
# @author Dirk Holzapfel <
|
10
|
+
# @author Dirk Holzapfel <cache.zero@mailbox.org>
|
7
11
|
#
|
8
12
|
class Graph
|
9
|
-
|
10
|
-
|
13
|
+
#
|
14
|
+
# @param [Hash] opts the options to create a graph with
|
15
|
+
# @option opts [Boolean] :directed defines if the graph is directed or undirected.
|
16
|
+
# (defaults to false == undirected)
|
17
|
+
# @option opts [String] :adjacency_matrix a representation of the graph in form
|
18
|
+
# of an adjacency matrix
|
19
|
+
#
|
20
|
+
# @example Build a graph with the help of an adjacency matrix
|
21
|
+
# adjacency_matrix = <<-matrix
|
22
|
+
# 0 42 0
|
23
|
+
# 42 0 23
|
24
|
+
# 0 23 0
|
25
|
+
# matrix
|
26
|
+
# graph = Abuelo::Graph.new(adjacency_matrix: adjacency_matrix)
|
27
|
+
#
|
28
|
+
def initialize(opts = {})
|
11
29
|
@nodes = {} # @nodes = { node_name => node_object }
|
12
30
|
@edges = {} # @edges = { node_object => { node_object => edge }}
|
31
|
+
@directed = opts.fetch(:directed, false)
|
32
|
+
init_by_adjacency_matrix(opts[:adjacency_matrix]) if opts[:adjacency_matrix]
|
13
33
|
end
|
14
34
|
|
35
|
+
#
|
36
|
+
# @return [Boolean] true if the graph is directed, false if it is undirected
|
37
|
+
#
|
38
|
+
def directed?
|
39
|
+
@directed
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @return [Boolean] true if the graph is undirected, false if it is directed
|
44
|
+
#
|
45
|
+
def undirected?
|
46
|
+
!directed?
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# @return [Integer] the order of the graph == the number of nodes
|
51
|
+
#
|
52
|
+
def order
|
53
|
+
nodes.count
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# @return [Integer] the size of the graph == the number of edges
|
58
|
+
#
|
59
|
+
def size
|
60
|
+
edges.count
|
61
|
+
end
|
15
62
|
|
16
63
|
#
|
17
64
|
# @return [Array<Abuelo::Node>] list of nodes of the graph
|
@@ -29,12 +76,13 @@ module Abuelo
|
|
29
76
|
#
|
30
77
|
# @raise [Abuelo::Exceptions::NodeAlreadyExistsError] if the node is
|
31
78
|
# already contained in the graph
|
32
|
-
#
|
79
|
+
#
|
33
80
|
def add_node(node)
|
34
81
|
raise Abuelo::Exceptions::NodeAlreadyExistsError if has_node?(node)
|
35
82
|
|
36
83
|
@nodes[node.name] = node
|
37
84
|
node.graph = self
|
85
|
+
|
38
86
|
self
|
39
87
|
end
|
40
88
|
|
@@ -44,7 +92,7 @@ module Abuelo
|
|
44
92
|
# @param [Abuelo::Node] node
|
45
93
|
#
|
46
94
|
# @return [Boolean]
|
47
|
-
#
|
95
|
+
#
|
48
96
|
def has_node?(node)
|
49
97
|
has_node_with_name?(node.name)
|
50
98
|
end
|
@@ -55,22 +103,46 @@ module Abuelo
|
|
55
103
|
# @param [String] name of the node
|
56
104
|
#
|
57
105
|
# @return [Boolean]
|
58
|
-
#
|
106
|
+
#
|
59
107
|
def has_node_with_name?(name)
|
60
|
-
|
108
|
+
!find_node_by_name(name).nil?
|
61
109
|
end
|
62
110
|
|
111
|
+
#
|
112
|
+
# Returns the node with the given name if it is contained in the graph.
|
113
|
+
#
|
114
|
+
# @param [String] name of the node
|
115
|
+
#
|
116
|
+
# @return [Abuelo::Node, nil] the node if found, otherwise nil
|
117
|
+
#
|
118
|
+
def find_node_by_name(name)
|
119
|
+
@nodes[name]
|
120
|
+
end
|
63
121
|
|
64
122
|
#
|
65
|
-
# @return [Array<Abuelo::Edge>]
|
66
|
-
#
|
123
|
+
# @return [Array<Abuelo::Edge>, Array<Array(Abuelo::Edge, Abuelo::Edge)>]
|
124
|
+
# list of edges of the graph if directed,
|
125
|
+
# list of list of symmetric pairs of edges of the graph if undirected
|
126
|
+
#
|
127
|
+
# @example directed graph
|
128
|
+
# "graph.edges" #=> [edge_from_node_1_to_node_2]
|
129
|
+
#
|
130
|
+
# @example undirected graph
|
131
|
+
# "graph.edges" #=> [[edge_from_node_1_to_node_2, edge_from_node_2_to_node_1]]
|
132
|
+
#
|
67
133
|
def edges
|
68
|
-
edges = @edges.keys.
|
69
|
-
|
134
|
+
edges = @edges.keys.flat_map { |key| @edges[key].values }
|
135
|
+
|
136
|
+
if directed?
|
137
|
+
edges
|
138
|
+
else
|
139
|
+
edges.each_slice(2).to_a
|
140
|
+
end
|
70
141
|
end
|
71
142
|
|
72
143
|
#
|
73
144
|
# Adds an edge to the graph.
|
145
|
+
# Auto-adds the symmetric counterpart if graph is undirected.
|
74
146
|
#
|
75
147
|
# @param [Abuelo::Edge] edge to add
|
76
148
|
#
|
@@ -78,12 +150,17 @@ module Abuelo
|
|
78
150
|
#
|
79
151
|
# @raise [Abuelo::Exceptions::EdgeAlreadyExistsError] if the edge is
|
80
152
|
# already contained in the graph
|
81
|
-
#
|
82
|
-
def add_edge(edge)
|
153
|
+
#
|
154
|
+
def add_edge(edge, opts = {})
|
83
155
|
raise Abuelo::Exceptions::EdgeAlreadyExistsError if has_edge?(edge)
|
84
156
|
|
85
157
|
@edges[edge.node_1] ||= {}
|
86
158
|
@edges[edge.node_1][edge.node_2] = edge
|
159
|
+
|
160
|
+
if undirected? && !opts[:symmetric]
|
161
|
+
add_edge(edge.symmetric, symmetric: true)
|
162
|
+
end
|
163
|
+
|
87
164
|
self
|
88
165
|
end
|
89
166
|
|
@@ -93,19 +170,19 @@ module Abuelo
|
|
93
170
|
# @param [Abuelo::Edge] edge
|
94
171
|
#
|
95
172
|
# @return [Boolean]
|
96
|
-
#
|
173
|
+
#
|
97
174
|
def has_edge?(edge)
|
98
|
-
find_edge(edge.node_1, edge.node_2)
|
175
|
+
!find_edge(edge.node_1, edge.node_2).nil?
|
99
176
|
end
|
100
177
|
|
101
178
|
#
|
102
|
-
#
|
179
|
+
# Returns the edge if there is one between the two given nodes.
|
103
180
|
#
|
104
181
|
# @param [Abuelo::Node] node_1
|
105
182
|
# @param [Abuelo::Node] node_2
|
106
183
|
#
|
107
184
|
# @return [Abuelo::Edge, nil] the edge if found, otherwise nil
|
108
|
-
#
|
185
|
+
#
|
109
186
|
def find_edge(node_1, node_2)
|
110
187
|
@edges[node_1][node_2] if @edges[node_1]
|
111
188
|
end
|
@@ -116,12 +193,30 @@ module Abuelo
|
|
116
193
|
# @param [Abuelo::Node] node <description>
|
117
194
|
#
|
118
195
|
# @return [Array<Abuelo::Edge>] list of edges that start from the given node
|
119
|
-
#
|
196
|
+
#
|
120
197
|
def edges_for_node(node)
|
121
|
-
edges
|
122
|
-
edges += @edges[node].values.to_a if @edges[node]
|
123
|
-
edges
|
198
|
+
Hash(@edges[node]).values
|
124
199
|
end
|
125
200
|
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def init_by_adjacency_matrix(adjacency_matrix)
|
205
|
+
nodes = {}
|
206
|
+
1.upto(adjacency_matrix.lines.count) do |i|
|
207
|
+
node = Abuelo::Node.new("node #{i}")
|
208
|
+
nodes[i] = node
|
209
|
+
add_node(node)
|
210
|
+
end
|
211
|
+
|
212
|
+
adjacency_matrix.split(/\r?\n/).each_with_index do |row, row_index|
|
213
|
+
row.split(' ').map(&:to_i).each_with_index do |weight, column_index|
|
214
|
+
if weight != 0
|
215
|
+
edge = Abuelo::Edge.new(nodes[row_index+1], nodes[column_index+1], weight)
|
216
|
+
add_edge(edge, symmetric: true)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
126
221
|
end
|
127
222
|
end
|
data/lib/abuelo/node.rb
CHANGED
@@ -3,7 +3,7 @@ module Abuelo
|
|
3
3
|
# Class Node provides a representation of a node.
|
4
4
|
# A node has a name. Any object may be attached on initialization.
|
5
5
|
#
|
6
|
-
# @author Dirk Holzapfel <
|
6
|
+
# @author Dirk Holzapfel <cache.zero@mailbox.org>
|
7
7
|
#
|
8
8
|
class Node
|
9
9
|
# @return [String] name
|
@@ -21,7 +21,7 @@ module Abuelo
|
|
21
21
|
# @param [String] name of the node
|
22
22
|
# @param [Object] object to attach to the node.
|
23
23
|
# This is useful on some algorithm implementations.
|
24
|
-
#
|
24
|
+
#
|
25
25
|
def initialize(name, object = nil)
|
26
26
|
@name = name
|
27
27
|
@object = object
|
@@ -29,14 +29,21 @@ module Abuelo
|
|
29
29
|
|
30
30
|
#
|
31
31
|
# @return [Array<Abuelo::Edge>] list of edges starting from the node
|
32
|
-
#
|
32
|
+
#
|
33
33
|
def edges
|
34
34
|
graph.edges_for_node(self) if graph
|
35
35
|
end
|
36
36
|
|
37
|
+
#
|
38
|
+
# @return [Array<Abuelo::Node>] list of nodes that are adjacent to the node
|
39
|
+
#
|
40
|
+
def neighbours
|
41
|
+
edges.map(&:node_2)
|
42
|
+
end
|
43
|
+
|
37
44
|
#
|
38
45
|
# @return [String] human readable representation of node
|
39
|
-
#
|
46
|
+
#
|
40
47
|
def to_s
|
41
48
|
@name
|
42
49
|
end
|
@@ -44,12 +51,12 @@ module Abuelo
|
|
44
51
|
#
|
45
52
|
# Equality check.
|
46
53
|
#
|
47
|
-
# @param [Abuelo::Node]
|
54
|
+
# @param [Abuelo::Node] other
|
48
55
|
#
|
49
56
|
# @return [Boolean] true if name is equal
|
50
|
-
#
|
51
|
-
def ==(
|
52
|
-
|
57
|
+
#
|
58
|
+
def ==(other)
|
59
|
+
name == other.name
|
53
60
|
end
|
54
61
|
end
|
55
62
|
end
|
data/lib/abuelo/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Abuelo
|
2
|
-
VERSION =
|
3
|
-
end
|
2
|
+
VERSION = '0.0.2'.freeze
|
3
|
+
end
|
data/spec/edge_spec.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Abuelo::Edge do
|
4
|
-
|
5
4
|
let(:node_1) { Abuelo::Node.new('node 1') }
|
6
5
|
let(:node_2) { Abuelo::Node.new('node 2') }
|
7
6
|
|
8
7
|
let(:edge) { described_class.new(node_1, node_2, 3) }
|
9
8
|
|
10
9
|
context 'initialization' do
|
11
|
-
it 'sets the weight to
|
12
|
-
expect(described_class.new(node_1, node_2).weight).to eq
|
10
|
+
it 'sets the weight to 1 if not given' do
|
11
|
+
expect(described_class.new(node_1, node_2).weight).to eq 1
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
@@ -21,12 +20,12 @@ RSpec.describe Abuelo::Edge do
|
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
|
-
describe '#
|
23
|
+
describe '#symmetric' do
|
25
24
|
it 'returns an edge object with the same weight, but reversed nodes' do
|
26
|
-
|
27
|
-
expect(
|
28
|
-
expect(
|
29
|
-
expect(
|
25
|
+
symmetric = edge.symmetric
|
26
|
+
expect(symmetric.node_1).to eq edge.node_2
|
27
|
+
expect(symmetric.node_2).to eq edge.node_1
|
28
|
+
expect(symmetric.weight).to eq edge.weight
|
30
29
|
end
|
31
30
|
end
|
32
31
|
|
data/spec/graph_spec.rb
CHANGED
@@ -1,130 +1,375 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Abuelo::Graph do
|
4
|
-
|
5
4
|
let(:node_1) { Abuelo::Node.new('node 1') }
|
6
5
|
let(:node_2) { Abuelo::Node.new('node 2') }
|
7
|
-
let(:
|
6
|
+
let(:node_3) { Abuelo::Node.new('node 3') }
|
7
|
+
let(:edge_1) { Abuelo::Edge.new(node_1, node_2, 42) }
|
8
|
+
let(:edge_2) { Abuelo::Edge.new(node_2, node_3, 23) }
|
9
|
+
|
10
|
+
let(:directed_graph) do
|
11
|
+
described_class.new(directed: true)
|
12
|
+
.add_node(node_1)
|
13
|
+
.add_node(node_2)
|
14
|
+
.add_node(node_3)
|
15
|
+
.add_edge(edge_1)
|
16
|
+
.add_edge(edge_2)
|
17
|
+
end
|
8
18
|
|
9
|
-
let(:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
19
|
+
let(:undirected_graph) do
|
20
|
+
described_class.new
|
21
|
+
.add_node(node_1)
|
22
|
+
.add_node(node_2)
|
23
|
+
.add_node(node_3)
|
24
|
+
.add_edge(edge_1)
|
25
|
+
.add_edge(edge_2)
|
14
26
|
end
|
15
27
|
|
16
|
-
context '
|
17
|
-
describe '#
|
18
|
-
it '
|
19
|
-
|
28
|
+
context 'directed graph' do
|
29
|
+
describe '#initialize' do
|
30
|
+
it 'can be initialized with an adjacency matrix with weights' do
|
31
|
+
adjacency_matrix = <<-matrix
|
32
|
+
0 7 9 0 0 14
|
33
|
+
7 0 10 15 0 0
|
34
|
+
9 10 0 11 0 2
|
35
|
+
0 0 11 0 6 0
|
36
|
+
0 0 0 6 0 9
|
37
|
+
0 0 2 0 9 0
|
38
|
+
matrix
|
39
|
+
|
40
|
+
graph = described_class.new(adjacency_matrix: adjacency_matrix, directed: true)
|
41
|
+
node_1 = graph.find_node_by_name('node 1')
|
42
|
+
node_2 = graph.find_node_by_name('node 2')
|
43
|
+
node_4 = graph.find_node_by_name('node 4')
|
44
|
+
|
45
|
+
expect(graph.order).to eq 6
|
46
|
+
expect(graph.size).to eq 16
|
47
|
+
|
48
|
+
expect(graph.has_node_with_name?('node 1')).to be true
|
49
|
+
expect(graph.has_node_with_name?('node 0')).to be false
|
50
|
+
expect(graph.has_node_with_name?('node 7')).to be false
|
51
|
+
|
52
|
+
expect(graph.find_edge(node_1, node_4)).to be nil
|
53
|
+
|
54
|
+
edge_1_2 = graph.find_edge(node_1, node_2)
|
55
|
+
expect(edge_1_2.weight).to eq 7
|
20
56
|
end
|
21
57
|
end
|
22
58
|
|
23
|
-
describe '#
|
24
|
-
it '
|
25
|
-
|
59
|
+
describe '#undirected?' do
|
60
|
+
it 'is false' do
|
61
|
+
expect(directed_graph.undirected?).to be false
|
62
|
+
end
|
63
|
+
end
|
26
64
|
|
27
|
-
|
28
|
-
|
29
|
-
|
65
|
+
describe '#directed?' do
|
66
|
+
it 'is true' do
|
67
|
+
expect(directed_graph.directed?).to be true
|
68
|
+
end
|
69
|
+
end
|
30
70
|
|
31
|
-
|
71
|
+
describe 'order' do
|
72
|
+
it 'returns the count of nodes' do
|
73
|
+
expect(directed_graph.order).to eq 3
|
32
74
|
end
|
75
|
+
end
|
33
76
|
|
34
|
-
|
35
|
-
|
77
|
+
describe 'size' do
|
78
|
+
it 'returns the count of edges' do
|
79
|
+
expect(directed_graph.size).to eq 2
|
80
|
+
end
|
81
|
+
end
|
36
82
|
|
37
|
-
|
38
|
-
|
39
|
-
|
83
|
+
context 'nodes' do
|
84
|
+
describe '#nodes' do
|
85
|
+
it 'returns the nodes of the graph' do
|
86
|
+
expect(directed_graph.nodes).to match_array [node_1, node_2, node_3]
|
87
|
+
end
|
40
88
|
end
|
41
89
|
|
42
|
-
|
43
|
-
|
90
|
+
describe '#add_node(node)' do
|
91
|
+
it 'adds the node to the graph' do
|
92
|
+
new_node = Abuelo::Node.new('node 4')
|
93
|
+
|
94
|
+
expect do
|
95
|
+
directed_graph.add_node(new_node)
|
96
|
+
end.to change { directed_graph.nodes.count }.by 1
|
97
|
+
|
98
|
+
expect(directed_graph.has_node?(new_node)).to be true
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'raises an error if a node with the same name in the graph exists' do
|
102
|
+
new_node = Abuelo::Node.new('node 1')
|
44
103
|
|
45
|
-
|
46
|
-
|
47
|
-
|
104
|
+
expect do
|
105
|
+
directed_graph.add_node(new_node)
|
106
|
+
end.to raise_error(Abuelo::Exceptions::NodeAlreadyExistsError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'sets the graph of the node to itself' do
|
110
|
+
new_node = Abuelo::Node.new('node 4')
|
111
|
+
|
112
|
+
expect do
|
113
|
+
directed_graph.add_node(new_node)
|
114
|
+
end.to change { new_node.graph }.to(directed_graph)
|
115
|
+
end
|
48
116
|
end
|
49
|
-
end
|
50
117
|
|
51
|
-
|
52
|
-
|
53
|
-
|
118
|
+
describe '#has_node?(node)' do
|
119
|
+
it 'returns true if node is in the graph. checks object name' do
|
120
|
+
expect(directed_graph.has_node?(node_1)).to be true
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'returns false if node is not in the graph. checks object name' do
|
124
|
+
new_node = Abuelo::Node.new('node 99')
|
125
|
+
expect(directed_graph.has_node?(new_node)).to be false
|
126
|
+
end
|
54
127
|
end
|
55
128
|
|
56
|
-
|
57
|
-
|
58
|
-
|
129
|
+
describe '#has_node_with_name?(name)' do
|
130
|
+
it 'returns true if a node with the given name is in the graph' do
|
131
|
+
expect(directed_graph.has_node_with_name?('node 1')).to be true
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'returns false if no node with the given name is in the graph' do
|
135
|
+
expect(directed_graph.has_node_with_name?('foo')).to be false
|
136
|
+
end
|
59
137
|
end
|
60
138
|
end
|
61
139
|
|
62
|
-
|
63
|
-
|
64
|
-
|
140
|
+
context 'edges' do
|
141
|
+
describe '#edges' do
|
142
|
+
it 'returns the edges of the graph' do
|
143
|
+
expect(directed_graph.edges).to match_array [edge_1, edge_2]
|
144
|
+
end
|
65
145
|
end
|
66
146
|
|
67
|
-
|
68
|
-
|
147
|
+
describe '#add_edge(edge)' do
|
148
|
+
it 'adds the edge to the graph' do
|
149
|
+
new_edge = Abuelo::Edge.new(node_2, node_1, 23)
|
150
|
+
|
151
|
+
expect do
|
152
|
+
directed_graph.add_edge(new_edge)
|
153
|
+
end.to change { directed_graph.edges.count }.by 1
|
154
|
+
|
155
|
+
expect(directed_graph.has_edge?(new_edge)).to be true
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'raises an error if an edge with the same name nodes in the graph exists' do
|
159
|
+
new_edge = Abuelo::Edge.new(node_1, node_2, 23)
|
160
|
+
|
161
|
+
expect do
|
162
|
+
directed_graph.add_edge(new_edge)
|
163
|
+
end.to raise_error(Abuelo::Exceptions::EdgeAlreadyExistsError)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#edges_for_node(node)' do
|
168
|
+
it 'returns all edges that start from the given node' do
|
169
|
+
expect(directed_graph.edges_for_node(node_1)).to eq [edge_1]
|
170
|
+
expect(directed_graph.edges_for_node(node_2)).to eq [edge_2]
|
171
|
+
expect(directed_graph.edges_for_node(node_3)).to eq []
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe '#has_edge?(edge)' do
|
176
|
+
it 'returns true if there is an edge with similiar nodes in the graph' do
|
177
|
+
expect(directed_graph.has_edge?(edge_1)).to be true
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'does not have a symmetric edge automatically' do
|
181
|
+
expect(directed_graph.has_edge?(edge_1.symmetric)).to be false
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'returns false if there is no edge with similiar nodes in the graph' do
|
185
|
+
other_edge = Abuelo::Edge.new(node_2, node_1, 23)
|
186
|
+
expect(directed_graph.has_edge?(other_edge)).to be false
|
187
|
+
end
|
69
188
|
end
|
70
|
-
end
|
71
|
-
end
|
72
189
|
|
190
|
+
describe '#find_edge(node_1, node_2)' do
|
191
|
+
it 'returns the edge if it can be found in the graph' do
|
192
|
+
expect(directed_graph.find_edge(node_1, node_2)).to eq edge_1
|
193
|
+
end
|
73
194
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
expect(graph.edges).to match_array [edge]
|
195
|
+
it 'returns nil if the edge cannot be found in the graph' do
|
196
|
+
expect(directed_graph.find_edge(node_2, node_1)).to be nil
|
197
|
+
end
|
78
198
|
end
|
79
199
|
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'undirected graph' do
|
203
|
+
describe '#initialize' do
|
204
|
+
it 'can be initialized with an adjacency matrix with weights' do
|
205
|
+
adjacency_matrix = <<-matrix
|
206
|
+
0 7 9 0 0 14
|
207
|
+
7 0 10 15 0 0
|
208
|
+
9 10 0 11 0 2
|
209
|
+
0 15 11 0 6 0
|
210
|
+
0 0 0 6 0 9
|
211
|
+
14 0 2 0 9 0
|
212
|
+
matrix
|
213
|
+
|
214
|
+
graph = described_class.new(adjacency_matrix: adjacency_matrix)
|
215
|
+
node_1 = graph.find_node_by_name('node 1')
|
216
|
+
node_2 = graph.find_node_by_name('node 2')
|
217
|
+
node_4 = graph.find_node_by_name('node 4')
|
80
218
|
|
81
|
-
|
82
|
-
|
83
|
-
new_edge = Abuelo::Edge.new(node_2, node_1, 23)
|
219
|
+
expect(graph.order).to eq 6
|
220
|
+
expect(graph.size).to eq 9
|
84
221
|
|
85
|
-
|
86
|
-
graph.
|
87
|
-
|
222
|
+
expect(graph.has_node_with_name?('node 1')).to be true
|
223
|
+
expect(graph.has_node_with_name?('node 0')).to be false
|
224
|
+
expect(graph.has_node_with_name?('node 7')).to be false
|
88
225
|
|
89
|
-
|
226
|
+
expect(graph.find_edge(node_1, node_4)).to be nil
|
227
|
+
|
228
|
+
edge_1_2 = graph.find_edge(node_1, node_2)
|
229
|
+
expect(edge_1_2.weight).to eq 7
|
90
230
|
end
|
231
|
+
end
|
91
232
|
|
92
|
-
|
93
|
-
|
233
|
+
describe '#undirected?' do
|
234
|
+
it 'is true' do
|
235
|
+
expect(undirected_graph.undirected?).to be true
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '#directed?' do
|
240
|
+
it 'is false' do
|
241
|
+
expect(undirected_graph.directed?).to be false
|
242
|
+
end
|
243
|
+
end
|
94
244
|
|
95
|
-
|
96
|
-
|
97
|
-
|
245
|
+
describe 'order' do
|
246
|
+
it 'returns the count of nodes' do
|
247
|
+
expect(undirected_graph.order).to eq 3
|
98
248
|
end
|
99
249
|
end
|
100
250
|
|
101
|
-
describe '
|
102
|
-
it 'returns
|
103
|
-
expect(
|
104
|
-
expect(graph.edges_for_node(node_2)).to eq []
|
251
|
+
describe 'size' do
|
252
|
+
it 'returns the count of edges' do
|
253
|
+
expect(undirected_graph.size).to eq 2
|
105
254
|
end
|
106
255
|
end
|
107
256
|
|
108
|
-
|
109
|
-
|
110
|
-
|
257
|
+
context 'nodes' do
|
258
|
+
describe '#nodes' do
|
259
|
+
it 'returns the nodes of the graph' do
|
260
|
+
expect(undirected_graph.nodes).to match_array [node_1, node_2, node_3]
|
261
|
+
end
|
111
262
|
end
|
112
263
|
|
113
|
-
|
114
|
-
|
115
|
-
|
264
|
+
describe '#add_node(node)' do
|
265
|
+
it 'adds the node to the graph' do
|
266
|
+
new_node = Abuelo::Node.new('node 4')
|
267
|
+
|
268
|
+
expect do
|
269
|
+
undirected_graph.add_node(new_node)
|
270
|
+
end.to change { undirected_graph.nodes.count }.by 1
|
271
|
+
|
272
|
+
expect(undirected_graph.has_node?(new_node)).to be true
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'raises an error if a node with the same name in the graph exists' do
|
276
|
+
new_node = Abuelo::Node.new('node 1')
|
277
|
+
|
278
|
+
expect do
|
279
|
+
undirected_graph.add_node(new_node)
|
280
|
+
end.to raise_error(Abuelo::Exceptions::NodeAlreadyExistsError)
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'sets the graph of the node to itself' do
|
284
|
+
new_node = Abuelo::Node.new('node 4')
|
285
|
+
|
286
|
+
expect do
|
287
|
+
undirected_graph.add_node(new_node)
|
288
|
+
end.to change { new_node.graph }.to(undirected_graph)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe '#has_node?(node)' do
|
293
|
+
it 'returns true if node is in the graph. checks object name' do
|
294
|
+
expect(undirected_graph.has_node?(node_1)).to be true
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'returns false if node is not in the graph. checks object name' do
|
298
|
+
new_node = Abuelo::Node.new('node 4')
|
299
|
+
expect(undirected_graph.has_node?(new_node)).to be false
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
describe '#has_node_with_name?(name)' do
|
304
|
+
it 'returns true if a node with the given name is in the graph' do
|
305
|
+
expect(undirected_graph.has_node_with_name?('node 1')).to be true
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'returns false if no node with the given name is in the graph' do
|
309
|
+
expect(undirected_graph.has_node_with_name?('foo')).to be false
|
310
|
+
end
|
116
311
|
end
|
117
312
|
end
|
118
313
|
|
119
|
-
|
120
|
-
|
121
|
-
|
314
|
+
context 'edges' do
|
315
|
+
describe '#edges' do
|
316
|
+
it 'returns the edges of the graph' do
|
317
|
+
edges = [[edge_1, edge_1.symmetric], [edge_2, edge_2.symmetric]]
|
318
|
+
expect(undirected_graph.edges).to match_array edges
|
319
|
+
end
|
122
320
|
end
|
123
321
|
|
124
|
-
|
125
|
-
|
322
|
+
describe '#add_edge(edge)' do
|
323
|
+
it 'adds the edge to the graph' do
|
324
|
+
new_edge = Abuelo::Edge.new(node_1, node_3, 23)
|
325
|
+
|
326
|
+
expect do
|
327
|
+
undirected_graph.add_edge(new_edge)
|
328
|
+
end.to change { undirected_graph.edges.count }.by 1
|
329
|
+
|
330
|
+
expect(undirected_graph.has_edge?(new_edge)).to be true
|
331
|
+
expect(undirected_graph.has_edge?(new_edge.symmetric)).to be true
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'raises an error if an edge with the same name nodes in the graph exists' do
|
335
|
+
new_edge = Abuelo::Edge.new(node_1, node_2, 23)
|
336
|
+
|
337
|
+
expect do
|
338
|
+
undirected_graph.add_edge(new_edge)
|
339
|
+
end.to raise_error(Abuelo::Exceptions::EdgeAlreadyExistsError)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
describe '#edges_for_node(node)' do
|
344
|
+
it 'returns all edges that start from the given node' do
|
345
|
+
expect(undirected_graph.edges_for_node(node_1)).to eq [edge_1]
|
346
|
+
expect(undirected_graph.edges_for_node(node_2)).to eq [edge_1.symmetric, edge_2]
|
347
|
+
expect(undirected_graph.edges_for_node(node_3)).to eq [edge_2.symmetric]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
describe '#has_edge?(edge)' do
|
352
|
+
it 'returns true if there is an edge with similiar nodes in the graph' do
|
353
|
+
expect(undirected_graph.has_edge?(edge_1)).to be true
|
354
|
+
expect(undirected_graph.has_edge?(edge_1.symmetric)).to be true
|
355
|
+
end
|
356
|
+
|
357
|
+
it 'returns false if there is no edge with similiar nodes in the graph' do
|
358
|
+
other_edge = Abuelo::Edge.new(node_1, node_3, 23)
|
359
|
+
expect(undirected_graph.has_edge?(other_edge)).to be false
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
describe '#find_edge(node_1, node_2)' do
|
364
|
+
it 'returns the edge if it can be found in the graph' do
|
365
|
+
expect(undirected_graph.find_edge(node_1, node_2)).to eq edge_1
|
366
|
+
expect(undirected_graph.find_edge(node_2, node_1)).to eq edge_1.symmetric
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'returns nil if the edge cannot be found in the graph' do
|
370
|
+
expect(undirected_graph.find_edge(node_1, node_3)).to be nil
|
371
|
+
end
|
126
372
|
end
|
127
373
|
end
|
128
374
|
end
|
129
|
-
|
130
375
|
end
|
data/spec/node_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Abuelo::Node do
|
4
|
-
|
5
4
|
let(:node) { described_class.new('node 1') }
|
6
5
|
|
7
6
|
context 'initialization' do
|
@@ -35,6 +34,24 @@ RSpec.describe Abuelo::Node do
|
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
37
|
+
describe '#neighbours' do
|
38
|
+
it 'returns an array with all nodes that are connected via an edge to the node' do
|
39
|
+
graph = Abuelo::Graph.new(directed: true, adjacency_matrix: <<-matrix
|
40
|
+
0 1 1
|
41
|
+
0 0 0
|
42
|
+
0 0 0
|
43
|
+
matrix
|
44
|
+
)
|
45
|
+
node_1 = graph.find_node_by_name('node 1')
|
46
|
+
node_2 = graph.find_node_by_name('node 2')
|
47
|
+
node_3 = graph.find_node_by_name('node 3')
|
48
|
+
|
49
|
+
expect(node_1.neighbours).to match_array [node_2, node_3]
|
50
|
+
expect(node_2.neighbours).to eq []
|
51
|
+
expect(node_3.neighbours).to eq []
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
38
55
|
describe '#to_s' do
|
39
56
|
it 'responds with its name' do
|
40
57
|
expect(node.to_s).to eq 'node 1'
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '/../lib')
|
2
2
|
require "#{File.dirname(__FILE__)}/../lib/abuelo"
|
3
3
|
require 'pry'
|
4
4
|
|
@@ -6,4 +6,4 @@ RSpec.configure do |config|
|
|
6
6
|
config.run_all_when_everything_filtered = true
|
7
7
|
config.filter_run :focus
|
8
8
|
config.order = 'random'
|
9
|
-
end
|
9
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abuelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dirk Holzapfel
|
@@ -67,12 +67,15 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
description: Abuelo is a graph theory library.
|
70
|
-
email:
|
70
|
+
email: cache.zero@mailbox.org
|
71
71
|
executables: []
|
72
72
|
extensions: []
|
73
73
|
extra_rdoc_files: []
|
74
74
|
files:
|
75
75
|
- ".gitignore"
|
76
|
+
- ".travis.yml"
|
77
|
+
- CHANGELOG.md
|
78
|
+
- CODE_OF_CONDUCT.md
|
76
79
|
- Gemfile
|
77
80
|
- Gemfile.lock
|
78
81
|
- MIT-LICENSE
|
@@ -118,3 +121,4 @@ test_files:
|
|
118
121
|
- spec/graph_spec.rb
|
119
122
|
- spec/node_spec.rb
|
120
123
|
- spec/spec_helper.rb
|
124
|
+
has_rdoc:
|