diff --git a/2015/day-04/main.rs b/2015/day-04/main.rs index da55172..e6b6396 100644 --- a/2015/day-04/main.rs +++ b/2015/day-04/main.rs @@ -1,4 +1,7 @@ mod md5; +mod threadpool; + +use threadpool::ThreadPool; macro_rules! infile { [$path:literal] => { ($path, include_str!($path)) } @@ -9,86 +12,90 @@ const INPUTS: &[(&str, &str)] = &[ infile!("inputs/puzzle.txt"), ]; -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; - -fn solve_part_1(input: &str, range: (usize, usize), found: Arc) { - for i in range.0..range.1 { - if found.load(Ordering::SeqCst) { - break; +fn extract_result(pool: &ThreadPool>) -> Option { + let mut smallest = None; + while let Some(result) = pool.next_result().unwrap() { + let result = result.into_iter().min(); + if !result.is_none() && (smallest.is_none() || smallest > result) { + smallest = result; } - let secret = format!("{}{}", input, i); - let hexdigest = md5::hexdigest(&secret); + } + smallest +} - if hexdigest.starts_with("00000") { - found.store(true, Ordering::SeqCst); - println!("5 zeroes found with hexdigest {} for {}: {}", hexdigest, secret, i); - break; +fn solve_part_1(pool: &ThreadPool>, input: &str) -> usize { + let mut start = 1; + let mut tries = 7; + + loop { + if tries == 0 { + match extract_result(&pool) { + None =>{ tries = 7 }, + Some(v) => return v, + } } + + let input = input.to_string(); + pool.add_task(move || { + (start..).into_iter() + .take(20_000) + .filter_map(|i| { + let secret = format!("{}{}", input, i); + let hexdigest = md5::hexdigest(&secret); + if hexdigest.starts_with("00000") { Some(i) } else { None } + }) + .collect::>() + }); + tries -= 1; + start += 20_000; } } -fn solve_part_2(input: &str, range: (usize, usize), found: Arc) { - for i in range.0..range.1 { - if found.load(Ordering::SeqCst) { - return; - } - let secret = format!("{}{}", input, i); - let hexdigest = md5::hexdigest(&secret); +fn solve_part_2(pool: &ThreadPool>, input: &str) -> usize { + let mut start = 1; + let mut tries = 8; - if hexdigest.starts_with("000000") { - found.store(true, Ordering::SeqCst); - println!("6 zeroes found with hexdigest {} for {}: {}", hexdigest, input, i); - break; + loop { + if tries == 0 { + match extract_result(&pool) { + None =>{ tries = 7 }, + Some(v) => return v, + } } + + let input = input.to_string(); + pool.add_task(move || { + (start..).into_iter() + .take(20_000) + .filter_map(|i| { + let secret = format!("{}{}", input, i); + let hexdigest = md5::hexdigest(&secret); + if hexdigest.starts_with("000000") { Some(i) } else { None } + }) + .collect::>() + }); + + tries -= 1; + start += 20_000; } } fn main() { - use std::{thread, process}; - - let mut threads = Vec::new(); + let pool = ThreadPool::with_threads(8); for (path, input) in INPUTS { println!("File: {}", path); for line in input.split('\n') { - if line.is_empty() { - continue; - } + if line.is_empty() { continue } - let found_solution_1 = Arc::new(AtomicBool::new(false)); - let found_solution_2 = Arc::new(AtomicBool::new(false)); + println!("\tInput: {}", line); - let ranges = (0..).into_iter().step_by(100_000).take(15).collect::>(); - for window in ranges.windows(2) { - let (from, to) = (window[0], window[1]); - println!("Searching solutions for {} in {}..{}", line, from, to); + let solution = solve_part_1(&pool, &line); + println!("\t\tFive leading zeroes: {}", solution); - let found = found_solution_1.clone(); - let t = thread::spawn(move || { - solve_part_1(&line, (from, to), found) - }); - threads.push(t); - } - - let ranges = (0..).into_iter().step_by(1_000_000).take(15).collect::>(); - for window in ranges.windows(2) { - let (from, to) = (window[0], window[1]); - println!("Searching solution for {} in {}..{}", line, from, to); - - let found = found_solution_2.clone(); - let t = thread::spawn(move || { - solve_part_2(&line, (from, to), found) - }); - threads.push(t); - } - } - } - - for thread in threads { - if let Err(_) = thread.join() { - eprintln!("Failed to join thread"); - process::exit(1); + let solution = solve_part_2(&pool, &line); + println!("\t\tSix leading zeroes: {}", solution); } } } diff --git a/2015/day-04/threadpool.rs b/2015/day-04/threadpool.rs new file mode 100644 index 0000000..df40181 --- /dev/null +++ b/2015/day-04/threadpool.rs @@ -0,0 +1,107 @@ +use std::{ + thread, + sync::{Arc, Mutex, mpsc}, +}; + +pub trait Task + where O: Send + 'static +{ + fn process(&self) -> O; +} + +impl Task for F + where F: Fn() -> O, + F: Send + 'static, + O: Send + 'static +{ + fn process(&self) -> O { + self() + } +} + +type BoxTask = Box + Send + 'static>; +type TaskReceiver = Arc>>>; +type TaskSender = mpsc::Sender>; +type OutputSender = Arc>>; +type OutputReceiver = mpsc::Receiver; + +#[derive(Clone)] +struct Shared + where O: Send + Clone + 'static +{ + incoming: TaskReceiver, + output: OutputSender, +} + +pub struct ThreadPool + where O: Send + Clone + 'static +{ + workers: Vec>, + shared: Shared, + tasks: TaskSender, + results: OutputReceiver, +} + +impl ThreadPool + where O: Send + Clone + 'static +{ + pub fn with_threads(size: usize) -> Self { + let (tasks, incoming) = mpsc::channel(); + let (output, results) = mpsc::channel(); + let shared = Shared { + incoming: Arc::new(Mutex::new(incoming)), + output: Arc::new(Mutex::new(output)), + }; + let mut pool = Self { + workers: Vec::new(), + shared, + tasks, + results + }; + + for worker_id in 0..size { + pool.spawn_worker(worker_id); + } + + pool + } + + pub fn add_task(&self, task: impl Task + Send + 'static) { + self.tasks.send(Box::new(task)).expect("Failed to enqueue task"); + } + + pub fn next_result(&self) -> Result, mpsc::TryRecvError> { + match self.results.try_recv() { + Ok(output) => Ok(Some(output)), + Err(mpsc::TryRecvError::Empty) => Ok(None), + Err(e) => Err(e), + } + } + + fn spawn_worker(&mut self, worker_id: usize) { + let shared = self.shared.clone(); + let worker = thread::Builder::new() + .name(format!("worker-{}", worker_id)) + .spawn(move || { + loop { + let task = { + let incoming = shared.incoming.lock().expect("Lock was poisoned"); + match incoming.recv() { + Ok(task) => task, + Err(_) => break, + } + }; + + let output = task.process(); + + let output_sender = shared.output.lock().expect("Lock was poisoned"); + if let Err(_) = output_sender.send(output) { + break; + } + } + }) + .expect("Failed to spawn thread pool worker"); + + self.workers.push(worker); + } +}