Update solution for 2015 day 04
Implement simple thread pool and make sure that the smallest number is always found.
This commit is contained in:
parent
54ba7fffa3
commit
286bc5412f
@ -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<AtomicBool>) {
|
||||
for i in range.0..range.1 {
|
||||
if found.load(Ordering::SeqCst) {
|
||||
break;
|
||||
fn extract_result(pool: &ThreadPool<Vec<usize>>) -> Option<usize> {
|
||||
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<Vec<usize>>, 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::<Vec<_>>()
|
||||
});
|
||||
tries -= 1;
|
||||
start += 20_000;
|
||||
}
|
||||
}
|
||||
|
||||
fn solve_part_2(input: &str, range: (usize, usize), found: Arc<AtomicBool>) {
|
||||
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<Vec<usize>>, 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::<Vec<_>>()
|
||||
});
|
||||
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
107
2015/day-04/threadpool.rs
Normal file
107
2015/day-04/threadpool.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use std::{
|
||||
thread,
|
||||
sync::{Arc, Mutex, mpsc},
|
||||
};
|
||||
|
||||
pub trait Task<O>
|
||||
where O: Send + 'static
|
||||
{
|
||||
fn process(&self) -> O;
|
||||
}
|
||||
|
||||
impl<F, O> Task<O> for F
|
||||
where F: Fn() -> O,
|
||||
F: Send + 'static,
|
||||
O: Send + 'static
|
||||
{
|
||||
fn process(&self) -> O {
|
||||
self()
|
||||
}
|
||||
}
|
||||
|
||||
type BoxTask<O> = Box<dyn Task<O> + Send + 'static>;
|
||||
type TaskReceiver<O> = Arc<Mutex<mpsc::Receiver<BoxTask<O>>>>;
|
||||
type TaskSender<O> = mpsc::Sender<BoxTask<O>>;
|
||||
type OutputSender<O> = Arc<Mutex<mpsc::Sender<O>>>;
|
||||
type OutputReceiver<O> = mpsc::Receiver<O>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Shared<O>
|
||||
where O: Send + Clone + 'static
|
||||
{
|
||||
incoming: TaskReceiver<O>,
|
||||
output: OutputSender<O>,
|
||||
}
|
||||
|
||||
pub struct ThreadPool<O>
|
||||
where O: Send + Clone + 'static
|
||||
{
|
||||
workers: Vec<thread::JoinHandle<()>>,
|
||||
shared: Shared<O>,
|
||||
tasks: TaskSender<O>,
|
||||
results: OutputReceiver<O>,
|
||||
}
|
||||
|
||||
impl<O> ThreadPool<O>
|
||||
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<O> + Send + 'static) {
|
||||
self.tasks.send(Box::new(task)).expect("Failed to enqueue task");
|
||||
}
|
||||
|
||||
pub fn next_result(&self) -> Result<Option<O>, 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user