| 340 | /// This can operator on a disconnected graph containing multiple DAGs. |
| 341 | #[allow(clippy::disallowed_types)] |
| 342 | pub fn topological_sort<T: Hash + Eq>( |
| 343 | graph: &std::collections::HashMap<T, Vec<T>>, |
| 344 | ) -> Result<std::collections::HashMap<&T, i32>, anyhow::Error> { |
| 345 | let mut referenced_by: std::collections::HashMap<&T, std::collections::HashSet<&T>> = |
| 346 | std::collections::HashMap::new(); |
| 347 | for (subject, references) in graph.iter() { |
| 348 | for reference in references { |
| 349 | referenced_by.entry(reference).or_default().insert(subject); |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | // Start with nodes that have no incoming edges (empty referenced_by sets). |
| 354 | // Also include nodes in graph that aren't in referenced_by at all (roots). |
| 355 | let mut queue: Vec<_> = graph |
| 356 | .keys() |
| 357 | .filter(|key| { |
| 358 | referenced_by |
| 359 | .get(*key) |
| 360 | .map_or(true, |subjects| subjects.is_empty()) |
| 361 | }) |
| 362 | .collect(); |
| 363 | |
| 364 | let mut ordered = std::collections::HashMap::new(); |
| 365 | let mut n = 0; |
| 366 | while let Some(subj_ver) = queue.pop() { |
| 367 | if let Some(refs) = graph.get(subj_ver) { |
| 368 | for ref_ver in refs { |
| 369 | let Some(subjects) = referenced_by.get_mut(ref_ver) else { |
| 370 | continue; |
| 371 | }; |
| 372 | subjects.remove(&subj_ver); |
| 373 | if subjects.is_empty() { |
| 374 | referenced_by.remove_entry(ref_ver); |
| 375 | queue.push(ref_ver); |
| 376 | } |
| 377 | } |
| 378 | } |
| 379 | ordered.insert(subj_ver, n); |
| 380 | n += 1; |
| 381 | } |
| 382 | |
| 383 | if referenced_by.is_empty() { |
| 384 | Ok(ordered) |
| 385 | } else { |
| 386 | Err(anyhow!("Cycled detected during topoligical sort")) |
| 387 | } |
| 388 | } |
| 389 | |
| 390 | async fn send_request<T>(req: reqwest::RequestBuilder) -> Result<T, UnhandledError> |
| 391 | where |