Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/portupgrade
Path: blob/master/lib/pkgtools/pkgtsort.rb
102 views
1
# vim: set sts=2 sw=2 ts=8 et:
2
#
3
# Copyright (c) 2001-2004 Akinori MUSHA <[email protected]>
4
# Copyright (c) 2006-2008 Sergey Matveychuk <[email protected]>
5
# Copyright (c) 2009-2012 Stanislav Sedov <[email protected]>
6
#
7
# All rights reserved.
8
#
9
# Redistribution and use in source and binary forms, with or without
10
# modification, are permitted provided that the following conditions
11
# are met:
12
# 1. Redistributions of source code must retain the above copyright
13
# notice, this list of conditions and the following disclaimer.
14
# 2. Redistributions in binary form must reproduce the above copyright
15
# notice, this list of conditions and the following disclaimer in the
16
# documentation and/or other materials provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
# SUCH DAMAGE.
29
#
30
31
#
32
# Topological Sorter
33
#
34
35
class PkgTSort
36
def initialize()
37
@hints = Hash.new([])
38
end
39
40
def empty?()
41
@hints.empty?
42
end
43
44
def clear()
45
@hints.clear
46
end
47
48
def dump()
49
@hints.dup
50
end
51
52
def dup()
53
Marshal.load(Marshal.dump(self))
54
end
55
56
# add(a, *b) : Tell that <a> depends on <*b>.
57
def add(a, *b)
58
a.nil? and return self
59
60
b.delete(nil)
61
@hints[a] |= b
62
63
b.each do |x|
64
@hints[x] = [] if not @hints.include?(x)
65
end
66
67
self
68
end
69
70
# delete(a) : Tell that <a> no longer exists.
71
# delete(a, *b) : Tell that <a> no longer depends on <*b>.
72
def delete(a, *b)
73
@hints.include?(a) or return self
74
75
if b.empty?
76
@hints.delete(a)
77
78
@hints.each_key do |x|
79
@hints[x].delete(a)
80
end
81
else
82
@hints[a] -= b
83
end
84
85
self
86
end
87
88
# tsort!(&block) : Perform sort and return a sorted array.
89
# Everything is cleared when it is done.
90
# If no block is given, it automatically unlinks
91
# cycles. If a block is given, it yields the
92
# block with a cycle every time it finds one,
93
# and the block can return an index to indicate
94
# where it should unlink the cycle. If the
95
# block returns nil, it quits immediately
96
# returning nil.
97
def tsort!(&block)
98
result = []
99
100
until empty?
101
ary = @hints.sort { |a,b| b[1].size <=> a[1].size }
102
key, deps = ary.pop
103
104
if deps.empty?
105
result << key
106
107
delete(key)
108
else
109
# there must be a cycle - find it
110
while (cycle = find_cycle(key)).nil?
111
if ary.empty?
112
raise 'cannot resolve cyclic dependency - maybe a bug.'
113
end
114
115
key, deps = ary.pop
116
end
117
118
if block
119
# yield <block> with the found cycle
120
at = block.call(cycle)
121
122
return nil if at.nil?
123
124
if cycle[at + 1].nil?
125
delete(cycle.last, cycle.first)
126
else
127
delete(cycle[at], cycle[at + 1])
128
end
129
else
130
# unlink the cycle
131
delete(cycle.last, cycle.first)
132
end
133
134
return result + tsort!(&block)
135
end
136
end
137
138
result
139
end
140
141
# tsort(&block) : Same as tsort! but not destructive. (It costs)
142
def tsort(&block)
143
dup.tsort!(&block)
144
end
145
146
private
147
def find_cycle(start, current = start, path = [])
148
deps = @hints[current]
149
150
if deps.include?(start)
151
return path + [start]
152
end
153
154
deps.each do |dep|
155
next if path.include?(dep)
156
157
if cycle = find_cycle(start, dep, path + [dep])
158
return cycle
159
end
160
end
161
162
return nil
163
end
164
end
165
166
if __FILE__ == $0
167
t = PkgTSort.new
168
t.add(1, 2, 3).add(2, 4).add(3, 4).add(2, 3).add(1,3).add(6, 5).add(5, 1)
169
170
p t.dump
171
a = t.tsort { |cycle| puts "cycle found: " + cycle.join('-'); false }
172
puts(*a)
173
174
t = PkgTSort.new
175
t.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)
176
177
p t.dump
178
a = t.tsort { |cycle| puts "cycle found: " + cycle.join('-'); nil }
179
puts(*a)
180
181
p t.dump
182
a = t.tsort! { |cycle| puts "cycle found: " + cycle.join('-'); 1 }
183
puts(*a)
184
185
p t.dump
186
end
187
188