plexus 0.5.4 → 0.5.5

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.
Files changed (70) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +37 -0
  3. data/README.md +208 -0
  4. data/Rakefile +25 -0
  5. data/lib/plexus.rb +90 -0
  6. data/lib/plexus/adjacency_graph.rb +225 -0
  7. data/lib/plexus/arc.rb +60 -0
  8. data/lib/plexus/arc_number.rb +50 -0
  9. data/lib/plexus/biconnected.rb +84 -0
  10. data/lib/plexus/chinese_postman.rb +91 -0
  11. data/lib/plexus/classes/graph_classes.rb +28 -0
  12. data/lib/plexus/common.rb +63 -0
  13. data/lib/plexus/comparability.rb +63 -0
  14. data/lib/plexus/directed_graph.rb +78 -0
  15. data/lib/plexus/directed_graph/algorithms.rb +95 -0
  16. data/lib/plexus/directed_graph/distance.rb +167 -0
  17. data/lib/plexus/dot.rb +94 -0
  18. data/lib/plexus/edge.rb +38 -0
  19. data/lib/plexus/ext.rb +79 -0
  20. data/lib/plexus/graph.rb +628 -0
  21. data/lib/plexus/graph_api.rb +35 -0
  22. data/lib/plexus/labels.rb +112 -0
  23. data/lib/plexus/maximum_flow.rb +77 -0
  24. data/lib/plexus/ruby_compatibility.rb +17 -0
  25. data/lib/plexus/search.rb +510 -0
  26. data/lib/plexus/strong_components.rb +93 -0
  27. data/lib/plexus/support/support.rb +9 -0
  28. data/lib/plexus/undirected_graph.rb +56 -0
  29. data/lib/plexus/undirected_graph/algorithms.rb +90 -0
  30. data/lib/plexus/version.rb +6 -0
  31. data/spec/biconnected_spec.rb +27 -0
  32. data/spec/chinese_postman_spec.rb +27 -0
  33. data/spec/community_spec.rb +44 -0
  34. data/spec/complement_spec.rb +27 -0
  35. data/spec/digraph_distance_spec.rb +121 -0
  36. data/spec/digraph_spec.rb +339 -0
  37. data/spec/dot_spec.rb +48 -0
  38. data/spec/edge_spec.rb +158 -0
  39. data/spec/inspection_spec.rb +38 -0
  40. data/spec/multi_edge_spec.rb +32 -0
  41. data/spec/neighborhood_spec.rb +36 -0
  42. data/spec/properties_spec.rb +146 -0
  43. data/spec/search_spec.rb +227 -0
  44. data/spec/spec.opts +4 -0
  45. data/spec/spec_helper.rb +59 -0
  46. data/spec/strong_components_spec.rb +61 -0
  47. data/spec/triangulated_spec.rb +125 -0
  48. data/spec/undirected_graph_spec.rb +220 -0
  49. data/vendor/priority-queue/CHANGELOG +33 -0
  50. data/vendor/priority-queue/Makefile +140 -0
  51. data/vendor/priority-queue/README +133 -0
  52. data/vendor/priority-queue/benchmark/dijkstra.rb +171 -0
  53. data/vendor/priority-queue/compare_comments.rb +49 -0
  54. data/vendor/priority-queue/doc/c-vs-rb.png +0 -0
  55. data/vendor/priority-queue/doc/compare_big.gp +14 -0
  56. data/vendor/priority-queue/doc/compare_big.png +0 -0
  57. data/vendor/priority-queue/doc/compare_small.gp +15 -0
  58. data/vendor/priority-queue/doc/compare_small.png +0 -0
  59. data/vendor/priority-queue/doc/results.csv +37 -0
  60. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
  61. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/priority_queue.c +947 -0
  62. data/vendor/priority-queue/lib/priority_queue.rb +14 -0
  63. data/vendor/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
  64. data/vendor/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
  65. data/vendor/priority-queue/lib/priority_queue/ruby_priority_queue.rb +526 -0
  66. data/vendor/priority-queue/priority_queue.so +0 -0
  67. data/vendor/priority-queue/setup.rb +1551 -0
  68. data/vendor/priority-queue/test/priority_queue_test.rb +371 -0
  69. data/vendor/rdot.rb +360 -0
  70. metadata +100 -10
