1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
//! # The Sum of Its Parts
//!
//! Part one is a [topological sort](https://en.wikipedia.org/wiki/Topological_sorting)
//! of the steps based on the dependencies between them.
use crate::util::hash::*;
use std::collections::BTreeMap;
type Input = FastMap<u8, Step>;
#[derive(Clone, Default)]
pub struct Step {
remaining: u32,
children: Vec<u8>,
}
pub fn parse(input: &str) -> Input {
let mut steps = FastMap::new();
for line in input.lines().map(str::as_bytes) {
// Each step is a single uppercase letter.
let from = line[5];
let to = line[36];
// Add all steps that depend on this one to children vec.
let step = steps.entry(from).or_insert(Step::default());
step.children.push(to);
// Count how many steps must finish before this step is ready.
// We only need the total count, the exact steps are not necessary.
let step = steps.entry(to).or_insert(Step::default());
step.remaining += 1;
}
steps
}
pub fn part1(input: &Input) -> String {
// Move all steps with no dependencies to the `ready` map. A `BTreeMap` is sorted by key
// so will retrieve steps in alphabetical order.
let mut ready = BTreeMap::new();
let mut blocked = FastMap::new();
for (key, step) in input.clone() {
if step.remaining == 0 {
ready.insert(key, step);
} else {
blocked.insert(key, step);
}
}
let mut done = String::new();
while let Some((key, step)) = ready.pop_first() {
// Keep track of the order of completed tasks.
done.push(key as char);
// For each dependent step, decrease the remaining count by one. Once a step reaches zero
// then all its dependencies have been completed and we can move it to the `ready` map.
for key in step.children {
let mut step = blocked.remove(&key).unwrap();
step.remaining -= 1;
if step.remaining == 0 {
ready.insert(key, step);
} else {
blocked.insert(key, step);
}
}
}
done
}
pub fn part2(input: &Input) -> u32 {
part2_testable(input, 5, 60)
}
pub fn part2_testable(input: &Input, max_workers: usize, base_duration: u32) -> u32 {
// Same as part one, move all tasks that are root nodes to the `ready` map.
let mut ready = BTreeMap::new();
let mut blocked = FastMap::new();
for (key, step) in input.clone() {
if step.remaining == 0 {
ready.insert(key, step);
} else {
blocked.insert(key, step);
}
}
// Loop until there are no more steps available and all workers are idle.
let mut time = 0;
let mut workers = Vec::new();
while !ready.is_empty() || !workers.is_empty() {
// Assign any steps to available workers until one or the other runs out first.
while !ready.is_empty() && workers.len() < max_workers {
let (key, step) = ready.pop_first().unwrap();
let finish = time + base_duration + (key - 64) as u32;
// Sort workers in reverse order, so that the worker that will finish first is at
// the end of the vec.
workers.push((finish, step));
workers.sort_unstable_by_key(|(time, _)| u32::MAX - time);
}
// Fast forward time until the earliest available worker finishes their step.
// This may not unblock a dependent step right away in which case the outer loop will
// bring things back here for another worker to complete.
let (finish, step) = workers.pop().unwrap();
time = finish;
// Update dependent tasks the same as part one.
for key in step.children {
let mut step = blocked.remove(&key).unwrap();
step.remaining -= 1;
if step.remaining == 0 {
ready.insert(key, step);
} else {
blocked.insert(key, step);
}
}
}
time
}