graphs 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
Binary file
@@ -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�
@@ -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