GoNodes 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/Original_README.md +41 -0
- data/README.markdown +86 -0
- data/Rakefile +1 -0
- data/SUBMSSIONS_GUIDES.md +5 -0
- data/_spike/combo +0 -0
- data/_spike/combo.c +69 -0
- data/_spike/examples.rb +14 -0
- data/_spike/speed.rb +27 -0
- data/autotest/discover.rb +1 -0
- data/gonodes.gemspec +20 -0
- data/lib/gonodes.rb +13 -0
- data/lib/gonodes/edge.rb +77 -0
- data/lib/gonodes/edge_list.rb +73 -0
- data/lib/gonodes/error.rb +9 -0
- data/lib/gonodes/graph.rb +57 -0
- data/lib/gonodes/node.rb +29 -0
- data/lib/gonodes/node_list.rb +52 -0
- data/lib/gonodes/version.rb +3 -0
- data/lib/monkeypatch/set.rb +9 -0
- data/spec/gonodes/edge_list_spec.rb +144 -0
- data/spec/gonodes/edge_spec.rb +190 -0
- data/spec/gonodes/graph_spec.rb +78 -0
- data/spec/gonodes/node_spec.rb +53 -0
- data/spec/gonodes/nodes_list_spec.rb +92 -0
- data/spec/monkeypatch/set_spec.rb +21 -0
- data/spec/spec_helper.rb +5 -0
- metadata +77 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Mike Bethany
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Original_README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
NOTE: If in doubt about how to submit your project, read SUBMISSION_GUIDELINES
|
2
|
+
|
3
|
+
Combinatorics is a fascinating branch of mathematics that every programmer should
|
4
|
+
have at least a working knowledge of. In this exercise, you'll research the
|
5
|
+
topic as a group, and then produce some code that serves as an interesting
|
6
|
+
example of what you learned.
|
7
|
+
|
8
|
+
This is primarily a research exercise, but in order to get credit for working on
|
9
|
+
it, each student is expected to produce working code that either does something
|
10
|
+
useful on its own or alternatively serves as a tool for other projects to use.
|
11
|
+
|
12
|
+
While working on this assignment, please keep the following guidelines in mind:
|
13
|
+
|
14
|
+
* You can research any area of combinatorics of interest to you, but in
|
15
|
+
particular, you should look into graph theory because it is most directly
|
16
|
+
applicable to programming problems and is fairly accessible without too much
|
17
|
+
background information. In addition to the tremendous amount of information
|
18
|
+
on wikipedia, there is a [free book on Graph
|
19
|
+
Theory](http://code.google.com/p/graph-theory-algorithms-book/) that might
|
20
|
+
be a good place to start.
|
21
|
+
|
22
|
+
* You should actively share resources with your fellow students in the session,
|
23
|
+
as well as discuss your ideas and questions as they arise. This will make it
|
24
|
+
possible to sift through the information much faster, and also help with
|
25
|
+
figuring out what areas might be worth focusing on. The open-endedness of this
|
26
|
+
exercise is intentional, so don't let it overwhelm you: Work together!
|
27
|
+
|
28
|
+
* Once you've gained a basic understanding of what combinatorics is about, you
|
29
|
+
should think of either a program you can write that uses some combinatorial
|
30
|
+
concepts, or think about building some library code that will make it easier
|
31
|
+
for others to use combinatorial structures and algorithms in their own code.
|
32
|
+
Be sure to include some practical examples no matter which approach you
|
33
|
+
choose.
|
34
|
+
|
35
|
+
* Don't spend too much time on concepts that feel far outside of your reach, and
|
36
|
+
similarly, don't worry about picking a very advanced concept to demonstrate in
|
37
|
+
your programs. Instead, stick to the things that seem most fundamental, and
|
38
|
+
work with us to figure out whether what you have in mind will be a good fit
|
39
|
+
for this assignment. You can submit your project ideas via university-web for
|
40
|
+
feedback at any time during the course, and it's recommended to do so before
|
41
|
+
putting a lot of work into any particular idea.
|
data/README.markdown
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# GoNodes
|
2
|
+
An easy way to create simple, weighted graphs. Even randomly populated ones!
|
3
|
+
|
4
|
+
## Description
|
5
|
+
If you need to create simple (in the mathematical sense), weighted graphs with
|
6
|
+
random values for the connections and weights then this is the gem for you.
|
7
|
+
|
8
|
+
## Install
|
9
|
+
`$ gem install gonodes`
|
10
|
+
|
11
|
+
## Instructions
|
12
|
+
The main entry into the app is the GoNodes::Graph class so I'll only cover
|
13
|
+
that class here. To learn how to customize usage of the library check out the
|
14
|
+
specs.
|
15
|
+
|
16
|
+
### GoNodes::Graph
|
17
|
+
To create your graph you might initialize a new instance like so:
|
18
|
+
|
19
|
+
graph = GoNodes::Graph.new(
|
20
|
+
node_count: 4,
|
21
|
+
completeness: 0.5,
|
22
|
+
random_edges: true,
|
23
|
+
random_weights: {median: 10, range: 4}
|
24
|
+
)
|
25
|
+
|
26
|
+
#### GoNodes::Graph Parameters
|
27
|
+
**node_count:**
|
28
|
+
*integer*
|
29
|
+
How many nodes there will be in the graph.
|
30
|
+
|
31
|
+
**completeness:**
|
32
|
+
*float*
|
33
|
+
The percentage of connectivity between the nodes with. A complete graph has an
|
34
|
+
edge going from every node to every other node. For instance if you had 5 nodes
|
35
|
+
then you would have 10 edges.
|
36
|
+
|
37
|
+
1.0 would be 100% complete
|
38
|
+
0.25 would be 25% complete
|
39
|
+
|
40
|
+
**random\_edges:**
|
41
|
+
*boolean*
|
42
|
+
If this is set to true then edges will be randomly generated between nodes
|
43
|
+
instead of being created in a repeatable manner.
|
44
|
+
|
45
|
+
**random\_weights:**
|
46
|
+
*(hash of values)*
|
47
|
+
To create random weights between for your edges you can specify the median
|
48
|
+
value and how much to randomly deviate from that number. So if you had a
|
49
|
+
median of 10 and a range of 4 then your weights assigned to your edges would
|
50
|
+
be from 8 to 12 inclusively.
|
51
|
+
|
52
|
+
## Example usage
|
53
|
+
|
54
|
+
# A complete, simple graph with all nodes connected
|
55
|
+
graph = GoNodes::Graph.new(node_count: 12)
|
56
|
+
puts graph
|
57
|
+
|
58
|
+
# A full graph with all the features used
|
59
|
+
graph = GoNodes::Graph.new( node_count: 8,
|
60
|
+
completeness: 0.5,
|
61
|
+
random_edges: true,
|
62
|
+
random_weights: {median: 50, range: 10}
|
63
|
+
)
|
64
|
+
puts graph
|
65
|
+
|
66
|
+
## License
|
67
|
+
Copyright (c) 2011 Mike Bethany
|
68
|
+
|
69
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
70
|
+
a copy of this software and associated documentation files (the
|
71
|
+
"Software"), to deal in the Software without restriction, including
|
72
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
73
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
74
|
+
permit persons to whom the Software is furnished to do so, subject to
|
75
|
+
the following conditions:
|
76
|
+
|
77
|
+
The above copyright notice and this permission notice shall be
|
78
|
+
included in all copies or substantial portions of the Software.
|
79
|
+
|
80
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
81
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
82
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
83
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
84
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
85
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
86
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,5 @@
|
|
1
|
+
Fork this repository and hack away! When you are ready for a review, log into the RMU web app and request a review for the appropriate exercise. You can do this as often as you'd like, a general rule of thumb is to request feedback early and often.
|
2
|
+
|
3
|
+
Other students in the session will be able to see your code on github, and it's okay to collaborate as long as the work you submit is mostly of your own creation. That said, do not share your solution or the problem description with anyone outside of the session without talking to me about it first. Instructors in this course will not share your submission with anyone outside of your session except RMU staff without your permission.
|
4
|
+
|
5
|
+
NOTE: No work done after 7/28 will be evaluated, be sure to request an initial review way before then!
|
data/_spike/combo
ADDED
Binary file
|
data/_spike/combo.c
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
// From:
|
2
|
+
// http://snippets.dzone.com/posts/show/4666
|
3
|
+
// http://compprog.wordpress.com/2007/10/17/generating-combinations-1/
|
4
|
+
|
5
|
+
#include <stdio.h>
|
6
|
+
|
7
|
+
/* Prints out a combination like {1, 2} */
|
8
|
+
void printc(int comb[], int k) {
|
9
|
+
printf("{");
|
10
|
+
int i;
|
11
|
+
|
12
|
+
for (i = 0; i < k; ++i)
|
13
|
+
{printf("%d, ", comb[i] + 1);}
|
14
|
+
|
15
|
+
printf("\b\b}\n");
|
16
|
+
}
|
17
|
+
|
18
|
+
/*
|
19
|
+
next_comb(int comb[], int k, int n)
|
20
|
+
Generates the next combination of n elements as k after comb
|
21
|
+
|
22
|
+
comb => the previous combination ( use (0, 1, 2, ..., k) for first)
|
23
|
+
k => the size of the subsets to generate
|
24
|
+
n => the size of the original set
|
25
|
+
|
26
|
+
Returns: 1 if a valid combination was found 0, otherwise
|
27
|
+
*/
|
28
|
+
int next_comb(int comb[], int k, int n) {
|
29
|
+
int i = k - 1;
|
30
|
+
++comb[i];
|
31
|
+
while ((i >= 0) && (comb[i] >= n - k + 1 + i))
|
32
|
+
{
|
33
|
+
--i;
|
34
|
+
++comb[i];
|
35
|
+
}
|
36
|
+
|
37
|
+
if (comb[0] > n - k) /* Combination (n-k, n-k+1, ..., n) reached */
|
38
|
+
return 0; /* No more combinations can be generated */
|
39
|
+
|
40
|
+
/* comb now looks like (..., x, n, n, n, ..., n).
|
41
|
+
Turn it into (..., x, x + 1, x + 2, ...) */
|
42
|
+
for (i = i + 1; i < k; ++i)
|
43
|
+
{comb[i] = comb[i - 1] + 1;}
|
44
|
+
|
45
|
+
return 1;
|
46
|
+
}
|
47
|
+
|
48
|
+
int main(int argc, char *argv[]) {
|
49
|
+
int n = 10000; /* The size of the set; for {1, 2, 3, 4} it's 4 */
|
50
|
+
int k = 2; /* The size of the subsets; for {1, 2}, {1, 3}, ... it's 2 */
|
51
|
+
int comb[16]; /* comb[i] is the index of the i-th element in the combination */
|
52
|
+
|
53
|
+
/* Setup comb for the initial combination */
|
54
|
+
int i;
|
55
|
+
for (i = 0; i < k; ++i)
|
56
|
+
{ comb[i] = i; }
|
57
|
+
|
58
|
+
/* Print the first combination */
|
59
|
+
//printc(comb, k);
|
60
|
+
|
61
|
+
/* Generate and print all the other combinations */
|
62
|
+
while (next_comb(comb, k, n))
|
63
|
+
{
|
64
|
+
//printc(comb, k);
|
65
|
+
}
|
66
|
+
|
67
|
+
printf("\n all done\n");
|
68
|
+
return 0;
|
69
|
+
}
|
data/_spike/examples.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "/../lib"))
|
2
|
+
require 'gonodes'
|
3
|
+
|
4
|
+
# A complete, simple graph with all nodes connected
|
5
|
+
graph = GoNodes::Graph.new(node_count: 12)
|
6
|
+
puts graph
|
7
|
+
|
8
|
+
# A full graph with all the features used
|
9
|
+
graph = GoNodes::Graph.new( node_count: 8,
|
10
|
+
completeness: 0.5,
|
11
|
+
random_edges: true,
|
12
|
+
random_weights: {median: 50, range: 10}
|
13
|
+
)
|
14
|
+
puts graph
|
data/_spike/speed.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# 1 = A ... 26 = Z, 27 = AA, 28 = AB ... etc
|
2
|
+
def alpha_new(number)
|
3
|
+
"A".tap {|a| (number-1).times {a.succ!}}
|
4
|
+
end
|
5
|
+
|
6
|
+
def alpha_old(number)
|
7
|
+
alpha = ""
|
8
|
+
begin
|
9
|
+
alpha << ((number-=1) % 26) + 65
|
10
|
+
end while (number /= 26) > 0
|
11
|
+
alpha.reverse
|
12
|
+
end
|
13
|
+
#puts alpha_old(64 * 1024)
|
14
|
+
|
15
|
+
# t = Time.now
|
16
|
+
# (20000).times {|x| alpha_new(x) }
|
17
|
+
# puts Time.now - t
|
18
|
+
|
19
|
+
# t = Time.now
|
20
|
+
# (10000).times {|x| alpha_new(x) }
|
21
|
+
# puts Time.now - t
|
22
|
+
# => 20.895054
|
23
|
+
|
24
|
+
t = Time.now
|
25
|
+
(5000).times {|x| alpha_old(x) }
|
26
|
+
puts Time.now - t
|
27
|
+
# => 0.011024
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery {"rspec2"}
|
data/gonodes.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gonodes/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "GoNodes"
|
7
|
+
s.version = GoNodes::VERSION
|
8
|
+
s.authors = ["Mike Bethany"]
|
9
|
+
s.email = ["mikbe.tk@gmail.com"]
|
10
|
+
s.homepage = "http://mikbe.tk/projects#gonodes"
|
11
|
+
s.summary = %q{An easy way to create simple, weighted graphs. Even randomly populated ones!}
|
12
|
+
s.description = %q{If you need to create simple (in the mathematical sense), weighted graphs with
|
13
|
+
random values for the connections and weights then this is the gem for you.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "gonodes"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
data/lib/gonodes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "delegate"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
require "monkeypatch/set"
|
5
|
+
|
6
|
+
require "gonodes/version"
|
7
|
+
|
8
|
+
require "gonodes/error"
|
9
|
+
require "gonodes/node"
|
10
|
+
require "gonodes/node_list"
|
11
|
+
require "gonodes/edge"
|
12
|
+
require "gonodes/edge_list"
|
13
|
+
require "gonodes/graph"
|
data/lib/gonodes/edge.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module GoNodes
|
2
|
+
|
3
|
+
class Edge
|
4
|
+
|
5
|
+
attr_accessor :name, :start_node, :end_node, :weight, :direction
|
6
|
+
|
7
|
+
def initialize(params={})
|
8
|
+
|
9
|
+
@start_node = params[:start_node] if params[:start_node]
|
10
|
+
@end_node = params[:end_node] if params[:end_node]
|
11
|
+
|
12
|
+
@weight = params[:weight] if params[:weight]
|
13
|
+
|
14
|
+
@directed = !!params[:directed] if params[:directed]
|
15
|
+
@directed ||= false
|
16
|
+
|
17
|
+
set_direction
|
18
|
+
create_name
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
[" name: #{@name}",
|
24
|
+
" weight: #{@weight}",
|
25
|
+
" direction: #{@direction}"].join("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
def is_directed?
|
29
|
+
@directed
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_direction
|
33
|
+
return unless is_directed? && @start_node && @end_node
|
34
|
+
|
35
|
+
@direction = "#{@start_node.name} -> #{@end_node.name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_name
|
39
|
+
return unless @start_node && @end_node
|
40
|
+
start_name = convert_node_name(@start_node)
|
41
|
+
end_name = convert_node_name(@end_node)
|
42
|
+
|
43
|
+
@name = "{#{start_name},#{end_name}}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(other_edge)
|
47
|
+
|
48
|
+
self_nodes = [self.start_node, self.end_node]
|
49
|
+
other_nodes = [other_edge.start_node, other_edge.end_node]
|
50
|
+
|
51
|
+
unless self.is_directed? || other_edge.is_directed?
|
52
|
+
self_nodes.sort!
|
53
|
+
other_nodes.sort!
|
54
|
+
end
|
55
|
+
|
56
|
+
[self_nodes, self.is_directed?, self.weight] ==
|
57
|
+
[other_nodes, other_edge.is_directed?, other_edge.weight]
|
58
|
+
end
|
59
|
+
|
60
|
+
def <=>(other_edge)
|
61
|
+
self_nodes = [self.start_node, self.end_node]
|
62
|
+
other_nodes = [other_edge.start_node, other_edge.end_node]
|
63
|
+
|
64
|
+
[self_nodes, self.is_directed?, self.weight] <=>
|
65
|
+
[other_nodes, other_edge.is_directed?, other_edge.weight]
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def convert_node_name(node)
|
71
|
+
return node.name if node.name.is_a? Numeric
|
72
|
+
"#{node.name.inspect}".downcase
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module GoNodes
|
2
|
+
|
3
|
+
class EdgeList < DelegateClass(Set)
|
4
|
+
|
5
|
+
attr_reader :completeness
|
6
|
+
|
7
|
+
def initialize(node_list)
|
8
|
+
@node_list = node_list
|
9
|
+
@edges = Set.new
|
10
|
+
super(@edges)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@edges.map{|edge| edge.to_s}.join("\n\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.new_with_count(params)
|
18
|
+
EdgeList.new(params[:node_list]).
|
19
|
+
populate_with_count(params[:count])
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.new_with_completeness(params)
|
23
|
+
EdgeList.new(params[:node_list]).
|
24
|
+
populate_with_completeness(params[:completeness])
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other_edge_list)
|
28
|
+
self.sort == other_edge_list.sort
|
29
|
+
end
|
30
|
+
|
31
|
+
def populate_with_count(edge_count)
|
32
|
+
@completeness = (edge_count.to_f / maximum_edges).round(2)
|
33
|
+
|
34
|
+
node_list_combo = possible_edges.cycle
|
35
|
+
|
36
|
+
@edges.clear
|
37
|
+
edge_count.times do
|
38
|
+
start_node, end_node = node_list_combo.next
|
39
|
+
@edges << Edge.new(
|
40
|
+
start_node: start_node,
|
41
|
+
end_node: end_node
|
42
|
+
)
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def populate_with_completeness(completeness)
|
48
|
+
edge_count = normalized_edge_count(completeness)
|
49
|
+
populate_with_count(edge_count)
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_edge(edge_params)
|
53
|
+
@edges << Edge.new(edge_params)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def normalized_edge_count(completeness)
|
58
|
+
(maximum_edges * completeness).to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
# refactor after everything works - slow with more than a few hundred
|
62
|
+
# if loopback edges are required I could use repeated_combination
|
63
|
+
def possible_edges
|
64
|
+
@possible_edges ||= @node_list.to_a.combination(2).to_a
|
65
|
+
end
|
66
|
+
|
67
|
+
def maximum_edges
|
68
|
+
(@node_list.count * (@node_list.count - 1)) / 2
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module GoNodes
|
4
|
+
|
5
|
+
class Graph
|
6
|
+
|
7
|
+
attr_reader :nodes, :edges
|
8
|
+
|
9
|
+
def initialize(params={})
|
10
|
+
|
11
|
+
@nodes = NodeList.new
|
12
|
+
@nodes.populate_with_count(params[:node_count]) if params[:node_count]
|
13
|
+
@nodes.populate_with_names(params[:node_names]) if params[:node_names]
|
14
|
+
|
15
|
+
@edges = EdgeList.new(@nodes)
|
16
|
+
completeness = params[:completeness] ? params[:completeness] : 1.0
|
17
|
+
|
18
|
+
if params[:random_edges] && completeness != 1.0
|
19
|
+
randomize_edges(completeness)
|
20
|
+
else
|
21
|
+
@edges.populate_with_completeness(completeness)
|
22
|
+
end
|
23
|
+
|
24
|
+
randomize_weights(params[:random_weights]) if params[:random_weights]
|
25
|
+
end
|
26
|
+
|
27
|
+
def completeness
|
28
|
+
@edges.completeness
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other_graph)
|
32
|
+
@nodes == other_graph.nodes && @edges == other_graph.edges
|
33
|
+
end
|
34
|
+
|
35
|
+
def randomize_edges(completeness)
|
36
|
+
edge_count = @edges.normalized_edge_count(completeness)
|
37
|
+
edge_nodes = @edges.possible_edges.sample(edge_count)
|
38
|
+
edge_nodes.each do |nodes|
|
39
|
+
@edges.add_edge(start_node: nodes[0], end_node: nodes[1])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def randomize_weights(random_weights)
|
44
|
+
median = random_weights[:median]
|
45
|
+
range = random_weights[:range]
|
46
|
+
@edges.each do |edge|
|
47
|
+
edge.weight = median + rand(range) - (range / 2)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"Nodes:\n#{@nodes.to_s}\n\nEdges:\n#{@edges.to_s}"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/lib/gonodes/node.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module GoNodes
|
2
|
+
|
3
|
+
class Node
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name=nil)
|
8
|
+
raise GoNodes::Error::BadNodeName,
|
9
|
+
"Nodes may only be named using a symbol, string, or number" unless
|
10
|
+
[NilClass, String, Symbol].include?(name.class) || name.is_a?(Numeric)
|
11
|
+
|
12
|
+
@name = name ? name : self.object_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@name
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other_node)
|
20
|
+
self.name == other_node.name
|
21
|
+
end
|
22
|
+
|
23
|
+
def <=>(other_node)
|
24
|
+
self.name <=> other_node.name
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module GoNodes
|
2
|
+
|
3
|
+
class NodeList < DelegateClass(Set)
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@nodes = Set.new
|
7
|
+
super(@nodes)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
@nodes.to_a.join(", ")
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.new_with_count(count)
|
15
|
+
node_list = NodeList.new
|
16
|
+
node_list.populate_with_count(count)
|
17
|
+
node_list
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.new_with_names(names)
|
21
|
+
node_list = NodeList.new
|
22
|
+
node_list.populate_with_names(names)
|
23
|
+
node_list
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](node_name)
|
27
|
+
@nodes.select{|node,_| node.name == node_name}.first
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other_node_list)
|
31
|
+
self.sort == other_node_list.sort
|
32
|
+
end
|
33
|
+
|
34
|
+
def populate_with_count(node_count)
|
35
|
+
return unless node_count
|
36
|
+
alpha = "A"
|
37
|
+
@nodes.clear
|
38
|
+
node_count.times do
|
39
|
+
@nodes << Node.new(alpha)
|
40
|
+
alpha = alpha.succ
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def populate_with_names(node_names)
|
45
|
+
return unless node_names
|
46
|
+
@nodes.clear
|
47
|
+
node_names.each { |name| @nodes << Node.new(name)}
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoNodes::EdgeList do
|
4
|
+
|
5
|
+
describe "a new EdgeList" do
|
6
|
+
|
7
|
+
it "should initialize using a number of edges" do
|
8
|
+
node_list = GoNodes::NodeList.new_with_count(4)
|
9
|
+
edge_list = GoNodes::EdgeList.new_with_count(
|
10
|
+
node_list: node_list,
|
11
|
+
count: 6
|
12
|
+
)
|
13
|
+
|
14
|
+
edge_list.count.should == 6
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should initialize using a completeness percentage" do
|
18
|
+
node_list = GoNodes::NodeList.new_with_count(4)
|
19
|
+
edge_list = GoNodes::EdgeList.new_with_completeness(
|
20
|
+
node_list: node_list,
|
21
|
+
completeness: 0.5
|
22
|
+
)
|
23
|
+
|
24
|
+
edge_list.count.should == 3
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should create an empty set if no count or completeness are given" do
|
28
|
+
node_list = GoNodes::NodeList.new_with_count(4)
|
29
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
30
|
+
|
31
|
+
edge_list.should be_empty
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when populating using a completeness percentage" do
|
37
|
+
|
38
|
+
it "should create the appropriate number of edges" do
|
39
|
+
node_list = GoNodes::NodeList.new_with_count(4)
|
40
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
41
|
+
|
42
|
+
edge_list.populate_with_completeness(1.0)
|
43
|
+
|
44
|
+
edge_list.count.should == 6
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should normalize completeness level if not evenly divisible to edges" do
|
48
|
+
node_list = GoNodes::NodeList.new_with_count(5)
|
49
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
50
|
+
|
51
|
+
edge_list.populate_with_completeness(0.66)
|
52
|
+
|
53
|
+
edge_list.completeness.should == 0.6
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should create edges using the given nodes" do
|
57
|
+
node_list = GoNodes::NodeList.new_with_count(2)
|
58
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
59
|
+
edge_list.populate_with_completeness(1.0)
|
60
|
+
|
61
|
+
edge_list.first.start_node.name.should == "A"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should add new edge using edge parameters" do
|
65
|
+
node_list = GoNodes::NodeList.new_with_count(2)
|
66
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
67
|
+
|
68
|
+
edge_list.possible_edges.each do |edge|
|
69
|
+
edge_list.add_edge(start_node: edge[0], end_node: edge[1])
|
70
|
+
end
|
71
|
+
edge_list.first.start_node.should_not be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when populating using a number of edges" do
|
77
|
+
|
78
|
+
it "should create the specified number of edges" do
|
79
|
+
node_list = GoNodes::NodeList.new_with_count(5)
|
80
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
81
|
+
|
82
|
+
edge_list.populate_with_count(10)
|
83
|
+
|
84
|
+
edge_list.count.should == 10
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when testing equality" do
|
90
|
+
|
91
|
+
it "should equate edgelists with equal edges" do
|
92
|
+
node_list1 = GoNodes::NodeList.new_with_names(%w{B A})
|
93
|
+
edge_list1 = GoNodes::EdgeList.new(node_list1)
|
94
|
+
edge_list1.populate_with_completeness(1.0)
|
95
|
+
|
96
|
+
node_list2 = GoNodes::NodeList.new_with_names(%w{A B})
|
97
|
+
edge_list2 = GoNodes::EdgeList.new(node_list2)
|
98
|
+
edge_list2.populate_with_completeness(1.0)
|
99
|
+
|
100
|
+
edge_list1.should == edge_list2
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should not equate edgelists with unequal edges" do
|
104
|
+
node_list1 = GoNodes::NodeList.new_with_names(%w{A B})
|
105
|
+
edge_list1 = GoNodes::EdgeList.new(node_list1)
|
106
|
+
edge_list1.populate_with_completeness(1.0)
|
107
|
+
|
108
|
+
node_list2 = GoNodes::NodeList.new_with_names(%w{A C})
|
109
|
+
edge_list2 = GoNodes::EdgeList.new(node_list2)
|
110
|
+
edge_list2.populate_with_completeness(1.0)
|
111
|
+
|
112
|
+
edge_list1.should_not == edge_list2
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should calculate an array of possible edges" do
|
118
|
+
node_list = GoNodes::NodeList.new_with_names(%w{A B C})
|
119
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
120
|
+
|
121
|
+
edge_list.possible_edges.should == [
|
122
|
+
[node_list["A"], node_list["B"]],
|
123
|
+
[node_list["A"], node_list["C"]],
|
124
|
+
[node_list["B"], node_list["C"]],
|
125
|
+
]
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should output a human readable list when printed" do
|
130
|
+
node_list = GoNodes::NodeList.new_with_count(3)
|
131
|
+
edge_list = GoNodes::EdgeList.new(node_list)
|
132
|
+
edge_list.populate_with_completeness(1.0)
|
133
|
+
|
134
|
+
edge_list.to_s.should ==
|
135
|
+
" name: {\"a\",\"b\"}\n" +
|
136
|
+
" weight: \n direction: \n\n" +
|
137
|
+
" name: {\"a\",\"c\"}\n" +
|
138
|
+
" weight: \n direction: \n\n" +
|
139
|
+
" name: {\"b\",\"c\"}\n" +
|
140
|
+
" weight: \n direction: "
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoNodes::Edge do
|
4
|
+
|
5
|
+
it "should set its start node" do
|
6
|
+
node1 = GoNodes::Node.new(:A)
|
7
|
+
edge = GoNodes::Edge.new(start_node: node1)
|
8
|
+
edge.start_node.should == node1
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should set its end node" do
|
12
|
+
node2 = GoNodes::Node.new(:B)
|
13
|
+
edge = GoNodes::Edge.new(end_node: node2)
|
14
|
+
edge.end_node.should == node2
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set its weight" do
|
18
|
+
edge = GoNodes::Edge.new(weight: 4)
|
19
|
+
edge.weight.should == 4
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should set its direction" do
|
23
|
+
edge = GoNodes::Edge.new(directed: true)
|
24
|
+
edge.is_directed?.should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should default to undirected if direction is not specified" do
|
28
|
+
edge = GoNodes::Edge.new
|
29
|
+
edge.is_directed?.should be_false
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should set a direction of start_node -> end_node if it is directed" do
|
33
|
+
node1 = GoNodes::Node.new(:A)
|
34
|
+
node2 = GoNodes::Node.new(:B)
|
35
|
+
|
36
|
+
edge = GoNodes::Edge.new(start_node: node1, end_node: node2, directed: true)
|
37
|
+
|
38
|
+
edge.direction.should == "A -> B"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should create a lower case name for itself based on its end points" do
|
42
|
+
node1 = GoNodes::Node.new(:A)
|
43
|
+
node2 = GoNodes::Node.new(:B)
|
44
|
+
|
45
|
+
edge = GoNodes::Edge.new(start_node: node1, end_node: node2)
|
46
|
+
|
47
|
+
edge.name.should == "{:a,:b}"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should create a name using numerically named end points" do
|
51
|
+
require 'bigdecimal'
|
52
|
+
names = [BigDecimal("#{Math::PI}"), BigDecimal("#{Math::E}")]
|
53
|
+
node1 = GoNodes::Node.new(names[0])
|
54
|
+
node2 = GoNodes::Node.new(names[1])
|
55
|
+
|
56
|
+
edge = GoNodes::Edge.new(start_node: node1, end_node: node2)
|
57
|
+
|
58
|
+
edge.name.should == "{#{names[0]},#{names[1]}}"
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when testing equality' do
|
62
|
+
|
63
|
+
it 'should equate edges having the same properites' do
|
64
|
+
node1 = GoNodes::Node.new(:A)
|
65
|
+
node2 = GoNodes::Node.new(:B)
|
66
|
+
node3 = GoNodes::Node.new(:A)
|
67
|
+
node4 = GoNodes::Node.new(:B)
|
68
|
+
|
69
|
+
edge1 = GoNodes::Edge.new(
|
70
|
+
start_node: node1,
|
71
|
+
end_node: node2,
|
72
|
+
directed: true,
|
73
|
+
weight: 10
|
74
|
+
)
|
75
|
+
edge2 = GoNodes::Edge.new(
|
76
|
+
start_node: node3,
|
77
|
+
end_node: node4,
|
78
|
+
directed: true,
|
79
|
+
weight: 10
|
80
|
+
)
|
81
|
+
|
82
|
+
edge1.should == edge2
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should not equate edges with only some similar properties' do
|
86
|
+
node1 = GoNodes::Node.new(:A)
|
87
|
+
node2 = GoNodes::Node.new(:B)
|
88
|
+
node3 = GoNodes::Node.new(:A)
|
89
|
+
node4 = GoNodes::Node.new(:C)
|
90
|
+
|
91
|
+
edge1 = GoNodes::Edge.new(
|
92
|
+
start_node: node1,
|
93
|
+
end_node: node2
|
94
|
+
)
|
95
|
+
edge2 = GoNodes::Edge.new(
|
96
|
+
start_node: node3,
|
97
|
+
end_node: node4
|
98
|
+
)
|
99
|
+
|
100
|
+
edge1.should_not == edge2
|
101
|
+
end
|
102
|
+
|
103
|
+
it "shouldn't == directed with same endpoints but different directions" do
|
104
|
+
node1 = GoNodes::Node.new(:A)
|
105
|
+
node2 = GoNodes::Node.new(:B)
|
106
|
+
node3 = GoNodes::Node.new(:B)
|
107
|
+
node4 = GoNodes::Node.new(:A)
|
108
|
+
|
109
|
+
edge1 = GoNodes::Edge.new(
|
110
|
+
start_node: node1,
|
111
|
+
end_node: node2,
|
112
|
+
directed: true
|
113
|
+
)
|
114
|
+
edge2 = GoNodes::Edge.new(
|
115
|
+
start_node: node3,
|
116
|
+
end_node: node4,
|
117
|
+
directed: true
|
118
|
+
)
|
119
|
+
|
120
|
+
edge1.should_not == edge2
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should equate undirected edges with same endpoints in different order" do
|
124
|
+
node1 = GoNodes::Node.new(:A)
|
125
|
+
node2 = GoNodes::Node.new(:B)
|
126
|
+
node3 = GoNodes::Node.new(:B)
|
127
|
+
node4 = GoNodes::Node.new(:A)
|
128
|
+
|
129
|
+
edge1 = GoNodes::Edge.new(
|
130
|
+
start_node: node1,
|
131
|
+
end_node: node2,
|
132
|
+
directed: false
|
133
|
+
)
|
134
|
+
edge2 = GoNodes::Edge.new(
|
135
|
+
start_node: node3,
|
136
|
+
end_node: node4,
|
137
|
+
directed: false
|
138
|
+
)
|
139
|
+
|
140
|
+
edge1.should == edge2
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
context "when sorting" do
|
146
|
+
|
147
|
+
it "should sort using nodes, weight, and is_directed" do
|
148
|
+
node1 = GoNodes::Node.new(:A)
|
149
|
+
node2 = GoNodes::Node.new(:B)
|
150
|
+
node3 = GoNodes::Node.new(:A)
|
151
|
+
node4 = GoNodes::Node.new(:D)
|
152
|
+
|
153
|
+
edge1 = GoNodes::Edge.new(
|
154
|
+
start_node: node3,
|
155
|
+
end_node: node4,
|
156
|
+
directed: true,
|
157
|
+
weight: 10
|
158
|
+
)
|
159
|
+
edge2 = GoNodes::Edge.new(
|
160
|
+
start_node: node1,
|
161
|
+
end_node: node2,
|
162
|
+
directed: true,
|
163
|
+
weight: 10
|
164
|
+
)
|
165
|
+
|
166
|
+
[edge1, edge2].sort.should == [edge2, edge1]
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should output human readable information when printed" do
|
173
|
+
node1 = GoNodes::Node.new(:A)
|
174
|
+
node2 = GoNodes::Node.new(:B)
|
175
|
+
|
176
|
+
edge = GoNodes::Edge.new(
|
177
|
+
start_node: node1,
|
178
|
+
end_node: node2,
|
179
|
+
directed: true,
|
180
|
+
weight: 10
|
181
|
+
)
|
182
|
+
|
183
|
+
edge.to_s.should ==
|
184
|
+
""" name: {:a,:b}
|
185
|
+
weight: 10
|
186
|
+
direction: A -> B"""
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoNodes::Graph do
|
4
|
+
|
5
|
+
let(:graph) {GoNodes::Graph.new}
|
6
|
+
|
7
|
+
it "should create the specified number of nodes" do
|
8
|
+
graph = GoNodes::Graph.new(node_count: 4)
|
9
|
+
graph.nodes.count.should == 4
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should create nodes using a provided array of names" do
|
13
|
+
graph = GoNodes::Graph.new(node_names: %w{D E F 7 5 4})
|
14
|
+
graph.nodes.collect{|node| node.name}.sort.should == %w{D E F 7 5 4}.sort
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set the specified level of completeness" do
|
18
|
+
graph = GoNodes::Graph.new(node_count: 4, completeness: 0.5)
|
19
|
+
graph.completeness.should == 0.5
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should set completeness to 100% if it is not specified" do
|
23
|
+
graph = GoNodes::Graph.new(node_count: 4)
|
24
|
+
graph.completeness.should == 1.0
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should equate graphs will all things being equal" do
|
28
|
+
graph1 = GoNodes::Graph.new(node_count: 4, completeness: 1.0)
|
29
|
+
graph2 = GoNodes::Graph.new(node_count: 4, completeness: 1.0)
|
30
|
+
graph1.should == graph2
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should create not generate random graphs normally" do
|
34
|
+
settings = {node_count: 50, completeness: 0.1, random_edges: false}
|
35
|
+
graphs = 10.times.collect{GoNodes::Graph.new(settings)}
|
36
|
+
|
37
|
+
graphs.combination(2).all? do |combo|
|
38
|
+
combo[0] == combo[1]
|
39
|
+
end.should be_true
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should create randomly generated graphs" do
|
44
|
+
settings = {node_count: 50, completeness: 0.1, random_edges: true}
|
45
|
+
graphs = 10.times.collect{GoNodes::Graph.new(settings)}
|
46
|
+
|
47
|
+
graphs.combination(2).any? do |combo|
|
48
|
+
combo[0] != combo[1]
|
49
|
+
end.should be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should make randomly weighted edges using a median and range" do
|
53
|
+
settings = {
|
54
|
+
node_count: 4,
|
55
|
+
random_weights: {median: 50, range: 10}
|
56
|
+
}
|
57
|
+
graphs = 10.times.collect{GoNodes::Graph.new(settings)}
|
58
|
+
|
59
|
+
graphs.combination(2).any? do |combo|
|
60
|
+
combo[0] != combo[1]
|
61
|
+
end.should be_true
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should print out a human readable represenation of the graph" do
|
65
|
+
graph = GoNodes::Graph.new(node_count: 3, completeness: 1.0)
|
66
|
+
graph.to_s.should ==
|
67
|
+
"Nodes:\n" +
|
68
|
+
"A, B, C\n\n" +
|
69
|
+
"Edges:\n" +
|
70
|
+
" name: {\"a\",\"b\"}\n" +
|
71
|
+
" weight: \n direction: \n\n" +
|
72
|
+
" name: {\"a\",\"c\"}\n" +
|
73
|
+
" weight: \n direction: \n\n" +
|
74
|
+
" name: {\"b\",\"c\"}\n" +
|
75
|
+
" weight: \n direction: "
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoNodes::Node do
|
4
|
+
|
5
|
+
describe "a new node" do
|
6
|
+
|
7
|
+
it "should set the specified name" do
|
8
|
+
node = GoNodes::Node.new("A")
|
9
|
+
node.name.should == "A"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should set its name to its object ID if no name is specified" do
|
13
|
+
node = GoNodes::Node.new
|
14
|
+
node.name.should == node.object_id
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set any type of number as its name" do
|
18
|
+
require 'bigdecimal'
|
19
|
+
numbers = [42, 4.2, BigDecimal("#{Math::PI}")]
|
20
|
+
numbers.collect {|name| GoNodes::Node.new(name).name}.should == numbers
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should raise an exception if something other than alphanumerics or numbers are given as a name" do
|
24
|
+
expect{GoNodes::Node.new(String)}.should raise_error(GoNodes::Error::BadNodeName)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not change its name if the object that originally specified its name changes' do
|
30
|
+
name = "A"
|
31
|
+
node = GoNodes::Node.new(name)
|
32
|
+
expect{name.succ!}.should_not change(node, :name)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should equate two nodes with the same name" do
|
36
|
+
node1 = GoNodes::Node.new("A")
|
37
|
+
node2 = GoNodes::Node.new("A")
|
38
|
+
node1.should == node2
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should sort by node name" do
|
42
|
+
node1 = GoNodes::Node.new("Z")
|
43
|
+
node2 = GoNodes::Node.new("A")
|
44
|
+
|
45
|
+
[node1, node2].sort.should == [node2, node1]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should output its name when printed" do
|
49
|
+
node1 = GoNodes::Node.new("Z")
|
50
|
+
node1.to_s.should == "Z"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoNodes::NodeList do
|
4
|
+
|
5
|
+
let(:node_list){GoNodes::NodeList.new}
|
6
|
+
|
7
|
+
describe "a new NodeList" do
|
8
|
+
|
9
|
+
it "should initialize using a number of nodes" do
|
10
|
+
node_list = GoNodes::NodeList.new_with_count(15)
|
11
|
+
node_list.last.name.should == "O"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should initialize using an array of node names" do
|
15
|
+
node_list = GoNodes::NodeList.new_with_names(%w{A B C D E F G})
|
16
|
+
node_list.last.name.should == "G"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when populating the node list" do
|
22
|
+
|
23
|
+
it "should create an empty set if none were specified when initialized" do
|
24
|
+
GoNodes::NodeList.new
|
25
|
+
node_list.should be_empty
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should create the specified number of nodes" do
|
29
|
+
node_list.populate_with_count(4)
|
30
|
+
node_list.count.should == 4
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should name nodes alphabetically" do
|
34
|
+
node_list.populate_with_count(4)
|
35
|
+
node_list.last.name.should == "D"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should name nodes alphabetically" do
|
39
|
+
node_list.populate_with_count(4)
|
40
|
+
node_list.first.name.should == "A"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should name nodes uniquely when there are more than 26 node_list" do
|
44
|
+
node_list.populate_with_count(64 * 1024)
|
45
|
+
node_list.last.name.should == "CRXP"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should create nodes using an array of node names" do
|
49
|
+
node_list.populate_with_names(%w{A B C 1 2 3})
|
50
|
+
node_list.collect{|node| node.name}.sort.should == %w{A B C 1 2 3}.sort
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when accessing the node list using square brackets []" do
|
56
|
+
|
57
|
+
it "should return the Node object matching the node name" do
|
58
|
+
node_list.populate_with_count(4)
|
59
|
+
node_list["A"].should eql(node_list.first)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should not use the index of keys but the keys themselves" do
|
63
|
+
node_list.populate_with_names([10,11,12,13])
|
64
|
+
node_list[0].should be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should return the correct node when using numbers" do
|
68
|
+
node_list.populate_with_names([10.4,11.6,12.1,13.78])
|
69
|
+
node_list[13.78].should eql(node_list.last)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when testing equality" do
|
75
|
+
|
76
|
+
it "should equate regardless of order" do
|
77
|
+
node_list1 = GoNodes::NodeList.new_with_names(%w{A B})
|
78
|
+
node_list2 = GoNodes::NodeList.new_with_names(%w{B A})
|
79
|
+
|
80
|
+
node_list1.should == node_list2
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
it "it should print out a human readable list of nodes" do
|
86
|
+
node_list = GoNodes::NodeList.new_with_names(%w{A B})
|
87
|
+
|
88
|
+
node_list.to_s.should == "A, B"
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Set do
|
4
|
+
|
5
|
+
it "should have a last if it has a first" do
|
6
|
+
set = Set.new
|
7
|
+
set.should respond_to(:last) if set.respond_to? :first
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return the last value of the set" do
|
11
|
+
set = Set.new
|
12
|
+
|
13
|
+
set << "A"
|
14
|
+
set << "M"
|
15
|
+
set << "Z"
|
16
|
+
|
17
|
+
set.last.should == "Z"
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: GoNodes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mike Bethany
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-07-20 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! "If you need to create simple (in the mathematical sense), weighted
|
15
|
+
graphs with \n random values for the connections and weights then this is the
|
16
|
+
gem for you."
|
17
|
+
email:
|
18
|
+
- mikbe.tk@gmail.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- .gitignore
|
24
|
+
- Gemfile
|
25
|
+
- LICENSE.txt
|
26
|
+
- Original_README.md
|
27
|
+
- README.markdown
|
28
|
+
- Rakefile
|
29
|
+
- SUBMSSIONS_GUIDES.md
|
30
|
+
- _spike/combo
|
31
|
+
- _spike/combo.c
|
32
|
+
- _spike/examples.rb
|
33
|
+
- _spike/speed.rb
|
34
|
+
- autotest/discover.rb
|
35
|
+
- gonodes.gemspec
|
36
|
+
- lib/.DS_Store
|
37
|
+
- lib/gonodes.rb
|
38
|
+
- lib/gonodes/edge.rb
|
39
|
+
- lib/gonodes/edge_list.rb
|
40
|
+
- lib/gonodes/error.rb
|
41
|
+
- lib/gonodes/graph.rb
|
42
|
+
- lib/gonodes/node.rb
|
43
|
+
- lib/gonodes/node_list.rb
|
44
|
+
- lib/gonodes/version.rb
|
45
|
+
- lib/monkeypatch/set.rb
|
46
|
+
- spec/gonodes/edge_list_spec.rb
|
47
|
+
- spec/gonodes/edge_spec.rb
|
48
|
+
- spec/gonodes/graph_spec.rb
|
49
|
+
- spec/gonodes/node_spec.rb
|
50
|
+
- spec/gonodes/nodes_list_spec.rb
|
51
|
+
- spec/monkeypatch/set_spec.rb
|
52
|
+
- spec/spec_helper.rb
|
53
|
+
homepage: http://mikbe.tk/projects#gonodes
|
54
|
+
licenses: []
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project: gonodes
|
73
|
+
rubygems_version: 1.8.5
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: An easy way to create simple, weighted graphs. Even randomly populated ones!
|
77
|
+
test_files: []
|