GoNodes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gonodes.gemspec
4
+ gemspec
@@ -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.
@@ -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.
@@ -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.
@@ -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!
Binary file
@@ -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
+ }
@@ -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
@@ -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"}
@@ -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
@@ -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"
@@ -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,9 @@
1
+ module GoNodes
2
+
3
+ module Error
4
+
5
+ exceptions = [:BadNodeName] # %{NoNodeName SomeOtherError}
6
+ exceptions.each {|e| const_set(e, Class.new(StandardError))}
7
+
8
+ end
9
+ 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
@@ -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,3 @@
1
+ module GoNodes
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'set'
2
+
3
+ class Set
4
+
5
+ def last
6
+ @hash.keys.last
7
+ end
8
+
9
+ 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
@@ -0,0 +1,5 @@
1
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "/../lib"))
2
+ $: << '.'
3
+
4
+ require 'rspec'
5
+ require 'gonodes'
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: []