4
4
// file that was distributed with this source code.
5
5
//spell-checker:ignore TAOCP indegree
6
6
use clap:: { Arg , Command } ;
7
- use std:: collections:: { HashMap , HashSet , VecDeque } ;
7
+ use std:: collections:: hash_map:: Entry ;
8
+ use std:: collections:: { HashMap , VecDeque } ;
8
9
use std:: ffi:: OsString ;
9
10
use std:: path:: Path ;
10
11
use thiserror:: Error ;
@@ -34,13 +35,15 @@ enum TsortError {
34
35
/// The graph contains a cycle.
35
36
#[ error( "{input}: {message}" , input = . 0 , message = translate!( "tsort-error-loop" ) ) ]
36
37
Loop ( String ) ,
37
-
38
- /// A particular node in a cycle. (This is mainly used for printing.)
39
- #[ error( "{0}" ) ]
40
- LoopNode ( String ) ,
41
38
}
42
39
40
+ // Auxiliary struct, just for printing loop nodes via show! macro
41
+ #[ derive( Debug , Error ) ]
42
+ #[ error( "{0}" ) ]
43
+ struct LoopNode < ' a > ( & ' a str ) ;
44
+
43
45
impl UError for TsortError { }
46
+ impl UError for LoopNode < ' _ > { }
44
47
45
48
#[ uucore:: main]
46
49
pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
@@ -131,6 +134,12 @@ struct Graph<'input> {
131
134
nodes : HashMap < & ' input str , Node < ' input > > ,
132
135
}
133
136
137
+ #[ derive( Clone , Copy , PartialEq , Eq ) ]
138
+ enum VisitedState {
139
+ Opened ,
140
+ Closed ,
141
+ }
142
+
134
143
impl < ' input > Graph < ' input > {
135
144
fn new ( name : String ) -> Graph < ' input > {
136
145
Self {
@@ -224,8 +233,8 @@ impl<'input> Graph<'input> {
224
233
fn find_and_break_cycle ( & mut self , frontier : & mut VecDeque < & ' input str > ) {
225
234
let cycle = self . detect_cycle ( ) ;
226
235
show ! ( TsortError :: Loop ( self . name. clone( ) ) ) ;
227
- for node in & cycle {
228
- show ! ( TsortError :: LoopNode ( ( * node) . to_string ( ) ) ) ;
236
+ for & node in & cycle {
237
+ show ! ( LoopNode ( node) ) ;
229
238
}
230
239
let u = cycle[ 0 ] ;
231
240
let v = cycle[ 1 ] ;
@@ -240,41 +249,76 @@ impl<'input> Graph<'input> {
240
249
let mut nodes: Vec < _ > = self . nodes . keys ( ) . collect ( ) ;
241
250
nodes. sort_unstable ( ) ;
242
251
243
- let mut visited = HashSet :: new ( ) ;
252
+ let mut visited = HashMap :: new ( ) ;
244
253
let mut stack = Vec :: with_capacity ( self . nodes . len ( ) ) ;
245
254
for node in nodes {
246
- if !visited. contains ( node) && self . dfs ( node, & mut visited, & mut stack) {
247
- return stack;
255
+ if self . dfs ( node, & mut visited, & mut stack) {
256
+ // last elemet in the stack appears twice: at the begin
257
+ // and at the end of the loop
258
+ let ( loop_entry, _) = stack. pop ( ) . expect ( "loop is not empty" ) ;
259
+
260
+ // skip the prefix which doesn't belong to the loop
261
+ return stack
262
+ . into_iter ( )
263
+ . map ( |( node, _) | node)
264
+ . skip_while ( |& node| node != loop_entry)
265
+ . collect ( ) ;
248
266
}
249
267
}
250
- unreachable ! ( ) ;
268
+ unreachable ! ( "detect_cycle is expected to be called only on graphs with cycles" ) ;
251
269
}
252
270
253
- fn dfs (
254
- & self ,
271
+ fn dfs < ' a > (
272
+ & ' a self ,
255
273
node : & ' input str ,
256
- visited : & mut HashSet < & ' input str > ,
257
- stack : & mut Vec < & ' input str > ,
274
+ visited : & mut HashMap < & ' input str , VisitedState > ,
275
+ stack : & mut Vec < ( & ' input str , & ' a [ & ' input str ] ) > ,
258
276
) -> bool {
259
- if stack. contains ( & node) {
260
- return true ;
261
- }
262
- if visited. contains ( & node) {
277
+ stack. push ( (
278
+ node,
279
+ self . nodes . get ( node) . map_or ( & [ ] , |n| & n. successor_names ) ,
280
+ ) ) ;
281
+ let state = * visited. entry ( node) . or_insert ( VisitedState :: Opened ) ;
282
+
283
+ if state == VisitedState :: Closed {
263
284
return false ;
264
285
}
265
286
266
- visited. insert ( node) ;
267
- stack. push ( node) ;
268
-
269
- if let Some ( successor_names) = self . nodes . get ( node) . map ( |n| & n. successor_names ) {
270
- for & successor in successor_names {
271
- if self . dfs ( successor, visited, stack) {
272
- return true ;
287
+ while let Some ( ( node, pending_successors) ) = stack. pop ( ) {
288
+ let Some ( ( & next_node, pending) ) = pending_successors. split_first ( ) else {
289
+ // no more pending successors in the list -> close the node
290
+ visited. insert ( node, VisitedState :: Closed ) ;
291
+ continue ;
292
+ } ;
293
+
294
+ // schedule processing for the pending part of successors for this node
295
+ stack. push ( ( node, pending) ) ;
296
+
297
+ match visited. entry ( next_node) {
298
+ Entry :: Vacant ( v) => {
299
+ // It's a first time we enter this node
300
+ v. insert ( VisitedState :: Opened ) ;
301
+ stack. push ( (
302
+ next_node,
303
+ self . nodes
304
+ . get ( next_node)
305
+ . map_or ( & [ ] , |n| & n. successor_names ) ,
306
+ ) ) ;
307
+ }
308
+ Entry :: Occupied ( o) => {
309
+ if * o. get ( ) == VisitedState :: Opened {
310
+ // we are entering the same opened node again -> loop found
311
+ // stack contains it
312
+ //
313
+ // But part of the stack may not be belonging to this loop
314
+ // push found node to the stack to be able to trace the beginning of the loop
315
+ stack. push ( ( next_node, & [ ] ) ) ;
316
+ return true ;
317
+ }
273
318
}
274
319
}
275
320
}
276
321
277
- stack. pop ( ) ;
278
322
false
279
323
}
280
324
}
0 commit comments