44// file that was distributed with this source code.
55//spell-checker:ignore TAOCP indegree
66use clap:: { Arg , Command } ;
7- use std:: collections:: { HashMap , HashSet , VecDeque } ;
7+ use std:: collections:: hash_map:: Entry ;
8+ use std:: collections:: { HashMap , VecDeque } ;
89use std:: ffi:: OsString ;
910use std:: path:: Path ;
1011use thiserror:: Error ;
@@ -34,13 +35,15 @@ enum TsortError {
3435 /// The graph contains a cycle.
3536 #[ error( "{input}: {message}" , input = . 0 , message = translate!( "tsort-error-loop" ) ) ]
3637 Loop ( String ) ,
37-
38- /// A particular node in a cycle. (This is mainly used for printing.)
39- #[ error( "{0}" ) ]
40- LoopNode ( String ) ,
4138}
4239
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+
4345impl UError for TsortError { }
46+ impl UError for LoopNode < ' _ > { }
4447
4548#[ uucore:: main]
4649pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
@@ -131,6 +134,12 @@ struct Graph<'input> {
131134 nodes : HashMap < & ' input str , Node < ' input > > ,
132135}
133136
137+ #[ derive( Clone , Copy , PartialEq , Eq ) ]
138+ enum VisitedState {
139+ Opened ,
140+ Closed ,
141+ }
142+
134143impl < ' input > Graph < ' input > {
135144 fn new ( name : String ) -> Self {
136145 Self {
@@ -224,8 +233,8 @@ impl<'input> Graph<'input> {
224233 fn find_and_break_cycle ( & mut self , frontier : & mut VecDeque < & ' input str > ) {
225234 let cycle = self . detect_cycle ( ) ;
226235 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) ) ;
229238 }
230239 let u = cycle[ 0 ] ;
231240 let v = cycle[ 1 ] ;
@@ -240,41 +249,76 @@ impl<'input> Graph<'input> {
240249 let mut nodes: Vec < _ > = self . nodes . keys ( ) . collect ( ) ;
241250 nodes. sort_unstable ( ) ;
242251
243- let mut visited = HashSet :: new ( ) ;
252+ let mut visited = HashMap :: new ( ) ;
244253 let mut stack = Vec :: with_capacity ( self . nodes . len ( ) ) ;
245254 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 element 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 ( ) ;
248266 }
249267 }
250- unreachable ! ( ) ;
268+ unreachable ! ( "detect_cycle is expected to be called only on graphs with cycles" ) ;
251269 }
252270
253- fn dfs (
254- & self ,
271+ fn dfs < ' a > (
272+ & ' a self ,
255273 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 ] ) > ,
258276 ) -> 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 {
263284 return false ;
264285 }
265286
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+ }
273318 }
274319 }
275320 }
276321
277- stack. pop ( ) ;
278322 false
279323 }
280324}
0 commit comments