graphs 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/lib/graph.rb +571 -0
- data/lib/graphs/gdf.rb +225 -0
- data/lib/graphs/json.rb +71 -0
- data/tests/edge_tests.rb +92 -0
- data/tests/gdf_tests.rb +377 -0
- data/tests/graph_tests.rb +703 -0
- data/tests/json_tests.rb +149 -0
- data/tests/node_tests.rb +141 -0
- data/tests/tests.rb +24 -0
- metadata +137 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 712ed1650338434fe66d21f64141a38ffc156032
|
4
|
+
data.tar.gz: de13347bd21397c4e211748eaab0557193a52250
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 058240c841658dbcc58c8c76a74e049afda210d17b4a7b7476a3dd3c55c1624f93692cf316e40724dca4565a8b0b7df463addc795482ee7dec5c5d1878db1e71
|
7
|
+
data.tar.gz: 2f7b48932434a3f06b22745e8973157806784d8b087ebae412f3aca06c90298ec383e6bebfb5732555d8a530c39e52cd28a972c5d14f6d68c79b253f34f9dfc3
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
K��iΑV�!�x�@^�|H��}K^חT�:q����CBu�ۗ�]%�|�q=*E,]����G��o��]��%�k��S�g"�A�D�y�u-].w��V#��T��a^��h�ெ�%�9{u���.z�Gt��ȸ��0�C�Bߐ�w� 8�S����nn��(��>���"�W`x��Բ�U�i�Q�c%KU�Zj��\�mX��Ó���dv ��Qr����}�YИ�3�6Z�!p��zc۩��IA�
|
data/lib/graph.rb
ADDED
@@ -0,0 +1,571 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# -*- coding: UTF-8 -*-
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
# A graph with nodes and edges
|
7
|
+
class Graph
|
8
|
+
|
9
|
+
# Return a new Graph which is the intersection of every given graph.
|
10
|
+
# Each node of the intersection is in every given graph (idem for edges).
|
11
|
+
# The last argument may be a hash of options.
|
12
|
+
# @option options [Boolean] +:same_fields+ use only fields which are in
|
13
|
+
# every graph to compare nodes/edges to perform
|
14
|
+
# the intersection
|
15
|
+
# @see Graph#&
|
16
|
+
# @see Graph.union
|
17
|
+
# @see Graph.xor
|
18
|
+
# @return [Graph]
|
19
|
+
def Graph::intersection(*graphs)
|
20
|
+
perform_graphs_group_op(*graphs, &:&)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return a new {Graph} which is the union of every given graph.
|
24
|
+
# Each node of the union is in one or more given graph(s) (idem for edges).
|
25
|
+
# The last argument may be a hash of options.
|
26
|
+
# @option options [Boolean] +:same_fields+ use only fields which are in
|
27
|
+
# every graph to compare nodes/edges to perform
|
28
|
+
# the union
|
29
|
+
# @see Graph#|
|
30
|
+
# @see Graph.intersection
|
31
|
+
# @see Graph.xor
|
32
|
+
# @return [Graph]
|
33
|
+
def Graph::union(*graphs)
|
34
|
+
perform_graphs_group_op(*graphs, &:|)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Perform a XOR operation on all given graphs, and returns the result.
|
38
|
+
# The last argument may be a hash of options.
|
39
|
+
# @option options [Boolean] :same_fields use only fields which are in every
|
40
|
+
# graph to compare nodes/edges to perform the XOR operation
|
41
|
+
# @see Graph#^
|
42
|
+
# @see Graph.union
|
43
|
+
# @see Graph.intersection
|
44
|
+
# @return [Graph]
|
45
|
+
def Graph::xor(*graphs)
|
46
|
+
perform_graphs_group_op(*graphs, &:^)
|
47
|
+
end
|
48
|
+
|
49
|
+
# A node. This class is just a wrapper around a hash of attributes. Before
|
50
|
+
# 0.1.6, nodes were simple hashs
|
51
|
+
# @since 0.1.6
|
52
|
+
class Node
|
53
|
+
|
54
|
+
# @return Node's attributes
|
55
|
+
attr_accessor :attrs
|
56
|
+
|
57
|
+
# Create a new Node
|
58
|
+
# @param attrs [Node, Hash]
|
59
|
+
def initialize(attrs=nil)
|
60
|
+
@attrs = attrs.is_a?(Node) ? attrs.attrs : attrs || {}
|
61
|
+
end
|
62
|
+
|
63
|
+
# compare two nodes
|
64
|
+
# @param other [Node]
|
65
|
+
# @return [Boolean]
|
66
|
+
def ==(other)
|
67
|
+
return false if !other.is_a?(Node)
|
68
|
+
|
69
|
+
@attrs == other.attrs
|
70
|
+
end
|
71
|
+
|
72
|
+
# Update the current node, like the +Hash#update+ method.
|
73
|
+
# @param h [Hash]
|
74
|
+
# @return [Node]
|
75
|
+
def update(h)
|
76
|
+
Node.new super(h)
|
77
|
+
end
|
78
|
+
|
79
|
+
def method_missing(method, *args, &block)
|
80
|
+
return @attrs[method.to_sym] if @attrs.has_key? method.to_sym
|
81
|
+
return @attrs[method.to_s] if @attrs.has_key? method.to_s
|
82
|
+
|
83
|
+
@attrs.send(method, *args, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
# An edge. This class is just a wrapper around a hash of
|
89
|
+
# attributes since before version 0.1.5 edges were simple hashes
|
90
|
+
# @since 0.1.6
|
91
|
+
class Edge
|
92
|
+
|
93
|
+
# @return Edge's attributes
|
94
|
+
attr_accessor :attrs
|
95
|
+
|
96
|
+
# Create a new edge
|
97
|
+
# @param attrs [Edge, Hash]
|
98
|
+
def initialize(attrs=nil)
|
99
|
+
@attrs = attrs.is_a?(Edge) ? attrs.attrs : attrs || {}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Compare two edges
|
103
|
+
# @param other [Edge]
|
104
|
+
# @return [Boolean]
|
105
|
+
def ==(other)
|
106
|
+
return false if !other.is_a?(Edge)
|
107
|
+
|
108
|
+
@attrs == other.attrs
|
109
|
+
end
|
110
|
+
|
111
|
+
# Update the current edge, like the +Hash#update+ method.
|
112
|
+
# @param h [Hash]
|
113
|
+
# @return [Edge]
|
114
|
+
def update(h)
|
115
|
+
Edge.new super(h)
|
116
|
+
end
|
117
|
+
|
118
|
+
def method_missing(method, *args, &block)
|
119
|
+
return @attrs[method.to_sym] if @attrs.has_key? method.to_sym
|
120
|
+
return @attrs[method.to_s] if @attrs.has_key? method.to_s
|
121
|
+
|
122
|
+
@attrs.send(method, *args, &block)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
# An array of Node objects
|
128
|
+
class NodeArray < Array
|
129
|
+
|
130
|
+
# Create a new +NodeArray+ from an existing +Array+.
|
131
|
+
# @param li [Array]
|
132
|
+
def initialize(li)
|
133
|
+
nodes = li.map { |n| n.is_a?(Node) ? n : Node.new(n) }
|
134
|
+
super(nodes)
|
135
|
+
@defaults = {}
|
136
|
+
end
|
137
|
+
|
138
|
+
# Set some default values for current elements.
|
139
|
+
# @note This method can be called multiple times.
|
140
|
+
# @param dict [Hash]
|
141
|
+
# @return [NodeArray]
|
142
|
+
# @example Set all nodes's 'created-at' value to '2012-05-03'
|
143
|
+
# myNodeList.set_default({'created-at'=>'2012-05-03'})
|
144
|
+
def set_default(dict)
|
145
|
+
@defaults.update(dict)
|
146
|
+
self.map! { |e| e.update(@defaults) }
|
147
|
+
end
|
148
|
+
|
149
|
+
# Add the given node at the end of the list
|
150
|
+
# @param n [Node]
|
151
|
+
# @return [NodeArray]
|
152
|
+
def push(n)
|
153
|
+
if (!n.is_a?(Hash) && !n.is_a?(Node))
|
154
|
+
raise TypeError.new "#{n.inspect} is not an Hash nor a Node!"
|
155
|
+
end
|
156
|
+
|
157
|
+
n = Node.new(n) if (n.is_a?(Hash))
|
158
|
+
|
159
|
+
super(n.clone.update(@defaults))
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
# An array of Edge objects
|
165
|
+
class EdgeArray < Array
|
166
|
+
|
167
|
+
# Create a new +EdgeArray+ from an existing +Array+.
|
168
|
+
# @param li [Array<Edge, Hash>]
|
169
|
+
def initialize(li)
|
170
|
+
edges = li.map { |n| n.is_a?(Edge) ? n : Edge.new(n) }
|
171
|
+
super(edges)
|
172
|
+
@defaults = {}
|
173
|
+
end
|
174
|
+
|
175
|
+
# Set some default values for current elements.
|
176
|
+
# @note This method can be called multiple times.
|
177
|
+
# @example Set all edges's 'created-at' value to '2012-05-03'
|
178
|
+
# myEdgeList.set_default({'created-at'=>'2012-05-03'})
|
179
|
+
# @param dict [Hash]
|
180
|
+
def set_default(dict)
|
181
|
+
@defaults.update(dict)
|
182
|
+
self.map! { |e| e.update(@defaults) }
|
183
|
+
end
|
184
|
+
|
185
|
+
# Add the given edge at the end of the list
|
186
|
+
# @param e [Edge]
|
187
|
+
# @return [EdgeArray]
|
188
|
+
def push(e)
|
189
|
+
if (!e.is_a?(Hash) && !e.is_a?(Edge))
|
190
|
+
raise TypeError.new "#{e.inspect} is not an Hash nor an Edge!"
|
191
|
+
end
|
192
|
+
|
193
|
+
e = Edge.new(e) if (e.is_a?(Hash))
|
194
|
+
|
195
|
+
super(e.clone.update(@defaults))
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
# @return [NodeArray] the graph's nodes
|
201
|
+
attr_accessor :nodes
|
202
|
+
|
203
|
+
# @return [EdgeArray] the graph's edges
|
204
|
+
attr_accessor :edges
|
205
|
+
|
206
|
+
# @return [Hash] the graph's attributes
|
207
|
+
attr_accessor :attrs
|
208
|
+
|
209
|
+
# Create a new +Graph+ from one set of nodes and one set of edges
|
210
|
+
# @param nodes [Array] Nodes of the graph
|
211
|
+
# @param edges [Array] Edges of the graph
|
212
|
+
def initialize(nodes=nil, edges=nil)
|
213
|
+
@nodes = NodeArray.new(nodes || [])
|
214
|
+
@edges = EdgeArray.new(edges || [])
|
215
|
+
@attrs = { :directed => true }
|
216
|
+
end
|
217
|
+
|
218
|
+
# Test if current graph has same nodes and edges as the other
|
219
|
+
# graph.
|
220
|
+
# @param other [Graph]
|
221
|
+
# @return [Boolean]
|
222
|
+
def ==(other)
|
223
|
+
if (!other.is_a?(Graph))
|
224
|
+
return false
|
225
|
+
end
|
226
|
+
(self.nodes === other.nodes) && (self.edges === other.edges)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Perform an intersection between the current graph and the other.
|
230
|
+
# Returns a new Graph which nodes are both in the current graph and
|
231
|
+
# the other (idem for edges).
|
232
|
+
# @param other [Graph]
|
233
|
+
# @return [Graph]
|
234
|
+
# @see Graph#^
|
235
|
+
# @see Graph.intersection
|
236
|
+
def &(other)
|
237
|
+
return unless other.is_a?(Graph)
|
238
|
+
|
239
|
+
nodes = @nodes & other.nodes
|
240
|
+
edges = @edges & other.edges
|
241
|
+
|
242
|
+
Graph.new(nodes, edges)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Perform a XOR operation between the current graph and the other. Returns
|
246
|
+
# a new Graph which nodes are in the current graph or in the other, but not
|
247
|
+
# in both (idem for edges).
|
248
|
+
# @param other [Graph]
|
249
|
+
# @return [Graph]
|
250
|
+
# @see Graph#&
|
251
|
+
def ^(other)
|
252
|
+
return unless other.is_a?(Graph)
|
253
|
+
|
254
|
+
nodes = (@nodes - other.nodes) + (other.nodes - @nodes)
|
255
|
+
edges = (@edges - other.edges) + (other.edges - @edges)
|
256
|
+
|
257
|
+
Graph.new(nodes, edges)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Add two graphs, keeping duplicate nodes and edges
|
261
|
+
# @param other [Graph]
|
262
|
+
# @return [Graph]
|
263
|
+
def +(other)
|
264
|
+
return unless other.is_a?(Graph)
|
265
|
+
|
266
|
+
nodes = @nodes + other.nodes
|
267
|
+
edges = @edges + other.edges
|
268
|
+
|
269
|
+
Graph.new(nodes, edges)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Perform an OR operation on the current Graph and the given one. Returns a
|
273
|
+
# new graph which every node is in the current Graph and/or the other
|
274
|
+
# (idem for edges).
|
275
|
+
# @param other [Graph]
|
276
|
+
# @return [Graph]
|
277
|
+
def |(other)
|
278
|
+
return unless other.is_a?(Graph)
|
279
|
+
|
280
|
+
nodes = @nodes | other.nodes
|
281
|
+
edges = @edges | other.edges
|
282
|
+
|
283
|
+
Graph.new(nodes, edges)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns a new Graph, which is a copy of the current graph without nodes
|
287
|
+
# and edges which are in the given Graph.
|
288
|
+
# @param other [Graph]
|
289
|
+
# @return [Graph]
|
290
|
+
def -(other)
|
291
|
+
return unless other.is_a?(Graph)
|
292
|
+
|
293
|
+
nodes = @nodes - other.nodes
|
294
|
+
edges = @edges - other.edges
|
295
|
+
|
296
|
+
Graph.new(nodes, edges)
|
297
|
+
end
|
298
|
+
|
299
|
+
# (see Graph#-)
|
300
|
+
def not(other)
|
301
|
+
self - other
|
302
|
+
end
|
303
|
+
|
304
|
+
# Return true if the Graph is directed.
|
305
|
+
# @return [Boolean]
|
306
|
+
# @see Graph.attrs
|
307
|
+
def directed?()
|
308
|
+
!!self.attrs[:directed]
|
309
|
+
end
|
310
|
+
|
311
|
+
# Clone the current graph. All nodes and edges are also cloned. A new Graph
|
312
|
+
# is returned.
|
313
|
+
# @return [Graph] a new graph
|
314
|
+
def clone()
|
315
|
+
g = Graph.new
|
316
|
+
g.nodes = self.nodes.clone
|
317
|
+
g.edges = self.edges.clone
|
318
|
+
|
319
|
+
g.nodes.map! {|h| h.clone}
|
320
|
+
g.edges.map! {|h| h.clone}
|
321
|
+
|
322
|
+
g
|
323
|
+
end
|
324
|
+
|
325
|
+
# Write the current Graph into a file.
|
326
|
+
# @param filename [String] A valid filename
|
327
|
+
# @param opts [Hash] A customizable set of options
|
328
|
+
# @return []
|
329
|
+
# @option opts [Boolean] :gephi Should be <tt>true</tt> if the file will be
|
330
|
+
# used with Gephi.
|
331
|
+
def write(filename, opts=nil)
|
332
|
+
|
333
|
+
has_ext = filename.split('.')
|
334
|
+
ext = (has_ext.length>1) ? has_ext[-1] : 'unknow'
|
335
|
+
|
336
|
+
m = (self.methods - Object.methods).map {|e| e.to_s}
|
337
|
+
|
338
|
+
if (m.include? 'write_'+ext.downcase)
|
339
|
+
self.send('write_'+ext.downcase, filename, opts)
|
340
|
+
|
341
|
+
elsif (ext == 'unknow' || ext == 'yml')
|
342
|
+
# YAML (default)
|
343
|
+
nodes = self.nodes.to_a
|
344
|
+
edges = self.edges.to_a
|
345
|
+
|
346
|
+
data = {'nodes'=>nodes, 'edges'=>edges}.to_yaml
|
347
|
+
f = open(filename, 'w')
|
348
|
+
f.write(data)
|
349
|
+
f.close
|
350
|
+
else
|
351
|
+
raise NoMethodError.new("No method to handle #{ext} file extension.")
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# Return the degree of the node n in the current graph, i.e. the number
|
356
|
+
# of edges which are connected to this node. Note that this is useful
|
357
|
+
# only for a undirected graph, for a directed one, you should use
|
358
|
+
# Graph#in_degree_of and/or Graph#out_degree_of.
|
359
|
+
#
|
360
|
+
# Edges must have the +node1+ and +node2+ attributes, which must contain
|
361
|
+
# the +label+ attributes of nodes.
|
362
|
+
#
|
363
|
+
# @param n [Node,String] A node or a label of one
|
364
|
+
# @return [Integer]
|
365
|
+
# @see Graph#in_degree_of
|
366
|
+
# @see Graph#out_degree_of
|
367
|
+
def degree_of(n)
|
368
|
+
label = Graph::get_label(n)
|
369
|
+
|
370
|
+
degree = 0
|
371
|
+
|
372
|
+
# This is more efficient than in_degree_of(n)+out_degree_of(n)
|
373
|
+
# since it goes only once through the edges array
|
374
|
+
self.edges.each do |e|
|
375
|
+
degree += 1 if (e['node1'] || e[:node1]).to_s == label
|
376
|
+
degree += 1 if (e['node2'] || e[:node2]).to_s == label
|
377
|
+
end
|
378
|
+
|
379
|
+
degree
|
380
|
+
end
|
381
|
+
|
382
|
+
# Return the “in degree” of the node n in the current graph, i.e. the
|
383
|
+
# number of edges which are directed to this node. Note that the graph must
|
384
|
+
# be oriented.
|
385
|
+
#
|
386
|
+
# Edges must have the +node1+ and +node2+ attributes, which must contain
|
387
|
+
# the +label+ attributes of nodes.
|
388
|
+
#
|
389
|
+
# @param n [Node,String] A node or a label of one
|
390
|
+
# @return [Integer]
|
391
|
+
# @see Graph#degree_of
|
392
|
+
# @see Graph#out_degree_of
|
393
|
+
def in_degree_of(n)
|
394
|
+
label = Graph::get_label(n)
|
395
|
+
|
396
|
+
degree = 0
|
397
|
+
|
398
|
+
self.edges.each do |e|
|
399
|
+
degree += 1 if (e['node2'] || e[:node2]).to_s == label
|
400
|
+
end
|
401
|
+
|
402
|
+
degree
|
403
|
+
end
|
404
|
+
|
405
|
+
# Return the “out degree” of the node n in the current graph, i.e. the
|
406
|
+
# number of edges which are directed from this node. Note that the graph
|
407
|
+
# must be oriented.
|
408
|
+
#
|
409
|
+
# Edges must have the +node1+ and +node2+ attributes, which must contain
|
410
|
+
# the +label+ attributes of nodes.
|
411
|
+
#
|
412
|
+
# @param n [Node,String] A node or a node's label
|
413
|
+
# @return [Integer]
|
414
|
+
# @see Graph#degree_of
|
415
|
+
# @see Graph#out_degree_of
|
416
|
+
def out_degree_of(n)
|
417
|
+
label = Graph::get_label(n)
|
418
|
+
|
419
|
+
degree = 0
|
420
|
+
|
421
|
+
self.edges.each do |e|
|
422
|
+
degree += 1 if (e['node1'] || e[:node1]).to_s == label
|
423
|
+
end
|
424
|
+
|
425
|
+
degree
|
426
|
+
end
|
427
|
+
|
428
|
+
# return the first node which mach the given label in the current graph
|
429
|
+
# @param label [String] A node's label
|
430
|
+
# @return [Node]
|
431
|
+
def get_node(label)
|
432
|
+
label = Graph::get_label(label)
|
433
|
+
|
434
|
+
self.nodes.find { |n| n.label == label }
|
435
|
+
end
|
436
|
+
|
437
|
+
# return an array of the neighbours of a node in the current graph.
|
438
|
+
# @param n [Node,String] A node with a 'label' or :label attribute, or a
|
439
|
+
# string
|
440
|
+
# @return [Array<Node>]
|
441
|
+
def get_neighbours(n)
|
442
|
+
|
443
|
+
label = Graph::get_label n
|
444
|
+
neighbours = NodeArray.new []
|
445
|
+
|
446
|
+
self.edges.each do |e|
|
447
|
+
|
448
|
+
l1 = e[:node1] || e['node1']
|
449
|
+
l2 = e[:node2] || e['node2']
|
450
|
+
|
451
|
+
if l2 && l1 == label
|
452
|
+
|
453
|
+
n2 = self.get_node l2
|
454
|
+
|
455
|
+
unless n2.nil? || neighbours.include?(n2)
|
456
|
+
|
457
|
+
neighbours.push(n2)
|
458
|
+
|
459
|
+
end
|
460
|
+
|
461
|
+
end
|
462
|
+
|
463
|
+
if l1 && l2 == label && !self.directed?
|
464
|
+
|
465
|
+
n1 = self.get_node l1
|
466
|
+
|
467
|
+
unless n1.nil? || neighbours.include?(n1)
|
468
|
+
|
469
|
+
neighbours.push(n1)
|
470
|
+
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|
474
|
+
|
475
|
+
end
|
476
|
+
|
477
|
+
neighbours
|
478
|
+
|
479
|
+
end
|
480
|
+
|
481
|
+
# return the label of a node. Raise a TypeError exception if the argument
|
482
|
+
# is not a Node nor a String object.
|
483
|
+
# @param n [Node,String] A node with a 'label' or :label attribute, or a
|
484
|
+
# string
|
485
|
+
# @return [String]
|
486
|
+
def Graph::get_label(n)
|
487
|
+
label = n.is_a?(Node) \
|
488
|
+
? n.label.to_s \
|
489
|
+
: n.is_a?(String) ? n : nil
|
490
|
+
|
491
|
+
if label.nil?
|
492
|
+
raise TypeError.new("#{n.inspect} must be a Node or String object.")
|
493
|
+
end
|
494
|
+
|
495
|
+
label
|
496
|
+
end
|
497
|
+
|
498
|
+
private
|
499
|
+
|
500
|
+
# return the provided set of graphs, from which every node/edge label which
|
501
|
+
# is not in all graphs has been removed. So every returned graph has same
|
502
|
+
# node/edge labels than each other
|
503
|
+
def Graph::keep_only_same_fields(*graphs)
|
504
|
+
graphs.map! {|g| g.clone}
|
505
|
+
|
506
|
+
# every first node of every graphs
|
507
|
+
nodes_ref = graphs.map {|g| g.nodes[0] || {}}
|
508
|
+
# every first edge of every graphs
|
509
|
+
edges_ref = graphs.map {|g| g.edges[0] || {}}
|
510
|
+
|
511
|
+
nodes_keys_ref = nodes_ref.map {|n| n.keys}
|
512
|
+
edges_keys_ref = edges_ref.map {|e| e.keys}
|
513
|
+
|
514
|
+
# keep only same keys
|
515
|
+
nodes_keys_uniq = nodes_keys_ref.inject {|i,e| i &= e}
|
516
|
+
edges_keys_uniq = edges_keys_ref.inject {|i,e| i &= e}
|
517
|
+
|
518
|
+
graphs.map do |g|
|
519
|
+
g.nodes.map! do |n|
|
520
|
+
|
521
|
+
newnode = {}
|
522
|
+
|
523
|
+
n.each_key do |k|
|
524
|
+
newnode[k] = n[k] if nodes_keys_uniq.include?(k)
|
525
|
+
end
|
526
|
+
|
527
|
+
newnode
|
528
|
+
end
|
529
|
+
g.edges.map! do |n|
|
530
|
+
|
531
|
+
newedge = {}
|
532
|
+
|
533
|
+
n.each_key do |k|
|
534
|
+
newedge[k] = n[k] if edges_keys_uniq.include?(k)
|
535
|
+
end
|
536
|
+
|
537
|
+
newedge
|
538
|
+
end
|
539
|
+
g
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
# Perform an operation on a graphs group
|
544
|
+
# @param graphs [Array<Graph>]
|
545
|
+
# @param block [Block] operation
|
546
|
+
# @return [Graph]
|
547
|
+
def Graph::perform_graphs_group_op(*graphs, &block)
|
548
|
+
return if graphs.length == 0
|
549
|
+
|
550
|
+
# options
|
551
|
+
opts = {}
|
552
|
+
|
553
|
+
# if the last arg is an hash, use it as a set of options and remove it
|
554
|
+
# from the arguments
|
555
|
+
if graphs[-1].is_a?(Hash)
|
556
|
+
return if graphs.length == 1
|
557
|
+
opts = graphs.pop
|
558
|
+
end
|
559
|
+
|
560
|
+
# return nil if one argument is not a graph
|
561
|
+
graphs.each do |g|
|
562
|
+
return if !g.is_a?(Graph)
|
563
|
+
end
|
564
|
+
|
565
|
+
# if :same_fields option is set, call `keep_only_same_fields` function
|
566
|
+
graphs = keep_only_same_fields(*graphs) if opts[:same_fields]
|
567
|
+
|
568
|
+
# perform an and operation on all graph list
|
569
|
+
graphs.inject(&block)
|
570
|
+
end
|
571
|
+
end
|