@@ -0,0 +1,60 @@
1
+ module Plexus
2
+ # Arc includes classes for representing egdes of directed and
3
+ # undirected graphs. There is no need for a Vertex class, because any ruby
4
+ # object can be a vertex of a graph.
5
+ #
6
+ # Arc's base is a Struct with a :source, a :target and a :label.
7
+ Struct.new("ArcBase", :source, :target, :label)
8
+
9
+ class Arc < Struct::ArcBase
10
+ def initialize(p_source, p_target, p_label = nil)
11
+ super(p_source, p_target, p_label)
12
+ end
13
+
14
+ # Ignore labels for equality.
15
+ def eql?(other)
16
+ self.class == other.class and target == other.target and source == other.source
17
+ end
18
+ alias == eql?
19
+
20
+ # Returns (v,u) if self == (u,v).
21
+ def reverse()
22
+ self.class.new(target, source, label)
23
+ end
24
+
25
+ # Sort support.
26
+ def <=>(rhs)
27
+ [source, target] <=> [rhs.source, rhs.target]
28
+ end
29
+
30
+ # Arc[1,2].to_s => "(1-2)"
31
+ # Arc[1,2,'test'].to_s => "(1-2 test)"
32
+ def to_s
33
+ l = label ? " '#{label.to_s}'" : ''
34
+ "(#{source}-#{target}#{l})"
35
+ end
36
+
37
+ # Hash is defined in such a way that label is not
38
+ # part of the hash value
39
+ # FIXME: I had to get rid of that in order to make to_dot_graph
40
+ # work, but I can't figure it out (doesn't show up in the stack!)
41
+ def hash
42
+ source.hash ^ (target.hash + 1)
43
+ end
44
+
45
+ # Shortcut constructor.
46
+ #
47
+ # Instead of Arc.new(1,2) one can use Arc[1,2].
48
+ def self.[](p_source, p_target, p_label = nil)
49
+ new(p_source, p_target, p_label)
50
+ end
51
+
52
+ def inspect
53
+ "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]"
54
+ end
55
+ end
56
+
57
+ class MultiArc < Arc
58
+ include ArcNumber
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ module Plexus
2
+ # This module handles internal numbering of edges in order to differente between mutliple edges.
3
+ module ArcNumber
4
+ # Used to differentiate between mutli-edges
5
+ attr_accessor :number
6
+
7
+ def initialize(p_source, p_target, p_number, p_label = nil)
8
+ self.number = p_number
9
+ super(p_source, p_target, p_label)
10
+ end
11
+
12
+ # Returns (v,u) if self == (u,v).
13
+ def reverse
14
+ self.class.new(target, source, number, label)
15
+ end
16
+
17
+ # Allow for hashing of self loops.
18
+ def hash
19
+ super ^ number.hash
20
+ end
21
+
22
+ def to_s
23
+ super + "[#{number}]"
24
+ end
25
+
26
+ def <=>(rhs)
27
+ (result = super(rhs)) == 0 ? number <=> rhs.number : result
28
+ end
29
+
30
+ def inspect
31
+ "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]"
32
+ end
33
+
34
+ def eql?(rhs)
35
+ super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number)
36
+ end
37
+
38
+ def ==(rhs)
39
+ eql?(rhs)
40
+ end
41
+
42
+ # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2]
43
+ def self.included(cl)
44
+ # FIXME: lacks a cl.class_eval, no?
45
+ def cl.[](p_source, p_target, p_number = nil, p_label = nil)
46
+ new(p_source, p_target, p_number, p_label)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,84 @@
1
+ module Plexus
2
+
3
+ # Biconnected is a module for adding the biconnected algorithm to
4
+ # UndirectedGraphs
5
+ module Biconnected
6
+
7
+ # biconnected computes the biconnected subgraphs
8
+ # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan
9
+ # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on
10
+ # Computing, 1(2):146-160, 1972
11
+ #
12
+ # The output of the algorithm is a pair, the first value is an
13
+ # array of biconnected subgraphs. The second is the set of
14
+ # articulation vertices.
15
+ #
16
+ # A connected graph is biconnected if the removal of any single vertex
17
+ # (and all edges incident on that vertex) cannot disconnect the graph.
18
+ # More generally, the biconnected components of a graph are the maximal
19
+ # subsets of vertices such that the removal of a vertex from a particular
20
+ # component will not disconnect the component. Unlike connected components,
21
+ # vertices may belong to multiple biconnected components: those vertices
22
+ # that belong to more than one biconnected component are called articulation
23
+ # points or, equivalently, cut vertices. Articulation points are vertices
24
+ # whose removal would increase the number of connected components in the graph.
25
+ # Thus, a graph without articulation points is biconnected.
26
+ def biconnected
27
+ dfs_num = 0
28
+ number = {}; predecessor = {}; low_point = {}
29
+ stack = []; result = []; articulation= []
30
+
31
+ root_vertex = Proc.new {|v| predecessor[v]=v }
32
+ enter_vertex = Proc.new {|u| number[u]=low_point[u]=(dfs_num+=1) }
33
+ tree_edge = Proc.new do |e|
34
+ stack.push(e)
35
+ predecessor[e.target] = e.source
36
+ end
37
+ back_edge = Proc.new do |e|
38
+ if e.target != predecessor[e.source]
39
+ stack.push(e)
40
+ low_point[e.source] = [low_point[e.source], number[e.target]].min
41
+ end
42
+ end
43
+ exit_vertex = Proc.new do |u|
44
+ parent = predecessor[u]
45
+ is_articulation_point = false
46
+ if number[parent] > number[u]
47
+ parent = predecessor[parent]
48
+ is_articulation_point = true
49
+ end
50
+ if parent == u
51
+ is_articulation_point = false if (number[u] + 1) == number[predecessor[u]]
52
+ else
53
+ low_point[parent] = [low_point[parent], low_point[u]].min
54
+ if low_point[u] >= number[parent]
55
+ if number[parent] > number[predecessor[parent]]
56
+ predecessor[u] = predecessor[parent]
57
+ predecessor[parent] = u
58
+ end
59
+ result << (component = self.class.new)
60
+ while number[stack[-1].source] >= number[u]
61
+ component.add_edge!(stack.pop)
62
+ end
63
+ component.add_edge!(stack.pop)
64
+ if stack.empty?
65
+ predecessor[u] = parent
66
+ predecessor[parent] = u
67
+ end
68
+ end
69
+ end
70
+ articulation << u if is_articulation_point
71
+ end
72
+
73
+ # Execute depth first search
74
+ dfs({:root_vertex => root_vertex,
75
+ :enter_vertex => enter_vertex,
76
+ :tree_edge => tree_edge,
77
+ :back_edge => back_edge,
78
+ :exit_vertex => exit_vertex})
79
+
80
+ [result, articulation]
81
+ end # biconnected
82
+
83
+ end # Biconnected
84
+ end # Plexus
@@ -0,0 +1,91 @@
1
+ module Plexus
2
+ module ChinesePostman
3
+
4
+ # Returns the shortest walk that traverses all arcs at least
5
+ # once, returning to the specified start node.
6
+ def closed_chinese_postman_tour(start, weight=nil, zero=0)
7
+ cost, path, delta = floyd_warshall(weight, zero)
8
+ return nil unless cp_valid_least_cost? cost, zero
9
+ positive, negative = cp_unbalanced(delta)
10
+ f = cp_find_feasible(delta, positive, negative, zero)
11
+ while cp_improve(f, positive, negative, cost, zero); end
12
+ cp_euler_circuit(start, f, path)
13
+ end
14
+
15
+ private
16
+
17
+ def cp_euler_circuit(start, f, path) # :nodoc:
18
+ circuit = [u=v=start]
19
+ bridge_taken = Hash.new {|h,k| h[k] = Hash.new}
20
+ until v.nil?
21
+ if v=f[u].keys.detect {|k| f[u][k] > 0}
22
+ f[u][v] -= 1
23
+ circuit << (u = path[u][v]) while u != v
24
+ else
25
+ unless bridge_taken[u][bridge = path[u][start]]
26
+ v = vertices.detect {|v1| v1 != bridge && edge?(u,v1) && !bridge_taken[u][v1]} || bridge
27
+ bridge_taken[u][v] = true
28
+ circuit << v
29
+ end
30
+ end
31
+ u=v
32
+ end; circuit
33
+ end
34
+
35
+ def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc:
36
+ u = start; k = nil
37
+ begin
38
+ v = path[u][start]
39
+ k = f[v][u] if cost[u][v] < zero and (k.nil? || k > f[v][u])
40
+ end until (u=v) != start
41
+ u = start
42
+ begin
43
+ v = path[u][start]
44
+ cost[u][v] < zero ? f[v][u] -= k : f[u][v] += k
45
+ end until (u=v) != start
46
+ true # This routine always returns true to make cp_improve easier
47
+ end
48
+
49
+ def cp_improve(f, positive, negative, cost, zero) # :nodoc:
50
+ residual = self.class.new
51
+ negative.each do |u|
52
+ positive.each do |v|
53
+ residual.add_edge!(u,v,cost[u][v])
54
+ residual.add_edge!(v,u,-cost[u][v]) if f[u][v] != 0
55
+ end
56
+ end
57
+ r_cost, r_path, r_delta = residual.floyd_warshall(nil, zero)
58
+ i = residual.vertices.detect {|v| r_cost[v][v] and r_cost[v][v] < zero}
59
+ i ? cp_cancel_cycle(r_cost, r_path, f, i) : false
60
+ end
61
+
62
+ def cp_find_feasible(delta, positive, negative, zero) # :nodoc:
63
+ f = Hash.new {|h,k| h[k] = Hash.new}
64
+ negative.each do |i|
65
+ positive.each do |j|
66
+ f[i][j] = -delta[i] < delta[j] ? -delta[i] : delta[j]
67
+ delta[i] += f[i][j]
68
+ delta[j] -= f[i][j]
69
+ end
70
+ end; f
71
+ end
72
+
73
+ def cp_valid_least_cost?(c, zero) # :nodoc:
74
+ vertices.each do |i|
75
+ vertices.each do |j|
76
+ return false unless c[i][j] and c[i][j] >= zero
77
+ end
78
+ end; true
79
+ end
80
+
81
+ def cp_unbalanced(delta) # :nodoc:
82
+ negative = []; positive = []
83
+ vertices.each do |v|
84
+ negative << v if delta[v] < 0
85
+ positive << v if delta[v] > 0
86
+ end; [positive, negative]
87
+ end
88
+
89
+ end # Chinese Postman
90
+ end # Plexus
91
+
@@ -0,0 +1,28 @@
1
+ module Plexus
2
+ # A generic {GraphBuilder Graph} class you can inherit from.
3
+ class Graph; include GraphBuilder; end
4
+
5
+ # A generic {AdjacencyGraphBuilder AdjacencyGraph} class you can inherit from.
6
+ class AdjacencyGraph < Graph; include AdjacencyGraphBuilder; end
7
+
8
+ # A generic {DirectedGraphBuilder DirectedGraph} class you can inherit from.
9
+ class DirectedGraph < Graph; include DirectedGraphBuilder; end
10
+
11
+ # A generic {DigraphBuilder Digraph} class you can inherit from.
12
+ class Digraph < Graph; include DigraphBuilder; end
13
+
14
+ # A generic {DirectedPseudoGraphBuilder DirectedPseudoGraph} class you can inherit from.
15
+ class DirectedPseudoGraph < Graph; include DirectedPseudoGraphBuilder; end
16
+
17
+ # A generic {DirectedMultiGraphBuilder DirectedMultiGraph} class you can inherit from.
18
+ class DirectedMultiGraph < Graph; include DirectedMultiGraphBuilder; end
19
+
20
+ # A generic {UndirectedGraphBuilder UndirectedGraph} class you can inherit from.
21
+ class UndirectedGraph < Graph; include UndirectedGraphBuilder; end
22
+
23
+ # A generic {UndirectedPseudoGraphBuilder UndirectedPseudoGraph} class you can inherit from.
24
+ class UndirectedPseudoGraph < Graph; include UndirectedPseudoGraphBuilder; end
25
+
26
+ # A generic {UndirectedMultiGraphBuilder UndirectedMultiGraph} class you can inherit from.
27
+ class UndirectedMultiGraph < Graph; include UndirectedMultiGraphBuilder; end
28
+ end
@@ -0,0 +1,63 @@
1
+ module Plexus
2
+
3
+ # This class defines a cycle graph of size n.
4
+ # This is easily done by using the base Graph
5
+ # class and implemeting the minimum methods needed to
6
+ # make it work. This is a good example to look
7
+ # at for making one's own graph classes.
8
+ module CycleBuilder
9
+ def initialize(n)
10
+ @size = n;
11
+ end
12
+
13
+ def directed?
14
+ false
15
+ end
16
+
17
+ def vertices
18
+ (1..@size).to_a
19
+ end
20
+
21
+ def vertex?(v)
22
+ v > 0 and v <= @size
23
+ end
24
+
25
+ def edge?(u,v = nil)
26
+ u, v = [u.source, v.target] if u.is_a? Plexus::Arc
27
+ vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1))
28
+ end
29
+
30
+ def edges
31
+ Array.new(@size) { |i| Plexus::Edge[i+1, (i+1) == @size ? 1 : i+2]}
32
+ end
33
+ end # CycleBuilder
34
+
35
+ # This class defines a complete graph of size n.
36
+ # This is easily done by using the base Graph
37
+ # class and implemeting the minimum methods needed to
38
+ # make it work. This is a good example to look
39
+ # at for making one's own graph classes.
40
+ module CompleteBuilder
41
+ include CycleBuilder
42
+
43
+ def initialize(n)
44
+ @size = n
45
+ @edges = nil
46
+ end
47
+
48
+ def edges
49
+ return @edges if @edges # cache edges
50
+ @edges = []
51
+ @size.times do |u|
52
+ @size.times { |v| @edges << Plexus::Edge[u+1, v+1]}
53
+ end
54
+ @edges
55
+ end
56
+
57
+ def edge?(u, v = nil)
58
+ u, v = [u.source, v.target] if u.kind_of? Plexus::Arc
59
+ vertex?(u) && vertex?(v)
60
+ end
61
+ end # CompleteBuilder
62
+
63
+ end # Plexus
@@ -0,0 +1,63 @@
1
+ module Plexus
2
+ module Comparability
3
+
4
+ # A comparability graph is an UndirectedGraph that has a transitive
5
+ # orientation. This returns a boolean that says if this graph
6
+ # is a comparability graph.
7
+ def comparability?() gamma_decomposition[1]; end
8
+
9
+ # Returns an array with two values, the first being a hash of edges
10
+ # with a number containing their class assignment, the second valud
11
+ # is a boolean which states whether or not the graph is a
12
+ # comparability graph
13
+ #
14
+ # Complexity in time O(d*|E|) where d is the maximum degree of a vertex
15
+ # Complexity in space O(|V|+|E|)
16
+ def gamma_decomposition
17
+ k = 0; comparability=true; classification={}
18
+ edges.map {|edge| [edge.source,edge.target]}.each do |e|
19
+ if classification[e].nil?
20
+ k += 1
21
+ classification[e] = k; classification[e.reverse] = -k
22
+ comparability &&= plexus_comparability_explore(e, k, classification)
23
+ end
24
+ end; [classification, comparability]
25
+ end
26
+
27
+ # Returns one of the possible transitive orientations of
28
+ # the UndirectedGraph as a Digraph
29
+ def transitive_orientation(digraph_class=Digraph)
30
+ raise NotImplementError
31
+ end
32
+
33
+ private
34
+
35
+ # Taken from Figure 5.10, on pg. 130 of Martin Golumbic's, _Algorithmic_Graph_
36
+ # _Theory_and_Perfect_Graphs.
37
+ def plexus_comparability_explore(edge, k, classification, space='')
38
+ ret = plexus_comparability_explore_inner(edge, k, classification, :forward, space)
39
+ plexus_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret
40
+ end
41
+
42
+ def plexus_comparability_explore_inner(edge, k, classification, direction,space)
43
+ comparability = true
44
+ adj_target = adjacent(edge[1])
45
+ adjacent(edge[0]).select do |mt|
46
+ (classification[[edge[1],mt]] || k).abs < k or
47
+ not adj_target.any? {|adj_t| adj_t == mt}
48
+ end.each do |m|
49
+ e = (direction == :forward) ? [edge[0], m] : [m,edge[0]]
50
+ if classification[e].nil?
51
+ classification[e] = k
52
+ classification[e.reverse] = -k
53
+ comparability = plexus_comparability_explore(e, k, classification, ' '+space) && comparability
54
+ elsif classification[e] == -k
55
+ classification[e] = k
56
+ plexus_comparability_explore(e, k, classification, ' '+space)
57
+ comparability = false
58
+ end
59
+ end; comparability
60
+ end # plexus_comparability_explore_inner
61
+
62
+ end # Comparability
63
+ end # Plexus
@@ -0,0 +1,78 @@
1
+ module Plexus
2
+
3
+ # This implements a directed graph which does not allow parallel
4
+ # edges nor loops. That is, only one arc per nodes couple,
5
+ # and only one parent per node. Mimics the typical hierarchy
6
+ # structure.
7
+ module DirectedGraphBuilder
8
+ include GraphBuilder
9
+
10
+ autoload :Algorithms, "plexus/directed_graph/algorithms"
11
+ autoload :Distance, "plexus/directed_graph/distance"
12
+
13
+ # FIXME: DRY this snippet, I didn't find a clever way to
14
+ # to dit though
15
+ # TODO: well, extends_host_with do ... end would be cool,
16
+ # using Module.new.module_eval(&block) in the helper.
17
+ extends_host
18
+
19
+ module ClassMethods
20
+ def [](*a)
21
+ self.new.from_array(*a)
22
+ end
23
+ end
24
+
25
+ def initialize(*params)
26
+ # FIXME/TODO: setting args to the hash or {} while getting rid
27
+ # on the previous parameters prevents from passing another
28
+ # graph to the initializer, so you cannot do things like:
29
+ # UndirectedGraph.new(Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6])
30
+ # As args must be a hash, if we're to allow such syntax,
31
+ # we should provide a way to handle the graph as a hash
32
+ # member.
33
+ args = (params.pop if params.last.kind_of? Hash) || {}
34
+ args[:algorithmic_category] = DirectedGraphBuilder::Algorithms
35
+ super *(params << args)
36
+ end
37
+ end
38
+
39
+ # DirectedGraph is just an alias for Digraph should one desire
40
+ DigraphBuilder = DirectedGraphBuilder
41
+
42
+ # This is a Digraph that allows for parallel edges, but does not
43
+ # allow loops.
44
+ module DirectedPseudoGraphBuilder
45
+ include DirectedGraphBuilder
46
+ extends_host
47
+
48
+ module ClassMethods
49
+ def [](*a)
50
+ self.new.from_array(*a)
51
+ end
52
+ end
53
+
54
+ def initialize(*params)
55
+ args = (params.pop if params.last.kind_of? Hash) || {}
56
+ args[:parallel_edges] = true
57
+ super *(params << args)
58
+ end
59
+ end
60
+
61
+ # This is a Digraph that allows for both parallel edges and loops.
62
+ module DirectedMultiGraphBuilder
63
+ include DirectedPseudoGraphBuilder
64
+ extends_host
65
+
66
+ module ClassMethods
67
+ def [](*a)
68
+ self.new.from_array(*a)
69
+ end
70
+ end
71
+
72
+ def initialize(*params)
73
+ args = (params.pop if params.last.kind_of? Hash) || {}
74
+ args[:loops] = true
75
+ super *(params << args)
76
+ end
77
+ end
78
+ end