rgraph 0.0.6 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|