rugraph 0.4.0
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.
- checksums.yaml +7 -0
- data/lib/rugraph.rb +372 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aadae7e823b0900d0b3adf9e3dc2d31ce35418c1
|
4
|
+
data.tar.gz: 14550cb1699d8f55b4d2fdcd2492f313582c85f7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: add9e065cedb6518b27b801f6cf40b58d75af79c9ffaa44283b0099ed13284c452ea36416c1c5acf0292975ae27e563902208abd16da005ad330fdd935609c47
|
7
|
+
data.tar.gz: dfc8d3a96890dcae09c70712dd5cecae1b47715d680f0119c317a6afcafd5dd6081adb52ad9fd6049fa44b28a792a4438d934a66f0efd075d99e440aa122317d
|
data/lib/rugraph.rb
ADDED
@@ -0,0 +1,372 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
# Rugraph class representing a Directed graph.
|
3
|
+
class Rugraph
|
4
|
+
# @return [Rugraph] an empty graph.
|
5
|
+
def initialize
|
6
|
+
@structure = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
# Creates graph from describing hash. See {#load_from_vertices_hash}
|
10
|
+
#
|
11
|
+
# @param (see #load_from_vertices_hash)
|
12
|
+
# @return [Rugraph] the generated graph
|
13
|
+
def self.new_from_vertices_hash(vertices_hash)
|
14
|
+
new.load_from_vertices_hash(vertices_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates graph from describing array. See {#load_from_edges_array}
|
18
|
+
#
|
19
|
+
# @param (see #load_from_edges_array)
|
20
|
+
# @return [Rugraph] the generated graph
|
21
|
+
def self.new_from_edges_array(edges_array)
|
22
|
+
new.load_from_edges_array(edges_array)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adds vertices and edges to the graph from a hash [Hash] formatted
|
26
|
+
# as such
|
27
|
+
# { node1 => neighboring_nodes_array, node2 => ... }
|
28
|
+
#
|
29
|
+
# @param vertices_hash [Hash] a hash describing the Rugraph
|
30
|
+
# @return [Rugraph] the graph
|
31
|
+
def load_from_vertices_hash(vertices_hash)
|
32
|
+
@structure = vertices_hash
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Adds vertices and edges to the graph from an array [Array] formatted
|
37
|
+
# as such
|
38
|
+
# [[source_node, destination] [source_node2, destination2], ...]
|
39
|
+
#
|
40
|
+
# @param edges_array [Array] an array describing the Rugraph
|
41
|
+
# @return [Rugraph] the graph
|
42
|
+
def load_from_edges_array(edges_array)
|
43
|
+
@structure = edges_array
|
44
|
+
.each_with_object({}) do |edge, nodes|
|
45
|
+
nodes[edge[0]] = [] unless nodes.keys.include? edge[0]
|
46
|
+
nodes[edge[0]] << edge[1]
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Adds a vertex to the graph. If the vertex already exists, do nothing.
|
52
|
+
#
|
53
|
+
# @param node [Object] the value of the node to add to the graph
|
54
|
+
# @return [Rugraph] the graph
|
55
|
+
def add_vertex(node)
|
56
|
+
@structure[node] ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
# (see #add_vertex)
|
60
|
+
def add_node(node)
|
61
|
+
add_vertex(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Adds an edge to the graph. If the vertex already exists, do nothing.
|
65
|
+
#
|
66
|
+
# @param edge [Array] an array formatted as such [source, dest]
|
67
|
+
# @return [Rugraph] the graph
|
68
|
+
def add_edge(edge)
|
69
|
+
edge.each { |node| add_vertex(node) }
|
70
|
+
@structure[edge[0]] << edge[1]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Method returning the neighbors of a node.
|
74
|
+
#
|
75
|
+
# @param node [Object] the value corresponding to a node in the graph
|
76
|
+
# @return [Array, nil] an array of its neighbors if the node exists,
|
77
|
+
# nil otherwise
|
78
|
+
def neighbors_of(node)
|
79
|
+
return nil unless nodes.include?(node)
|
80
|
+
@structure[node] || []
|
81
|
+
end
|
82
|
+
|
83
|
+
# Computes the shortest path lengths from a node to all other
|
84
|
+
# accessible nodes.
|
85
|
+
#
|
86
|
+
# @param source [Object] the value of the source node.
|
87
|
+
# @return [Hash] a hash of the lengths of shortest path to each other node.
|
88
|
+
def shortest_path_lengths(source)
|
89
|
+
seen = {}
|
90
|
+
level = 0
|
91
|
+
nextlevel = { source => 1 }
|
92
|
+
until nextlevel.empty?
|
93
|
+
thislevel = nextlevel
|
94
|
+
nextlevel = {}
|
95
|
+
thislevel.each do |v, _|
|
96
|
+
if seen[v].nil?
|
97
|
+
seen[v] = level
|
98
|
+
nextlevel.update(neighbors_of(v).map { |w| [w, nil] }.to_h)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
level += 1
|
102
|
+
end
|
103
|
+
seen
|
104
|
+
end
|
105
|
+
|
106
|
+
# Computes the shortest path a node.
|
107
|
+
#
|
108
|
+
# @param source [Object] the value of the source node.
|
109
|
+
# @return [Array] an array [pred, seen] containing two hashes :
|
110
|
+
# - pred [Hash] a hash of the shortest path to each node
|
111
|
+
# - seen [Hash] a hash of the lengths of the shortest path to each node
|
112
|
+
def shortest_paths(source)
|
113
|
+
level = 0
|
114
|
+
nextlevel = [source]
|
115
|
+
seen = { source => level }
|
116
|
+
pred = { source => [] }
|
117
|
+
until nextlevel.empty?
|
118
|
+
level += 1
|
119
|
+
thislevel = nextlevel
|
120
|
+
nextlevel = []
|
121
|
+
thislevel.each do |v|
|
122
|
+
neighbors_of(v).each do |w|
|
123
|
+
next if (seen.keys.include? w) && (seen[w] != level)
|
124
|
+
unless seen.keys.include? w
|
125
|
+
pred[w] = []
|
126
|
+
seen[w] = level
|
127
|
+
nextlevel << w
|
128
|
+
end
|
129
|
+
pred[w] << v
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
[pred, seen]
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the edges around a node.
|
137
|
+
#
|
138
|
+
# @params node [Object]
|
139
|
+
# @return [Array] an array of the edges.
|
140
|
+
def edges_of(node)
|
141
|
+
edges_from(node) | edges_to(node)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the edges coming out of a node.
|
145
|
+
#
|
146
|
+
# @params node [Object]
|
147
|
+
# @return [Array] an array of the edges.
|
148
|
+
def edges_from(node)
|
149
|
+
self[node].map { |n| [node, n] }
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the edges coming from a node.
|
153
|
+
#
|
154
|
+
# @params node [Object]
|
155
|
+
# @return [Array] an array of the edges.
|
156
|
+
def edges_to(node)
|
157
|
+
edges.select { |edge| edge.last == node }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Computes the degree of each node.
|
161
|
+
#
|
162
|
+
# @return [Enumerator] an enumerator of the degrees of all nodes
|
163
|
+
def degrees
|
164
|
+
Enumerator.new(@structure.length) do |y|
|
165
|
+
nodes.each do |node|
|
166
|
+
y << [node, degree(node)]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Computes the degree of a node.
|
172
|
+
#
|
173
|
+
# @param node [Object] the value of a node.
|
174
|
+
# @return [Integer] the degree of the node.
|
175
|
+
def degree(node)
|
176
|
+
in_degree(node) + out_degree(node)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Computes the incoming degree of a node.
|
180
|
+
#
|
181
|
+
# @param (see #degree)
|
182
|
+
# @return (see #degree)
|
183
|
+
def in_degree(node)
|
184
|
+
self[node].length
|
185
|
+
end
|
186
|
+
|
187
|
+
# Computes the outcoming degree of a node.
|
188
|
+
#
|
189
|
+
# @param (see #degree)
|
190
|
+
# @return (see #degree)
|
191
|
+
def out_degree(node)
|
192
|
+
@structure.count { |_source, dest| dest.include? node }
|
193
|
+
end
|
194
|
+
|
195
|
+
# Computes the order (number of nodes) of the graph.
|
196
|
+
#
|
197
|
+
# @return [Integer] the order of the graph.
|
198
|
+
def order
|
199
|
+
nodes.length
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns the nodes in the graph
|
203
|
+
#
|
204
|
+
# @return [Array] the values of all the nodes in the graph.
|
205
|
+
def vertices
|
206
|
+
(@structure.keys | @structure.values).flatten.uniq
|
207
|
+
end
|
208
|
+
|
209
|
+
# (see #vertices)
|
210
|
+
def nodes
|
211
|
+
vertices
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns the list of all the edges in the graph.
|
215
|
+
#
|
216
|
+
# @return [Array] an array of pairs of nodes.
|
217
|
+
def edges
|
218
|
+
@structure.reduce([]) do |acc, node|
|
219
|
+
acc + node[1].map { |dest| [node[0], dest] }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Equivalent to {#neighbors_of}
|
224
|
+
#
|
225
|
+
# @return (see #neighbors_of)
|
226
|
+
def [](node)
|
227
|
+
neighbors_of(node)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Sets the edges coming out of a node.
|
231
|
+
#
|
232
|
+
# @params node_array [Array] an array of nodes
|
233
|
+
# @params node [node] the concerned node
|
234
|
+
# @return the new neighbors of the node
|
235
|
+
def []=(node, node_array)
|
236
|
+
@structure[node] = node_array
|
237
|
+
end
|
238
|
+
|
239
|
+
# Equivalent to #{add_vertex}.
|
240
|
+
#
|
241
|
+
# @params (see #add_vertex)
|
242
|
+
# @return (see #add_vertex)
|
243
|
+
def <<(node)
|
244
|
+
add_vertex(node)
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
# Calculates the betweenness centrality for each node.
|
249
|
+
#
|
250
|
+
# Based on Brandes algorithm :
|
251
|
+
# http://algo.uni-konstanz.de/publications/b-fabc-01.pdf
|
252
|
+
#
|
253
|
+
# @param graph [Rugraph] a graph
|
254
|
+
# @return [Hash] a hash with the nodes as keys and their centrality as values
|
255
|
+
def self.betweenness_centrality(graph)
|
256
|
+
cb = graph.vertices.map { |v| [v, 0] }.to_h
|
257
|
+
graph.vertices.each do |source|
|
258
|
+
stack = []
|
259
|
+
path = Hash.new { [] }
|
260
|
+
sig = Hash.new(0)
|
261
|
+
sig[source] = 1.0
|
262
|
+
distance = {}
|
263
|
+
distance[source] = 0
|
264
|
+
queue = []
|
265
|
+
queue << source
|
266
|
+
|
267
|
+
until queue.empty?
|
268
|
+
v = queue.shift
|
269
|
+
stack << v
|
270
|
+
|
271
|
+
graph.neighbors_of(v).each do |w|
|
272
|
+
unless distance.keys.include? w
|
273
|
+
queue << w
|
274
|
+
distance[w] = distance[v] + 1
|
275
|
+
end
|
276
|
+
if distance[w] == distance[v] + 1
|
277
|
+
sig[w] += sig[v]
|
278
|
+
path[w] += [v]
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
delta = Hash.new(0)
|
284
|
+
until stack.empty?
|
285
|
+
w = stack.pop
|
286
|
+
coeff = (1.0 + delta[w]) / sig[w].to_f
|
287
|
+
path[w].each do |n|
|
288
|
+
delta[n] += sig[n] * coeff
|
289
|
+
end
|
290
|
+
cb[w] += delta[w] if w != source
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
cb
|
295
|
+
end
|
296
|
+
|
297
|
+
# Calculate the closeness centrality for each node.
|
298
|
+
#
|
299
|
+
# @param (see #betweenness_centrality)
|
300
|
+
# @return (see #betweenness_centrality)
|
301
|
+
def self.closeness_centrality(graph)
|
302
|
+
closeness_centrality = {}
|
303
|
+
|
304
|
+
graph.vertices.each do |n|
|
305
|
+
shortest_paths = graph.shortest_path_lengths(n)
|
306
|
+
sum = shortest_paths.values.reduce(&:+)
|
307
|
+
closeness_centrality[n] = 0.0
|
308
|
+
next unless (sum > 0) && (graph.order > 1)
|
309
|
+
closeness_centrality[n] = (shortest_paths.length - 1) / sum.to_f
|
310
|
+
s = (shortest_paths.length - 1).to_f / (graph.order - 1)
|
311
|
+
closeness_centrality[n] *= s
|
312
|
+
end
|
313
|
+
|
314
|
+
closeness_centrality
|
315
|
+
end
|
316
|
+
|
317
|
+
# Calculate the degree centrality for each node.
|
318
|
+
#
|
319
|
+
# @param (see #betweenness_centrality)
|
320
|
+
# @return (see #betweenness_centrality)
|
321
|
+
def self.degree_centrality(graph)
|
322
|
+
s = 1.0 / (graph.order - 1)
|
323
|
+
graph.degrees
|
324
|
+
.map { |n, d| [n, d * s] }
|
325
|
+
.to_h
|
326
|
+
end
|
327
|
+
|
328
|
+
# Calculate the load centrality for each node.
|
329
|
+
#
|
330
|
+
# Based on NetworkX's Python implementation.
|
331
|
+
#
|
332
|
+
# @param (see #betweenness_centrality)
|
333
|
+
# @return (see #betweenness_centrality)
|
334
|
+
def self.load_centrality(graph)
|
335
|
+
betweenness = graph.vertices
|
336
|
+
.map { |n| [n, 0.0] }
|
337
|
+
.to_h
|
338
|
+
betweenness.each do |source, _|
|
339
|
+
ubetween = _node_betweenness(graph, source)
|
340
|
+
betweenness.merge(ubetween) do |key, v1, v2|
|
341
|
+
betweenness[key] = v1 + v2
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def self._node_betweenness(graph, source)
|
347
|
+
(pred, between) = graph.shortest_paths(source)
|
348
|
+
|
349
|
+
ordered = _ordered_nodes_from_paths(between)
|
350
|
+
between.each { |k, _| between[k] = 1.0 }
|
351
|
+
|
352
|
+
until ordered.empty?
|
353
|
+
v = ordered.pop
|
354
|
+
next unless pred.include? v
|
355
|
+
pred[v].each do |x|
|
356
|
+
break if x == source
|
357
|
+
between[x] += between[v] / pred[v].size.to_f
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
between.each { |n, _| between[n] -= 1 }
|
362
|
+
end
|
363
|
+
|
364
|
+
def self._ordered_nodes_from_paths(paths)
|
365
|
+
paths.to_a
|
366
|
+
.sort { |a, b| a.last <=> b.last }
|
367
|
+
.map(&:first)
|
368
|
+
.flatten
|
369
|
+
end
|
370
|
+
private_class_method :_node_betweenness
|
371
|
+
private_class_method :_ordered_nodes_from_paths
|
372
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rugraph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jérémie Bonal
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: A graph library implementing basic directed graph functions and some
|
28
|
+
algorithms.
|
29
|
+
email: jeremie.bonal@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/rugraph.rb
|
35
|
+
homepage: http://rubygems.org/gems/rugraph
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 2.6.1
|
56
|
+
signing_key:
|
57
|
+
specification_version: 4
|
58
|
+
summary: A graph library for Ruby.
|
59
|
+
test_files: []
|
60
|
+
has_rdoc:
|