1 # frozen_string_literal: true
4 # tsort.rb - provides a module for topological sorting and strongly connected components.
9 # TSort implements topological sorting using Tarjan's algorithm for
10 # strongly connected components.
12 # TSort is designed to be able to be used with any object which can be
13 # interpreted as a directed graph.
15 # TSort requires two methods to interpret an object as a graph,
16 # tsort_each_node and tsort_each_child.
18 # * tsort_each_node is used to iterate for all nodes over a graph.
19 # * tsort_each_child is used to iterate for child nodes of a given node.
21 # The equality of nodes are defined by eql? and hash since
22 # TSort uses Hash internally.
26 # The following example demonstrates how to mix the TSort module into an
27 # existing class (in this case, Hash). Here, we're treating each key in
28 # the hash as a node in the graph, and so we simply alias the required
29 # #tsort_each_node method to Hash's #each_key method. For each key in the
30 # hash, the associated value is an array of the node's child nodes. This
31 # choice in turn leads to our implementation of the required #tsort_each_child
32 # method, which fetches the array of child nodes and then iterates over that
33 # array using the user-supplied block.
39 # alias tsort_each_node each_key
40 # def tsort_each_child(node, &block)
41 # fetch(node).each(&block)
45 # {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort
48 # {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components
49 # #=> [[4], [2, 3], [1]]
51 # == A More Realistic Example
53 # A very simple `make' like tool can be implemented as follows:
63 # def rule(outputs, inputs=[], &block)
64 # triple = [outputs, inputs, block]
65 # outputs.each {|f| @dep[f] = [triple]}
66 # @dep[triple] = inputs
70 # each_strongly_connected_component_from(target) {|ns|
72 # fs = ns.delete_if {|n| Array === n}
73 # raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}")
77 # outputs, inputs, block = n
78 # inputs_time = inputs.map {|f| File.mtime f}.max
80 # outputs_time = outputs.map {|f| File.mtime f}.min
81 # rescue Errno::ENOENT
84 # if outputs_time == nil ||
85 # inputs_time != nil && outputs_time <= inputs_time
86 # sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i
93 # def tsort_each_child(node, &block)
94 # @dep[node].each(&block)
105 # m.rule(%w[t1]) { command 'date > t1' }
106 # m.rule(%w[t2]) { command 'date > t2' }
107 # m.rule(%w[t3]) { command 'date > t3' }
108 # m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' }
109 # m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' }
114 # * 'tsort.rb' is wrong name because this library uses
115 # Tarjan's algorithm for strongly connected components.
116 # Although 'strongly_connected_components.rb' is correct but too long.
120 # R. E. Tarjan, "Depth First Search and Linear Graph Algorithms",
121 # <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972.
128 class Cyclic < StandardError
131 # Returns a topologically sorted array of nodes.
132 # The array is sorted from children to parents, i.e.
133 # the first element has no child and the last node has no parent.
135 # If there is a cycle, TSort::Cyclic is raised.
142 # def tsort_each_child(n, &b) @g[n].each(&b) end
143 # def tsort_each_node(&b) @g.each_key(&b) end
146 # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
147 # p graph.tsort #=> [4, 2, 3, 1]
149 # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
150 # p graph.tsort # raises TSort::Cyclic
153 each_node = method(:tsort_each_node)
154 each_child = method(:tsort_each_child)
155 TSort.tsort(each_node, each_child)
158 # Returns a topologically sorted array of nodes.
159 # The array is sorted from children to parents, i.e.
160 # the first element has no child and the last node has no parent.
162 # The graph is represented by _each_node_ and _each_child_.
163 # _each_node_ should have +call+ method which yields for each node in the graph.
164 # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
166 # If there is a cycle, TSort::Cyclic is raised.
168 # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
169 # each_node = lambda {|&b| g.each_key(&b) }
170 # each_child = lambda {|n, &b| g[n].each(&b) }
171 # p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1]
173 # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
174 # each_node = lambda {|&b| g.each_key(&b) }
175 # each_child = lambda {|n, &b| g[n].each(&b) }
176 # p TSort.tsort(each_node, each_child) # raises TSort::Cyclic
178 def self.tsort(each_node, each_child)
179 tsort_each(each_node, each_child).to_a
182 # The iterator version of the #tsort method.
183 # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but
184 # modification of _obj_ during the iteration may lead to unexpected results.
186 # #tsort_each returns +nil+.
187 # If there is a cycle, TSort::Cyclic is raised.
194 # def tsort_each_child(n, &b) @g[n].each(&b) end
195 # def tsort_each_node(&b) @g.each_key(&b) end
198 # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
199 # graph.tsort_each {|n| p n }
205 def tsort_each(&block) # :yields: node
206 each_node = method(:tsort_each_node)
207 each_child = method(:tsort_each_child)
208 TSort.tsort_each(each_node, each_child, &block)
211 # The iterator version of the TSort.tsort method.
213 # The graph is represented by _each_node_ and _each_child_.
214 # _each_node_ should have +call+ method which yields for each node in the graph.
215 # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
217 # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
218 # each_node = lambda {|&b| g.each_key(&b) }
219 # each_child = lambda {|n, &b| g[n].each(&b) }
220 # TSort.tsort_each(each_node, each_child) {|n| p n }
226 def self.tsort_each(each_node, each_child) # :yields: node
227 return to_enum(__method__, each_node, each_child) unless block_given?
229 each_strongly_connected_component(each_node, each_child) {|component|
230 if component.size == 1
231 yield component.first
233 raise Cyclic.new("topological sort failed: #{component.inspect}")
238 # Returns strongly connected components as an array of arrays of nodes.
239 # The array is sorted from children to parents.
240 # Each elements of the array represents a strongly connected component.
247 # def tsort_each_child(n, &b) @g[n].each(&b) end
248 # def tsort_each_node(&b) @g.each_key(&b) end
251 # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
252 # p graph.strongly_connected_components #=> [[4], [2], [3], [1]]
254 # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
255 # p graph.strongly_connected_components #=> [[4], [2, 3], [1]]
257 def strongly_connected_components
258 each_node = method(:tsort_each_node)
259 each_child = method(:tsort_each_child)
260 TSort.strongly_connected_components(each_node, each_child)
263 # Returns strongly connected components as an array of arrays of nodes.
264 # The array is sorted from children to parents.
265 # Each elements of the array represents a strongly connected component.
267 # The graph is represented by _each_node_ and _each_child_.
268 # _each_node_ should have +call+ method which yields for each node in the graph.
269 # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
271 # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
272 # each_node = lambda {|&b| g.each_key(&b) }
273 # each_child = lambda {|n, &b| g[n].each(&b) }
274 # p TSort.strongly_connected_components(each_node, each_child)
275 # #=> [[4], [2], [3], [1]]
277 # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
278 # each_node = lambda {|&b| g.each_key(&b) }
279 # each_child = lambda {|n, &b| g[n].each(&b) }
280 # p TSort.strongly_connected_components(each_node, each_child)
281 # #=> [[4], [2, 3], [1]]
283 def self.strongly_connected_components(each_node, each_child)
284 each_strongly_connected_component(each_node, each_child).to_a
287 # The iterator version of the #strongly_connected_components method.
288 # <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to
289 # <tt><em>obj</em>.strongly_connected_components.each</tt>, but
290 # modification of _obj_ during the iteration may lead to unexpected results.
292 # #each_strongly_connected_component returns +nil+.
299 # def tsort_each_child(n, &b) @g[n].each(&b) end
300 # def tsort_each_node(&b) @g.each_key(&b) end
303 # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
304 # graph.each_strongly_connected_component {|scc| p scc }
310 # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
311 # graph.each_strongly_connected_component {|scc| p scc }
316 def each_strongly_connected_component(&block) # :yields: nodes
317 each_node = method(:tsort_each_node)
318 each_child = method(:tsort_each_child)
319 TSort.each_strongly_connected_component(each_node, each_child, &block)
322 # The iterator version of the TSort.strongly_connected_components method.
324 # The graph is represented by _each_node_ and _each_child_.
325 # _each_node_ should have +call+ method which yields for each node in the graph.
326 # _each_child_ should have +call+ method which takes a node argument and yields for each child node.
328 # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
329 # each_node = lambda {|&b| g.each_key(&b) }
330 # each_child = lambda {|n, &b| g[n].each(&b) }
331 # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
337 # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
338 # each_node = lambda {|&b| g.each_key(&b) }
339 # each_child = lambda {|n, &b| g[n].each(&b) }
340 # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
345 def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes
346 return to_enum(__method__, each_node, each_child) unless block_given?
350 each_node.call {|node|
351 unless id_map.include? node
352 each_strongly_connected_component_from(node, each_child, id_map, stack) {|c|
360 # Iterates over strongly connected component in the subgraph reachable from
363 # Return value is unspecified.
365 # #each_strongly_connected_component_from doesn't call #tsort_each_node.
372 # def tsort_each_child(n, &b) @g[n].each(&b) end
373 # def tsort_each_node(&b) @g.each_key(&b) end
376 # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
377 # graph.each_strongly_connected_component_from(2) {|scc| p scc }
381 # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
382 # graph.each_strongly_connected_component_from(2) {|scc| p scc }
386 def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes
387 TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block)
390 # Iterates over strongly connected components in a graph.
391 # The graph is represented by _node_ and _each_child_.
393 # _node_ is the first node.
394 # _each_child_ should have +call+ method which takes a node argument
395 # and yields for each child node.
397 # Return value is unspecified.
399 # #TSort.each_strongly_connected_component_from is a class method and
400 # it doesn't need a class to represent a graph which includes TSort.
402 # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
403 # each_child = lambda {|n, &b| graph[n].each(&b) }
404 # TSort.each_strongly_connected_component_from(1, each_child) {|scc|
411 def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes
412 return to_enum(__method__, node, each_child, id_map, stack) unless block_given?
414 minimum_id = node_id = id_map[node] = id_map.size
415 stack_length = stack.length
418 each_child.call(node) {|child|
419 if id_map.include? child
420 child_id = id_map[child]
421 minimum_id = child_id if child_id && child_id < minimum_id
424 each_strongly_connected_component_from(child, each_child, id_map, stack) {|c|
427 minimum_id = sub_minimum_id if sub_minimum_id < minimum_id
431 if node_id == minimum_id
432 component = stack.slice!(stack_length .. -1)
433 component.each {|n| id_map[n] = nil}
440 # Should be implemented by a extended class.
442 # #tsort_each_node is used to iterate for all nodes over a graph.
444 def tsort_each_node # :yields: node
445 raise NotImplementedError.new
448 # Should be implemented by a extended class.
450 # #tsort_each_child is used to iterate for child nodes of _node_.
452 def tsort_each_child(node) # :yields: child
453 raise NotImplementedError.new