module TSort
TSort implementiert topologische Sortierung mittels Tarjans Algorithmus für stark verbundene Komponenten.
TSort ist so konzipiert, dass es mit jedem Objekt verwendet werden kann, das als gerichteter Graph interpretiert werden kann.
TSort erfordert zwei Methoden zur Interpretation eines Objekts als Graph: tsort_each_node und tsort_each_child.
-
tsort_each_nodewird verwendet, um über alle Knoten in einem Graphen zu iterieren. -
tsort_each_childwird verwendet, um über die Kindknoten eines gegebenen Knotens zu iterieren.
Die Gleichheit von Knoten wird durch eql? und hash definiert, da TSort intern Hash verwendet.
Ein einfaches Beispiel
Das folgende Beispiel zeigt, wie das Modul TSort in eine bestehende Klasse (in diesem Fall Hash) gemischt werden kann. Hier behandeln wir jeden Schlüssel im Hash als einen Knoten im Graphen und leiten einfach die erforderliche Methode tsort_each_node an die each_key-Methode von Hash weiter. Für jeden Schlüssel im Hash ist der zugehörige Wert ein Array der Kindknoten des Knotens. Diese Wahl führt wiederum zu unserer Implementierung der erforderlichen Methode tsort_each_child, die das Array der Kindknoten abruft und dann über dieses Array mittels des vom Benutzer bereitgestellten Blocks iteriert.
require 'tsort' class Hash include TSort alias tsort_each_node each_key def tsort_each_child(node, &block) fetch(node).each(&block) end end {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort #=> [3, 2, 1, 4] {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components #=> [[4], [2, 3], [1]]
Ein realistischeres Beispiel
Ein sehr einfaches 'make'-ähnliches Werkzeug kann wie folgt implementiert werden
require 'tsort' class Make def initialize @dep = {} @dep.default = [] end def rule(outputs, inputs=[], &block) triple = [outputs, inputs, block] outputs.each {|f| @dep[f] = [triple]} @dep[triple] = inputs end def build(target) each_strongly_connected_component_from(target) {|ns| if ns.length != 1 fs = ns.delete_if {|n| Array === n} raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") end n = ns.first if Array === n outputs, inputs, block = n inputs_time = inputs.map {|f| File.mtime f}.max begin outputs_time = outputs.map {|f| File.mtime f}.min rescue Errno::ENOENT outputs_time = nil end if outputs_time == nil || inputs_time != nil && outputs_time <= inputs_time sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i block.call end end } end def tsort_each_child(node, &block) @dep[node].each(&block) end include TSort end def command(arg) print arg, "\n" system arg end m = Make.new m.rule(%w[t1]) { command 'date > t1' } m.rule(%w[t2]) { command 'date > t2' } m.rule(%w[t3]) { command 'date > t3' } m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } m.build('t5')
Fehler
-
‘tsort.rb’ ist ein falscher Name, da diese Bibliothek Tarjans Algorithmus für stark verbundene Komponenten verwendet. Obwohl ‘strongly_connected_components.rb’ korrekt, aber zu lang ist.
Referenzen
-
Tarjan, "Depth First Search and Linear Graph Algorithms",
-
SIAM Journal on Computing, Vol. 1, No. 2, pp. 146-160, Juni 1972.
Constants
- VERSION
-
Die Versionszeichenkette.
Öffentliche Klassenmethoden
Source
# File lib/tsort.rb, line 347 def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes return to_enum(__method__, each_node, each_child) unless block_given? id_map = {} stack = [] each_node.call {|node| unless id_map.include? node each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| yield c } end } nil end
Die Iterator-Version der Methode TSort.strongly_connected_components.
Der Graph wird durch each_node und each_child dargestellt. each_node sollte eine call-Methode haben, die für jeden Knoten im Graphen liefert. each_child sollte eine call-Methode haben, die ein Knotenargument entgegennimmt und für jeden Kindknoten liefert.
g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} each_node = lambda {|&b| g.each_key(&b) } each_child = lambda {|n, &b| g[n].each(&b) } TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } #=> [4] # [2] # [3] # [1] g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} each_node = lambda {|&b| g.each_key(&b) } each_child = lambda {|n, &b| g[n].each(&b) } TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } #=> [4] # [2, 3] # [1]
Source
# File lib/tsort.rb, line 413 def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes return to_enum(__method__, node, each_child, id_map, stack) unless block_given? minimum_id = node_id = id_map[node] = id_map.size stack_length = stack.length stack << node each_child.call(node) {|child| if id_map.include? child child_id = id_map[child] minimum_id = child_id if child_id && child_id < minimum_id else sub_minimum_id = each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| yield c } minimum_id = sub_minimum_id if sub_minimum_id < minimum_id end } if node_id == minimum_id component = stack.slice!(stack_length .. -1) component.each {|n| id_map[n] = nil} yield component end minimum_id end
Iteriert über stark verbundene Komponenten in einem Graphen. Der Graph wird durch node und each_child dargestellt.
node ist der erste Knoten. each_child sollte eine call-Methode haben, die ein Knotenargument entgegennimmt und für jeden Kindknoten liefert.
Rückgabewert ist nicht spezifiziert.
TSort.each_strongly_connected_component_from ist eine Klassenmethode und benötigt keine Klasse zur Darstellung eines Graphen, der TSort einschließt.
graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} each_child = lambda {|n, &b| graph[n].each(&b) } TSort.each_strongly_connected_component_from(1, each_child) {|scc| p scc } #=> [4] # [2, 3] # [1]
Source
# File lib/tsort.rb, line 285 def self.strongly_connected_components(each_node, each_child) each_strongly_connected_component(each_node, each_child).to_a end
Gibt stark verbundene Komponenten als Array von Arrays von Knoten zurück. Das Array ist von Kindern zu Eltern sortiert. Jedes Element des Arrays repräsentiert eine stark verbundene Komponente.
Der Graph wird durch each_node und each_child dargestellt. each_node sollte eine call-Methode haben, die für jeden Knoten im Graphen liefert. each_child sollte eine call-Methode haben, die ein Knotenargument entgegennimmt und für jeden Kindknoten liefert.
g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} each_node = lambda {|&b| g.each_key(&b) } each_child = lambda {|n, &b| g[n].each(&b) } p TSort.strongly_connected_components(each_node, each_child) #=> [[4], [2], [3], [1]] g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} each_node = lambda {|&b| g.each_key(&b) } each_child = lambda {|n, &b| g[n].each(&b) } p TSort.strongly_connected_components(each_node, each_child) #=> [[4], [2, 3], [1]]
Source
# File lib/tsort.rb, line 180 def self.tsort(each_node, each_child) tsort_each(each_node, each_child).to_a end
Gibt ein topologisch sortiertes Array von Knoten zurück. Das Array ist von Kindern zu Eltern sortiert, d.h. das erste Element hat keine Kinder und der letzte Knoten hat keine Eltern.
Der Graph wird durch each_node und each_child dargestellt. each_node sollte eine call-Methode haben, die für jeden Knoten im Graphen liefert. each_child sollte eine call-Methode haben, die ein Knotenargument entgegennimmt und für jeden Kindknoten liefert.
Wenn ein Zyklus vorhanden ist, wird TSort::Cyclic ausgelöst.
g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} each_node = lambda {|&b| g.each_key(&b) } each_child = lambda {|n, &b| g[n].each(&b) } p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} each_node = lambda {|&b| g.each_key(&b) } each_child = lambda {|n, &b| g[n].each(&b) } p TSort.tsort(each_node, each_child) # raises TSort::Cyclic
Source
# File lib/tsort.rb, line 228 def self.tsort_each(each_node, each_child) # :yields: node return to_enum(__method__, each_node, each_child) unless block_given? each_strongly_connected_component(each_node, each_child) {|component| if component.size == 1 yield component.first else raise Cyclic.new("topological sort failed: #{component.inspect}") end } end
Die Iterator-Version der Methode TSort.tsort.
Der Graph wird durch each_node und each_child dargestellt. each_node sollte eine call-Methode haben, die für jeden Knoten im Graphen liefert. each_child sollte eine call-Methode haben, die ein Knotenargument entgegennimmt und für jeden Kindknoten liefert.
g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} each_node = lambda {|&b| g.each_key(&b) } each_child = lambda {|n, &b| g[n].each(&b) } TSort.tsort_each(each_node, each_child) {|n| p n } #=> 4 # 2 # 3 # 1
Öffentliche Instanzmethoden
Source
# File lib/tsort.rb, line 318 def each_strongly_connected_component(&block) # :yields: nodes each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) TSort.each_strongly_connected_component(each_node, each_child, &block) end
Die Iterator-Version der Methode strongly_connected_components. obj.each_strongly_connected_component ähnelt obj.strongly_connected_components.each, aber Änderungen an obj während der Iteration können zu unerwarteten Ergebnissen führen.
each_strongly_connected_component gibt nil zurück.
class G include TSort def initialize(g) @g = g end def tsort_each_child(n, &b) @g[n].each(&b) end def tsort_each_node(&b) @g.each_key(&b) end end graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) graph.each_strongly_connected_component {|scc| p scc } #=> [4] # [2] # [3] # [1] graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) graph.each_strongly_connected_component {|scc| p scc } #=> [4] # [2, 3] # [1]
Source
# File lib/tsort.rb, line 388 def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) end
Iteriert über stark verbundene Komponenten im von node erreichbaren Subgraphen.
Rückgabewert ist nicht spezifiziert.
each_strongly_connected_component_from ruft tsort_each_node nicht auf.
class G include TSort def initialize(g) @g = g end def tsort_each_child(n, &b) @g[n].each(&b) end def tsort_each_node(&b) @g.each_key(&b) end end graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) graph.each_strongly_connected_component_from(2) {|scc| p scc } #=> [4] # [2] graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) graph.each_strongly_connected_component_from(2) {|scc| p scc } #=> [4] # [2, 3]
Source
# File lib/tsort.rb, line 259 def strongly_connected_components each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) TSort.strongly_connected_components(each_node, each_child) end
Gibt stark verbundene Komponenten als Array von Arrays von Knoten zurück. Das Array ist von Kindern zu Eltern sortiert. Jedes Element des Arrays repräsentiert eine stark verbundene Komponente.
class G include TSort def initialize(g) @g = g end def tsort_each_child(n, &b) @g[n].each(&b) end def tsort_each_node(&b) @g.each_key(&b) end end graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) p graph.strongly_connected_components #=> [[4], [2], [3], [1]] graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) p graph.strongly_connected_components #=> [[4], [2, 3], [1]]
Source
# File lib/tsort.rb, line 154 def tsort each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) TSort.tsort(each_node, each_child) end
Gibt ein topologisch sortiertes Array von Knoten zurück. Das Array ist von Kindern zu Eltern sortiert, d.h. das erste Element hat keine Kinder und der letzte Knoten hat keine Eltern.
Wenn ein Zyklus vorhanden ist, wird TSort::Cyclic ausgelöst.
class G include TSort def initialize(g) @g = g end def tsort_each_child(n, &b) @g[n].each(&b) end def tsort_each_node(&b) @g.each_key(&b) end end graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) p graph.tsort #=> [4, 2, 3, 1] graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) p graph.tsort # raises TSort::Cyclic
Source
# File lib/tsort.rb, line 207 def tsort_each(&block) # :yields: node each_node = method(:tsort_each_node) each_child = method(:tsort_each_child) TSort.tsort_each(each_node, each_child, &block) end
Die Iterator-Version der Methode tsort. obj.tsort_each ähnelt obj.tsort.each, aber Änderungen an obj während der Iteration können zu unerwarteten Ergebnissen führen.
tsort_each gibt nil zurück. Wenn ein Zyklus vorhanden ist, wird TSort::Cyclic ausgelöst.
class G include TSort def initialize(g) @g = g end def tsort_each_child(n, &b) @g[n].each(&b) end def tsort_each_node(&b) @g.each_key(&b) end end graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) graph.tsort_each {|n| p n } #=> 4 # 2 # 3 # 1
Source
# File lib/tsort.rb, line 454 def tsort_each_child(node) # :yields: child raise NotImplementedError.new end
Muss von einer erweiterten Klasse implementiert werden.
tsort_each_child wird verwendet, um über die Kindknoten von node zu iterieren.
Source
# File lib/tsort.rb, line 446 def tsort_each_node # :yields: node raise NotImplementedError.new end
Muss von einer erweiterten Klasse implementiert werden.
tsort_each_node wird verwendet, um über alle Knoten in einem Graphen zu iterieren.