|
@@ -1,9 +1,9 @@
|
|
|
-use std::{
|
|
|
- cmp::Reverse,
|
|
|
- collections::{BinaryHeap, HashMap, HashSet},
|
|
|
-};
|
|
|
-
|
|
|
-use aoc2022_niels_overkamp::common::{self, AOCResult};
|
|
|
+use aoc2023_niels_overkamp::common::{self, AOCResult};
|
|
|
+use itertools::Itertools;
|
|
|
+use std::iter::once;
|
|
|
+use std::thread;
|
|
|
+use std::error::Error;
|
|
|
+use std::collections::HashMap;
|
|
|
|
|
|
const DAY: &str = "day12";
|
|
|
|
|
@@ -11,109 +11,236 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
common::run(DAY, &run)
|
|
|
}
|
|
|
|
|
|
-enum Dirs {
|
|
|
- Left,
|
|
|
- Up,
|
|
|
- Right,
|
|
|
- Down,
|
|
|
-}
|
|
|
-
|
|
|
-impl Dirs {
|
|
|
- fn apply(&self, (x, y): (usize, usize)) -> (usize, usize) {
|
|
|
- match self {
|
|
|
- Dirs::Left => (x.wrapping_sub(1), y),
|
|
|
- Dirs::Up => (x, y + 1),
|
|
|
- Dirs::Right => (x + 1, y),
|
|
|
- Dirs::Down => (x, y.wrapping_sub(1)),
|
|
|
+fn count_valid_solutions(row: &[u8], description: &Vec<usize>) -> usize {
|
|
|
+ let total_groups = row.len() - description.iter().sum::<usize>() + 1;
|
|
|
+ let mut sum = 0;
|
|
|
+ for solution in (0..total_groups).combinations(description.len()) {
|
|
|
+ let mut valid = true;
|
|
|
+ let mut offset = 0;
|
|
|
+ let mut last = 0;
|
|
|
+ for (index, size) in solution.iter().zip(description.iter()) {
|
|
|
+ let start = index + offset;
|
|
|
+ let end = index+offset+size;
|
|
|
+ // eprint!(" {:?}{:?},",
|
|
|
+// Vec::from_iter(row[last..start].into_iter()).into_iter().map(|n| *n as char).collect::<Vec<_>>(),
|
|
|
+// Vec::from_iter(row[start..end].into_iter()).into_iter().map(|n| *n as char).collect::<Vec<char>>());
|
|
|
+ valid &= row[start..end].into_iter().all(|byte| *byte != b'.');
|
|
|
+ valid &= row[last..start].into_iter().all(|byte| *byte != b'#');
|
|
|
+ if !valid {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ offset += size;
|
|
|
+ last = end;
|
|
|
+ }
|
|
|
+ valid &= row[last..].into_iter().all(|byte| *byte != b'#');
|
|
|
+ if valid {
|
|
|
+ // eprintln!(" valid");
|
|
|
+ sum += 1;
|
|
|
+ } else {
|
|
|
+ // eprintln!("");
|
|
|
}
|
|
|
}
|
|
|
+ return sum
|
|
|
}
|
|
|
|
|
|
-const DIRECTIONS: [Dirs; 4] = [Dirs::Left, Dirs::Up, Dirs::Right, Dirs::Down];
|
|
|
+fn fast_count_valid_solutions(row: &[u8], description: &Vec<usize>) -> usize {
|
|
|
+ let description_length = description.len();
|
|
|
+ let row_length = row.len();
|
|
|
+
|
|
|
+ let mut stack: Vec<(usize, usize, Option<usize>)> = vec![];
|
|
|
+
|
|
|
+ let mut index = 0;
|
|
|
+ let mut size = description[index];
|
|
|
+ let mut offset = 0;
|
|
|
+
|
|
|
+ let mut cache = HashMap::new();
|
|
|
|
|
|
-pub fn run(input: &Vec<String>) -> AOCResult {
|
|
|
- let mut map: HashMap<(usize, usize), u8> = HashMap::new();
|
|
|
- let mut start: Option<(usize, usize)> = None;
|
|
|
- let mut goal: Option<(usize, usize)> = None;
|
|
|
- for (y, line) in input.iter().enumerate() {
|
|
|
- for (x, c) in line.bytes().enumerate() {
|
|
|
- map.insert(
|
|
|
- (x, y),
|
|
|
- match c {
|
|
|
- b'S' => 0,
|
|
|
- b'E' => 25,
|
|
|
- c => c - b'a',
|
|
|
- },
|
|
|
- );
|
|
|
- match c {
|
|
|
- b'S' => start = Some((x, y)),
|
|
|
- b'E' => goal = Some((x, y)),
|
|
|
- _ => (),
|
|
|
+ let count = 'outer: loop {
|
|
|
+ // eprintln!("i:{} o:{} s:{}",index ,offset, size);
|
|
|
+ // eprintln!(" {:?}", cache);
|
|
|
+ // eprintln!(" {:?}", stack);
|
|
|
+ let mut cut = false;
|
|
|
+ let mut valid = true;
|
|
|
+ let mut offset_delta = 0;
|
|
|
+ let mut count = 0;
|
|
|
+
|
|
|
+ if let Some(cached_count) = cache.get(&(index, offset)) {
|
|
|
+ // eprint!(" cache hit");
|
|
|
+ cut = true;
|
|
|
+ count = *cached_count;
|
|
|
+ } else if offset + size > row_length {
|
|
|
+ // eprint!(" too long");
|
|
|
+ cut = true;
|
|
|
+ } else if let Some(blank) = row[offset..offset+size]
|
|
|
+ .into_iter()
|
|
|
+ .position(|byte| *byte == b'.')
|
|
|
+ {
|
|
|
+ // eprint!(" blank at {}", offset+blank);
|
|
|
+ if row[offset..offset+blank].into_iter().any(|byte| *byte == b'#') {
|
|
|
+ // eprint!(" but fill before");
|
|
|
+ cut = true;
|
|
|
+ } else {
|
|
|
+ // eprint!(" offset+={}", blank + 1);
|
|
|
+ offset_delta = blank + 1;
|
|
|
}
|
|
|
+ } else if offset + size < row_length && row[offset+size] == b'#' {
|
|
|
+ // eprint!(" fill at end spacer");
|
|
|
+ if row[offset] == b'#' {
|
|
|
+ // eprint!(" and fill at start");
|
|
|
+ cut = true;
|
|
|
+ } else {
|
|
|
+ // eprint!(" offset++");
|
|
|
+ offset_delta = 1;
|
|
|
+ }
|
|
|
+ } else if index == description_length - 1 {
|
|
|
+ if let Some(filled) = row[offset+size..]
|
|
|
+ .into_iter()
|
|
|
+ .position(|byte| *byte == b'#')
|
|
|
+ {
|
|
|
+ // eprint!(" fill after last group at {}", filled + size + offset);
|
|
|
+ if row[offset..offset+filled+1]
|
|
|
+ .into_iter()
|
|
|
+ .any(|byte| *byte == b'#')
|
|
|
+ {
|
|
|
+ // eprint!(" and fill before with too large gap");
|
|
|
+ cut = true;
|
|
|
+ } else {
|
|
|
+ // eprint!(" offset+={}", filled + 1);
|
|
|
+ offset_delta = filled + 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // eprint!(" completed");
|
|
|
+ if row[offset] == b'#' {
|
|
|
+ // eprint!(" VALID\n\tbut terminates after");
|
|
|
+ cut = true;
|
|
|
+ } else {
|
|
|
+ // eprint!(" offset++ VALID");
|
|
|
+ offset_delta = 1;
|
|
|
+ }
|
|
|
+ count = 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ stack.push((index, offset, None));
|
|
|
+
|
|
|
+ offset_delta = size + 1;
|
|
|
+ offset += offset_delta;
|
|
|
+ index += 1;
|
|
|
+ size = description[index];
|
|
|
+ // eprintln!(" STEP");
|
|
|
+ continue;
|
|
|
}
|
|
|
- }
|
|
|
- let start = start.ok_or("No start found")?;
|
|
|
- let goal = goal.ok_or("No end found")?;
|
|
|
-
|
|
|
- // A* Search
|
|
|
- let h = |(x, y)| goal.0.abs_diff(x) + goal.1.abs_diff(y);
|
|
|
-
|
|
|
- let mut frontier = BinaryHeap::from([Reverse((h(start), start))]);
|
|
|
- let mut visited = HashSet::from([start]);
|
|
|
- let mut g_scores = HashMap::from([(start, 0usize)]);
|
|
|
- let mut f_scores = HashMap::from([(start, h(start))]);
|
|
|
- let mut length = None;
|
|
|
-
|
|
|
- while let Some(Reverse((_, pos))) = frontier.pop() {
|
|
|
- if pos == goal {
|
|
|
- length = g_scores.get(&goal);
|
|
|
- break;
|
|
|
- }
|
|
|
- let height = *map.get(&pos).unwrap();
|
|
|
- let g_score = *g_scores.get(&pos).unwrap();
|
|
|
- for dir in DIRECTIONS {
|
|
|
- let neighbour = dir.apply(pos);
|
|
|
- if let Some(_) = map.get(&neighbour).filter(|hn| **hn <= height + 1) {
|
|
|
- if *g_scores.get(&neighbour).unwrap_or(&usize::MAX) > g_score + 1 {
|
|
|
- g_scores.insert(neighbour, g_score + 1);
|
|
|
- let f_score = g_score + 1 + h(neighbour);
|
|
|
- f_scores.insert(neighbour, f_score);
|
|
|
- if !visited.contains(&neighbour) {
|
|
|
- frontier.push(Reverse((f_score, neighbour)));
|
|
|
- visited.insert(neighbour);
|
|
|
+
|
|
|
+ if cut {
|
|
|
+ // eprintln!(" CUT");
|
|
|
+ // eprintln!("{:?}\n{:?}", cache, stack);
|
|
|
+ cache.insert((index, offset), count);
|
|
|
+ 'inner: loop {
|
|
|
+ // eprintln!("{:?}\n{:?}", cache, stack);
|
|
|
+ match stack.pop() {
|
|
|
+ Some((entry_index, entry_offset, Some(entry_count)))
|
|
|
+ => {
|
|
|
+ count += entry_count;
|
|
|
+ cache.insert((entry_index, entry_offset), count);
|
|
|
+ }
|
|
|
+ Some((entry_index, entry_offset, None))
|
|
|
+ => {
|
|
|
+ if row[entry_offset] != b'#' {
|
|
|
+ stack.push((entry_index, entry_offset, Some(count)));
|
|
|
+ index = entry_index;
|
|
|
+ offset = entry_offset + 1;
|
|
|
+ size = description[index];
|
|
|
+ break 'inner;
|
|
|
+ } else {
|
|
|
+ cache.insert((entry_index, entry_offset), count);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ None
|
|
|
+ => {
|
|
|
+ // eprintln!("DONE");
|
|
|
+ break 'outer count;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ // eprintln!("{:?}\n{:?}", cache, stack);
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ stack.push((index, offset, Some(count)));
|
|
|
+ offset += offset_delta;
|
|
|
+ // eprintln!("");
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- let length = length.ok_or("No path to goal found")?;
|
|
|
+ };
|
|
|
+ // println!("\t\t{}", count);
|
|
|
+ return count;
|
|
|
+}
|
|
|
|
|
|
- let mut frontier = BinaryHeap::from([Reverse((0, goal))]);
|
|
|
- let mut visited = HashSet::from([goal]);
|
|
|
- let mut shortest_path = usize::MAX;
|
|
|
|
|
|
- while let Some(Reverse((length, pos))) = frontier.pop() {
|
|
|
- if *map.get(&pos).unwrap() == 0 {
|
|
|
- shortest_path = shortest_path.min(length);
|
|
|
- }
|
|
|
- let height = *map.get(&pos).unwrap();
|
|
|
- for neighbour in DIRECTIONS
|
|
|
- .iter()
|
|
|
- .map(|d| d.apply(pos))
|
|
|
- .filter(|pos| !visited.contains(pos))
|
|
|
- .filter(|pos| map.get(pos).map(|h| (*h + 1) >= height).unwrap_or(false))
|
|
|
- .collect::<Vec<_>>()
|
|
|
- {
|
|
|
- frontier.push(Reverse((length + 1, neighbour)));
|
|
|
- visited.insert(neighbour);
|
|
|
- }
|
|
|
+pub fn run(input: &Vec<String>) -> AOCResult {
|
|
|
+ let mut input = input.clone();
|
|
|
+ let mut sum = 0;
|
|
|
+ let mut long_sum = 0;
|
|
|
+
|
|
|
+ let mut handles = vec![];
|
|
|
+ let threads = 1;
|
|
|
+ for i in 0..threads {
|
|
|
+ let input = input.split_off(input.len() - input.len() / (threads - i));
|
|
|
+ let handle = thread::spawn(move || -> Result<_, String> {
|
|
|
+ let mut sum = 0;
|
|
|
+ let mut long_sum = 0;
|
|
|
+ let l = input.len();
|
|
|
+ for (j, line) in input.into_iter().enumerate() {
|
|
|
+ let temp = sum;
|
|
|
+ if let Some((row, description)) = line.split_once(' ') {
|
|
|
+ let description = description
|
|
|
+ .split(',')
|
|
|
+ .map(str::parse::<usize>)
|
|
|
+ .collect::<Result<Vec<usize>, _>>()
|
|
|
+ .map_err(|e| e.to_string())?;
|
|
|
+// print!("{}", row);
|
|
|
+ let row = row.as_bytes();
|
|
|
+
|
|
|
+ sum += fast_count_valid_solutions(row, &description);
|
|
|
+
|
|
|
+ let len = description.len()*5;
|
|
|
+ let description = Vec::from_iter(description.into_iter().cycle().take(len));
|
|
|
+ let len = row.len()*5 + 4;
|
|
|
+ let row: Vec<u8> = Vec::from_iter(row.into_iter().map(|n| *n).chain(once(b'?')).cycle().take(len));
|
|
|
+// print!("\tlong");
|
|
|
+ long_sum += fast_count_valid_solutions(row.as_slice(), &description);
|
|
|
+ } else {
|
|
|
+ return Err(format!("{} is not a valid \"row description\" format", line));
|
|
|
+ }
|
|
|
+ println!("{}: ({}/{})\t", i, j + 1, l);
|
|
|
+ }
|
|
|
+ return Ok((sum, long_sum));
|
|
|
+ });
|
|
|
+ handles.push(handle);
|
|
|
}
|
|
|
-
|
|
|
- Ok([Some(length.to_string()), Some(shortest_path.to_string())])
|
|
|
+
|
|
|
+ for handle in handles.into_iter() {
|
|
|
+ let (result, long_result) = (handle.join().unwrap())?;
|
|
|
+ sum += result;
|
|
|
+ long_sum += long_result;
|
|
|
+ }
|
|
|
+ Ok([Some(sum.to_string()), Some(long_sum.to_string())])
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
pub fn test_day12() {
|
|
|
assert!(common::run_test(DAY, &run))
|
|
|
+// loop {
|
|
|
+// let mut line = String::new();
|
|
|
+// std::io::stdin().read_line(&mut line).unwrap();
|
|
|
+// write!(std::io::stderr(), "{}", line);
|
|
|
+// match run(&Vec::from([line[..line.len()-1].to_owned()])) {
|
|
|
+// Ok([v, _]) => {
|
|
|
+// write!(std::io::stderr(), "{}\n", v.unwrap()).unwrap();
|
|
|
+// std::io::stderr().flush().unwrap();
|
|
|
+// }
|
|
|
+// Err(e) => {
|
|
|
+// write!(std::io::stderr(), "{}\n", e).unwrap();
|
|
|
+// std::io::stderr().flush().unwrap();
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
}
|
|
|
+
|