rgraph 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Guardfile +1 -1
- data/lib/rgraph/graph.rb +108 -13
- data/lib/rgraph/link.rb +18 -18
- data/lib/rgraph/version.rb +1 -1
- data/lib/rgraph.rb +0 -4
- data/rgraph.gemspec +1 -2
- data/spec/fixtures/small_graph.csv +8 -0
- data/spec/fixtures/three_links.csv +2 -2
- data/spec/fixtures/two_links_with_hole.csv +4 -0
- data/spec/rgraph/graph_spec.rb +94 -0
- data/spec/rgraph/link_spec.rb +6 -0
- metadata +6 -18
data/Guardfile
CHANGED
data/lib/rgraph/graph.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
#encoding: utf-8
|
1
2
|
require 'csv'
|
2
3
|
require_relative '../../lib/rgraph/link'
|
3
4
|
require_relative '../../lib/rgraph/node'
|
4
5
|
|
5
|
-
|
6
6
|
class Graph
|
7
|
-
attr_accessor :nodes, :links
|
7
|
+
attr_accessor :nodes, :links, :infinity
|
8
8
|
|
9
9
|
def initialize(csv)
|
10
10
|
@nodes = []
|
11
11
|
@links = []
|
12
|
+
@distance = nil
|
13
|
+
@infinity = 100_000
|
12
14
|
raise Exception.new("the file must be a .csv") unless File.extname(csv) == ".csv"
|
13
15
|
|
14
16
|
CSV.foreach(csv, headers: true) do |row|
|
@@ -16,14 +18,10 @@ class Graph
|
|
16
18
|
source_id = row.delete('source').last
|
17
19
|
target_id = row.delete('target').last
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
unless target = get_node_by_id(target_id)
|
24
|
-
target = Node.new(id: target_id)
|
25
|
-
@nodes << target
|
26
|
-
end
|
21
|
+
source = get_node_by_id(source_id) || Node.new(id: source_id)
|
22
|
+
target = get_node_by_id(target_id) || Node.new(id: target_id)
|
23
|
+
|
24
|
+
maybe_add_to_nodes(source, target)
|
27
25
|
|
28
26
|
@links << Link.new(source: source, target: target, weight: row['weight'], year: row['year'])
|
29
27
|
end
|
@@ -47,12 +45,103 @@ class Graph
|
|
47
45
|
|
48
46
|
def cumulative_degree
|
49
47
|
cached_degrees = degrees
|
50
|
-
|
48
|
+
out = []
|
51
49
|
|
52
50
|
0.upto(degrees.max - 1) do |i|
|
53
|
-
|
51
|
+
out[i] = cached_degrees.select{|degree| degree > i}.count
|
54
52
|
end
|
55
|
-
|
53
|
+
out.map{|a| a / out.max.to_f}
|
54
|
+
end
|
55
|
+
|
56
|
+
def idistance
|
57
|
+
dist = Array.new(@nodes.size) { Array.new(@nodes.size, 0) }
|
58
|
+
|
59
|
+
@nodes.sort{|a,b| a.id <=> b.id}.each_with_index do |n1, i|
|
60
|
+
@nodes.sort{|a,b| a.id <=> b.id}.each_with_index do |n2, j|
|
61
|
+
if i != j
|
62
|
+
dist[i][j] = n1.neighbours.include?(n2) ? @links.select{ |link| (link.source == n1 || link.source == n2) && (link.target == n1 || link.target == n2) }.first.weight.to_i : @infinity
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
dist
|
67
|
+
end
|
68
|
+
|
69
|
+
def distances
|
70
|
+
@path = Array.new(@nodes.size) { Array.new(@nodes.size, nil) }
|
71
|
+
|
72
|
+
@distance = idistance
|
73
|
+
@distance.each_index do |k|
|
74
|
+
@distance.each_index do |i|
|
75
|
+
@distance.each_index do |j|
|
76
|
+
new_dist = @distance[i][k] + @distance[k][j]
|
77
|
+
if @distance[i][j] > new_dist
|
78
|
+
@distance[i][j] = new_dist
|
79
|
+
@path[i][j] = k
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
@distance
|
85
|
+
end
|
86
|
+
|
87
|
+
def shortest_paths
|
88
|
+
tmp = []
|
89
|
+
|
90
|
+
distances unless @my_shortest_paths
|
91
|
+
|
92
|
+
0.upto(@nodes.size - 1).each do |i|
|
93
|
+
i.upto(@nodes.size - 1).each do |j|
|
94
|
+
next if i == j
|
95
|
+
sp = shortest_path(i, j)
|
96
|
+
tmp << sp if sp
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@shortest_paths = tmp
|
101
|
+
end
|
102
|
+
|
103
|
+
def shortest_path(i, j)
|
104
|
+
|
105
|
+
return [] if @distance[i][j] == @infinity
|
106
|
+
|
107
|
+
k = @path[i][j]
|
108
|
+
|
109
|
+
case k
|
110
|
+
when @infinity
|
111
|
+
[]
|
112
|
+
when nil
|
113
|
+
[i,j]
|
114
|
+
else
|
115
|
+
#We need to do this or k will appear three times
|
116
|
+
shortest_path(i, k)[0..-2] + [k] + shortest_path(k, j)[1..-1]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def between(i)
|
121
|
+
shortest_paths unless @shortest_paths
|
122
|
+
|
123
|
+
n = @shortest_paths.select{|c| c[1..-2].include?(i)}.size.to_f
|
124
|
+
m = @shortest_paths.size.to_f
|
125
|
+
n / m
|
126
|
+
end
|
127
|
+
|
128
|
+
def betweenness(normalized = false)
|
129
|
+
bts = 0.upto(@nodes.size - 1).map { |i| between(i) }
|
130
|
+
|
131
|
+
if normalized
|
132
|
+
max = bts.max
|
133
|
+
min = bts.min
|
134
|
+
|
135
|
+
bts.map{|bt| (bt - min) / (max - min)}
|
136
|
+
else
|
137
|
+
bts
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def diameter
|
142
|
+
distances unless @distance
|
143
|
+
|
144
|
+
(distances.flatten - [@infinity]).max
|
56
145
|
end
|
57
146
|
|
58
147
|
private
|
@@ -60,4 +149,10 @@ class Graph
|
|
60
149
|
def get_node_by_id(node_id)
|
61
150
|
@nodes.select{|n| n.id == node_id}.first
|
62
151
|
end
|
152
|
+
|
153
|
+
def maybe_add_to_nodes(*nodes)
|
154
|
+
nodes.each do |node|
|
155
|
+
@nodes << node unless get_node_by_id(node.id)
|
156
|
+
end
|
157
|
+
end
|
63
158
|
end
|
data/lib/rgraph/link.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class Link
|
2
|
+
attr_accessor :source, :target
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
def initialize(arg)
|
5
|
+
@args = arg
|
6
|
+
@source = @args.delete(:source)
|
7
|
+
@target = @args.delete(:target)
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
raise Exception.new("source cant be nil") unless @source
|
10
|
+
raise Exception.new("target cant be nil") unless @target
|
11
|
+
raise Exception.new("source must be of type Node") unless @source.is_a? Node
|
12
|
+
raise Exception.new("target must be of type Node") unless @target.is_a? Node
|
13
13
|
|
14
|
-
|
14
|
+
@args[:weight] ||= 1
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
@source.neighbours << @target
|
17
|
+
@target.neighbours << @source
|
18
|
+
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
20
|
+
def method_missing(name, *args)
|
21
|
+
super unless args.empty?
|
22
|
+
@args[name]
|
24
23
|
end
|
24
|
+
end
|
data/lib/rgraph/version.rb
CHANGED
data/lib/rgraph.rb
CHANGED
data/rgraph.gemspec
CHANGED
@@ -14,12 +14,11 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
17
|
-
spec.test_files = spec.files.grep(%r{^
|
17
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
20
|
spec.add_development_dependency "bundler", "~> 1.3"
|
21
21
|
spec.add_development_dependency "rake"
|
22
22
|
spec.add_development_dependency "rspec", "~> 2.14.1"
|
23
23
|
spec.add_development_dependency "guard-rspec", "~> 3.0.2"
|
24
|
-
spec.add_development_dependency "fastercsv", "~> 1.5.5"
|
25
24
|
end
|
data/spec/rgraph/graph_spec.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
1
3
|
require_relative '../../lib/rgraph/graph'
|
2
4
|
|
3
5
|
describe Graph do
|
4
6
|
describe "read .csv" do
|
5
7
|
subject { Graph.new('spec/fixtures/three_links.csv') }
|
8
|
+
|
6
9
|
it "creates three nodes" do
|
7
10
|
expect(subject.nodes.size).to eq(3)
|
8
11
|
end
|
@@ -43,6 +46,11 @@ describe Graph do
|
|
43
46
|
|
44
47
|
describe "Metrics" do
|
45
48
|
subject { Graph.new('spec/fixtures/three_links.csv') }
|
49
|
+
|
50
|
+
it "sets default infinity to 100_000" do
|
51
|
+
expect(subject.infinity).to eq(100_000)
|
52
|
+
end
|
53
|
+
|
46
54
|
it "all nodes degree" do
|
47
55
|
expect(subject.degrees).to eq([2,2,2])
|
48
56
|
end
|
@@ -53,4 +61,90 @@ describe Graph do
|
|
53
61
|
expect(subject.cumulative_degree).to eq([1.0, 1.0])
|
54
62
|
end
|
55
63
|
end
|
64
|
+
|
65
|
+
describe "Distance" do
|
66
|
+
subject { Graph.new('spec/fixtures/small_graph.csv') }
|
67
|
+
|
68
|
+
it "calculates the initial distance matrix" do
|
69
|
+
k = subject.infinity
|
70
|
+
expect(subject.idistance).to eq(
|
71
|
+
[[0,1,k,k,1,k],
|
72
|
+
[1,0,1,k,1,k],
|
73
|
+
[k,1,0,1,k,k],
|
74
|
+
[k,k,1,0,1,1],
|
75
|
+
[1,1,k,1,0,k],
|
76
|
+
[k,k,k,1,k,0]])
|
77
|
+
end
|
78
|
+
|
79
|
+
it "checks distances" do
|
80
|
+
expect(subject.distances).to eq(
|
81
|
+
[[0,1,2,2,1,3],
|
82
|
+
[1,0,1,2,1,3],
|
83
|
+
[2,1,0,1,2,2],
|
84
|
+
[2,2,1,0,1,1],
|
85
|
+
[1,1,2,1,0,2],
|
86
|
+
[3,3,2,1,2,0]])
|
87
|
+
end
|
88
|
+
|
89
|
+
it "understands holes on the graph" do
|
90
|
+
graph = Graph.new('spec/fixtures/two_links_with_hole.csv')
|
91
|
+
k = graph.infinity
|
92
|
+
expect(graph.idistance).to eq(
|
93
|
+
[[0,1,k,k,k],
|
94
|
+
[1,0,1,k,k],
|
95
|
+
[k,1,0,k,k],
|
96
|
+
[k,k,k,0,1],
|
97
|
+
[k,k,k,1,0]])
|
98
|
+
expect(graph.distances).to eq(
|
99
|
+
[[0,1,2,k,k],
|
100
|
+
[1,0,1,k,k],
|
101
|
+
[2,1,0,k,k],
|
102
|
+
[k,k,k,0,1],
|
103
|
+
[k,k,k,1,0]])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
describe "Paths' metrics" do
|
107
|
+
subject { Graph.new('spec/fixtures/small_graph.csv') }
|
108
|
+
it "calculates the shortest paths" do
|
109
|
+
expect(subject.shortest_paths.sort).to eq(
|
110
|
+
[[0, 1],
|
111
|
+
[0, 1, 2],
|
112
|
+
[0, 4],
|
113
|
+
[0, 4, 3],
|
114
|
+
[0, 4, 3, 5],
|
115
|
+
[1, 2],
|
116
|
+
[1, 2, 3],
|
117
|
+
[1, 2, 3, 5],
|
118
|
+
[1, 4],
|
119
|
+
[2, 1, 4],
|
120
|
+
[2, 3],
|
121
|
+
[2, 3, 5],
|
122
|
+
[3, 4], [3, 5],
|
123
|
+
[4, 3, 5]].sort)
|
124
|
+
end
|
125
|
+
it "calculates the betweenness of a single node" do
|
126
|
+
expect(subject.between(0)).to eq(0 / 15.0)
|
127
|
+
expect(subject.between(1)).to eq(2 / 15.0)
|
128
|
+
expect(subject.between(2)).to eq(2 / 15.0)
|
129
|
+
expect(subject.between(3)).to eq(4 / 15.0)
|
130
|
+
expect(subject.between(4)).to eq(2 / 15.0)
|
131
|
+
expect(subject.between(5)).to eq(0 / 15.0)
|
132
|
+
end
|
133
|
+
it "calculates all betweenness" do
|
134
|
+
expect(subject.betweenness).to eq(
|
135
|
+
[0 / 15.0,
|
136
|
+
2 / 15.0,
|
137
|
+
2 / 15.0,
|
138
|
+
4 / 15.0,
|
139
|
+
2 / 15.0,
|
140
|
+
0 / 15.0])
|
141
|
+
end
|
142
|
+
it "calculates all betweenness normalized" do
|
143
|
+
expect(subject.betweenness(true)).to eq(
|
144
|
+
[0.0, 0.5, 0.5, 1.0, 0.5, 0.0])
|
145
|
+
end
|
146
|
+
it "calculates diameter" do
|
147
|
+
expect(subject.diameter).to eq(3)
|
148
|
+
end
|
149
|
+
end
|
56
150
|
end
|
data/spec/rgraph/link_spec.rb
CHANGED
@@ -9,6 +9,12 @@ describe Link do
|
|
9
9
|
its(:target) { should be_kind_of Node }
|
10
10
|
its(:weight) { should == 1 }
|
11
11
|
its(:years) { should == [2011, 2012] }
|
12
|
+
|
13
|
+
it "checks the creation of neighbours" do
|
14
|
+
expect(subject.source.neighbours).to eq([subject.target])
|
15
|
+
expect(subject.target.neighbours).to eq([subject.source])
|
16
|
+
end
|
17
|
+
|
12
18
|
end
|
13
19
|
|
14
20
|
describe "creates a link passing a weight" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rgraph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-08-
|
13
|
+
date: 2013-08-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -76,22 +76,6 @@ dependencies:
|
|
76
76
|
- - ~>
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: 3.0.2
|
79
|
-
- !ruby/object:Gem::Dependency
|
80
|
-
name: fastercsv
|
81
|
-
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
|
-
requirements:
|
84
|
-
- - ~>
|
85
|
-
- !ruby/object:Gem::Version
|
86
|
-
version: 1.5.5
|
87
|
-
type: :development
|
88
|
-
prerelease: false
|
89
|
-
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
|
-
requirements:
|
92
|
-
- - ~>
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
version: 1.5.5
|
95
79
|
description: Ruby's Graph Library
|
96
80
|
email:
|
97
81
|
- aride.moulin@gmail.com
|
@@ -116,8 +100,10 @@ files:
|
|
116
100
|
- lib/rgraph/version.rb
|
117
101
|
- rgraph.gemspec
|
118
102
|
- spec/fixtures/2005.csv
|
103
|
+
- spec/fixtures/small_graph.csv
|
119
104
|
- spec/fixtures/three_links.csv
|
120
105
|
- spec/fixtures/two_links.csv
|
106
|
+
- spec/fixtures/two_links_with_hole.csv
|
121
107
|
- spec/rgraph/graph_spec.rb
|
122
108
|
- spec/rgraph/link_spec.rb
|
123
109
|
- spec/rgraph/node_spec.rb
|
@@ -150,8 +136,10 @@ specification_version: 3
|
|
150
136
|
summary: A Ruby's Graph Library
|
151
137
|
test_files:
|
152
138
|
- spec/fixtures/2005.csv
|
139
|
+
- spec/fixtures/small_graph.csv
|
153
140
|
- spec/fixtures/three_links.csv
|
154
141
|
- spec/fixtures/two_links.csv
|
142
|
+
- spec/fixtures/two_links_with_hole.csv
|
155
143
|
- spec/rgraph/graph_spec.rb
|
156
144
|
- spec/rgraph/link_spec.rb
|
157
145
|
- spec/rgraph/node_spec.rb
|