# vim: set sts=2 sw=2 ts=8 et:1#2# Copyright (c) 2001-2004 Akinori MUSHA <[email protected]>3# Copyright (c) 2006-2008 Sergey Matveychuk <[email protected]>4# Copyright (c) 2009-2012 Stanislav Sedov <[email protected]>5#6# All rights reserved.7#8# Redistribution and use in source and binary forms, with or without9# modification, are permitted provided that the following conditions10# are met:11# 1. Redistributions of source code must retain the above copyright12# notice, this list of conditions and the following disclaimer.13# 2. Redistributions in binary form must reproduce the above copyright14# notice, this list of conditions and the following disclaimer in the15# documentation and/or other materials provided with the distribution.16#17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF27# SUCH DAMAGE.28#2930#31# Topological Sorter32#3334class PkgTSort35def initialize()36@hints = Hash.new([])37end3839def empty?()40@hints.empty?41end4243def clear()44@hints.clear45end4647def dump()48@hints.dup49end5051def dup()52Marshal.load(Marshal.dump(self))53end5455# add(a, *b) : Tell that <a> depends on <*b>.56def add(a, *b)57a.nil? and return self5859b.delete(nil)60@hints[a] |= b6162b.each do |x|63@hints[x] = [] if not @hints.include?(x)64end6566self67end6869# delete(a) : Tell that <a> no longer exists.70# delete(a, *b) : Tell that <a> no longer depends on <*b>.71def delete(a, *b)72@hints.include?(a) or return self7374if b.empty?75@hints.delete(a)7677@hints.each_key do |x|78@hints[x].delete(a)79end80else81@hints[a] -= b82end8384self85end8687# tsort!(&block) : Perform sort and return a sorted array.88# Everything is cleared when it is done.89# If no block is given, it automatically unlinks90# cycles. If a block is given, it yields the91# block with a cycle every time it finds one,92# and the block can return an index to indicate93# where it should unlink the cycle. If the94# block returns nil, it quits immediately95# returning nil.96def tsort!(&block)97result = []9899until empty?100ary = @hints.sort { |a,b| b[1].size <=> a[1].size }101key, deps = ary.pop102103if deps.empty?104result << key105106delete(key)107else108# there must be a cycle - find it109while (cycle = find_cycle(key)).nil?110if ary.empty?111raise 'cannot resolve cyclic dependency - maybe a bug.'112end113114key, deps = ary.pop115end116117if block118# yield <block> with the found cycle119at = block.call(cycle)120121return nil if at.nil?122123if cycle[at + 1].nil?124delete(cycle.last, cycle.first)125else126delete(cycle[at], cycle[at + 1])127end128else129# unlink the cycle130delete(cycle.last, cycle.first)131end132133return result + tsort!(&block)134end135end136137result138end139140# tsort(&block) : Same as tsort! but not destructive. (It costs)141def tsort(&block)142dup.tsort!(&block)143end144145private146def find_cycle(start, current = start, path = [])147deps = @hints[current]148149if deps.include?(start)150return path + [start]151end152153deps.each do |dep|154next if path.include?(dep)155156if cycle = find_cycle(start, dep, path + [dep])157return cycle158end159end160161return nil162end163end164165if __FILE__ == $0166t = PkgTSort.new167t.add(1, 2, 3).add(2, 4).add(3, 4).add(2, 3).add(1,3).add(6, 5).add(5, 1)168169p t.dump170a = t.tsort { |cycle| puts "cycle found: " + cycle.join('-'); false }171puts(*a)172173t = PkgTSort.new174t.add(1, 2, 3).add(2, 4).add(3, 4).add(2, 3).add(4,1).add(1,3).add(6, 5).add(5, 6)175176p t.dump177a = t.tsort { |cycle| puts "cycle found: " + cycle.join('-'); nil }178puts(*a)179180p t.dump181a = t.tsort! { |cycle| puts "cycle found: " + cycle.join('-'); 1 }182puts(*a)183184p t.dump185end186187188