graphs 0.2.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
- 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
|