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,63 @@ 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
+ return stack. into_iter ( ) . map ( | ( node , _ ) | node ) . collect ( ) ;
248
257
}
249
258
}
250
- unreachable ! ( ) ;
259
+ unreachable ! ( "detect_cycle is expected to be called only on graphs with cycles" ) ;
251
260
}
252
261
253
- fn dfs (
254
- & self ,
262
+ fn dfs < ' a > (
263
+ & ' a self ,
255
264
node : & ' input str ,
256
- visited : & mut HashSet < & ' input str > ,
257
- stack : & mut Vec < & ' input str > ,
265
+ visited : & mut HashMap < & ' input str , VisitedState > ,
266
+ stack : & mut Vec < ( & ' input str , & ' a [ & ' input str ] ) > ,
258
267
) -> bool {
259
- if stack. contains ( & node) {
260
- return true ;
261
- }
262
- if visited. contains ( & node) {
268
+ stack. push ( (
269
+ node,
270
+ self . nodes . get ( node) . map_or ( & [ ] , |n| & n. successor_names ) ,
271
+ ) ) ;
272
+ let state = * visited. entry ( node) . or_insert ( VisitedState :: Opened ) ;
273
+
274
+ if state == VisitedState :: Closed {
263
275
return false ;
264
276
}
265
277
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 ;
278
+ while let Some ( ( node, pending_successors) ) = stack. pop ( ) {
279
+ let Some ( ( & next_node, pending) ) = pending_successors. split_first ( ) else {
280
+ // no more pending successors in the list -> close the node
281
+ visited. insert ( node, VisitedState :: Closed ) ;
282
+ continue ;
283
+ } ;
284
+
285
+ // schedule processing for the pending part of successors for this node
286
+ stack. push ( ( node, pending) ) ;
287
+
288
+ match visited. entry ( next_node) {
289
+ Entry :: Vacant ( v) => {
290
+ // It's a first time we enter this node
291
+ v. insert ( VisitedState :: Opened ) ;
292
+ stack. push ( (
293
+ next_node,
294
+ self . nodes
295
+ . get ( next_node)
296
+ . map_or ( & [ ] , |n| & n. successor_names ) ,
297
+ ) ) ;
298
+ }
299
+ Entry :: Occupied ( o) => {
300
+ if * o. get ( ) == VisitedState :: Opened {
301
+ // we are entering the same opened node again -> loop found
302
+ // stack contains it
303
+ return true ;
304
+ }
273
305
}
274
306
}
275
307
}
276
308
277
- stack. pop ( ) ;
278
309
false
279
310
}
280
311
}
0 commit comments