day-12 done
This commit is contained in:
1
2023/Cargo.lock
generated
1
2023/Cargo.lock
generated
@@ -118,6 +118,7 @@ version = "2023.0.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"nom",
|
||||
"rstest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -10,3 +10,6 @@ repository.workspace = true
|
||||
[dependencies]
|
||||
nom = { workspace = true }
|
||||
itertools = {workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = {workspace = true}
|
||||
|
||||
@@ -1,20 +1,155 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete,
|
||||
multi::{many1, separated_list1},
|
||||
sequence::separated_pair,
|
||||
IResult, Parser,
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
|
||||
enum SpringStatus {
|
||||
Working,
|
||||
Failing,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
struct Row {
|
||||
springs: Vec<SpringStatus>,
|
||||
broken_spans: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Row {
|
||||
fn process(&self) -> usize {
|
||||
let num_broken = self.broken_spans.iter().sum();
|
||||
let row_len = self.springs.len();
|
||||
let max_perm = 1_u32 << row_len;
|
||||
(1..max_perm)
|
||||
.filter(|x| x.count_ones() == num_broken)
|
||||
.map(|x| {
|
||||
let mut perm = Vec::new();
|
||||
(0..row_len).map(|y| 1 << y).for_each(|y| {
|
||||
if y & x == 0 {
|
||||
perm.push(SpringStatus::Working);
|
||||
} else {
|
||||
perm.push(SpringStatus::Failing);
|
||||
}
|
||||
});
|
||||
perm
|
||||
})
|
||||
.filter(|x| {
|
||||
self.springs
|
||||
.iter()
|
||||
.zip(x.iter())
|
||||
.all(|(a, b)| (a == b || *a == SpringStatus::Unknown))
|
||||
})
|
||||
.filter(|x| {
|
||||
let (mut array, last, current_run) =x.iter()
|
||||
.fold(
|
||||
(Vec::new(), SpringStatus::Working, 0_u32),
|
||||
|(mut array, _last, mut current_run), x| {
|
||||
if *x == SpringStatus::Failing {
|
||||
current_run += 1;
|
||||
} else {
|
||||
if current_run > 0 {
|
||||
array.push(current_run);
|
||||
}
|
||||
current_run = 0;
|
||||
}
|
||||
(array, *x, current_run)
|
||||
},
|
||||
);
|
||||
if last == SpringStatus::Failing {
|
||||
array.push(current_run);
|
||||
}
|
||||
array
|
||||
.iter()
|
||||
.zip(self.broken_spans.iter())
|
||||
.all(|(a, b)| a == b)
|
||||
})
|
||||
.count()
|
||||
}
|
||||
|
||||
//fn generate_permiatation(&self)
|
||||
}
|
||||
|
||||
/// day 12 part 1 of aoc 2023
|
||||
///
|
||||
/// # Arguments
|
||||
/// - input the input for today's puzzle
|
||||
///
|
||||
/// # Panics
|
||||
/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than
|
||||
/// usize
|
||||
#[must_use]
|
||||
pub fn part1 (_input: &str) -> String {
|
||||
"Not Finished".to_string()
|
||||
pub fn part1(input: &str) -> String {
|
||||
let (_, spas) = parse_input(input).expect("AOC always has valid input");
|
||||
spas.iter()
|
||||
.map(|x| x.process() as u64)
|
||||
.sum::<u64>()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn parse_spa_spots(input: &str) -> IResult<&str, Vec<SpringStatus>> {
|
||||
many1(alt((
|
||||
tag(".").map(|_| SpringStatus::Working),
|
||||
tag("#").map(|_| SpringStatus::Failing),
|
||||
tag("?").map(|_| SpringStatus::Unknown),
|
||||
)))(input)
|
||||
}
|
||||
|
||||
fn parse_spa_spans(input: &str) -> IResult<&str, Vec<u32>> {
|
||||
separated_list1(tag(","), complete::u32)(input)
|
||||
}
|
||||
|
||||
fn parse_spa_rows(input: &str) -> IResult<&str, Row> {
|
||||
separated_pair(parse_spa_spots, complete::space1, parse_spa_spans)(input).map(
|
||||
|(input, (springs, broken_spans))| {
|
||||
(
|
||||
input,
|
||||
Row {
|
||||
springs,
|
||||
broken_spans,
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_input(input: &str) -> IResult<&str, Vec<Row>> {
|
||||
separated_list1(complete::line_ending, parse_spa_rows)(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
const INPUT: &str = "";
|
||||
#[rstest]
|
||||
#[case("???.### 1,1,3", 1)]
|
||||
#[case(".??..??...?##. 1,1,3", 4)]
|
||||
#[case("?#?#?#?#?#?#?#? 1,3,1,6", 1)]
|
||||
#[case("????.#...#... 4,1,1", 1)]
|
||||
#[case("????.######..#####. 1,6,5", 4)]
|
||||
#[case("?###???????? 3,2,1", 10)]
|
||||
fn line_test(#[case] input: &str, #[case] expected: usize) {
|
||||
let (_, row) = parse_spa_rows(input).expect("should parse");
|
||||
assert_eq!(row.process(), expected);
|
||||
}
|
||||
|
||||
const INPUT: &str = "???.### 1,1,3
|
||||
.??..??...?##. 1,1,3
|
||||
?#?#?#?#?#?#?#? 1,3,1,6
|
||||
????.#...#... 4,1,1
|
||||
????.######..#####. 1,6,5
|
||||
?###???????? 3,2,1";
|
||||
|
||||
#[test]
|
||||
fn part1_works() {
|
||||
let result = part1(INPUT);
|
||||
assert_eq!(result, "Not Finished".to_string());
|
||||
assert_eq!(result, "21".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,171 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
|
||||
use std::{iter::repeat, collections::HashMap};
|
||||
|
||||
use itertools::Itertools;
|
||||
use nom::{
|
||||
bytes::complete::{tag, is_a},
|
||||
character::complete,
|
||||
multi::separated_list1,
|
||||
sequence::separated_pair,
|
||||
IResult};
|
||||
|
||||
|
||||
struct Row {
|
||||
springs: String,
|
||||
broken_spans: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Row {
|
||||
fn process(&self) -> usize {
|
||||
let mut cache = HashMap::new();
|
||||
self.dynamic_search(&mut cache, (0,0,0))
|
||||
}
|
||||
|
||||
fn dynamic_search(&self, cache: &mut HashMap<(usize,usize,u32),usize>, search: (usize, usize, u32)) -> usize {
|
||||
let (data_index, group_index, group_size) = search;
|
||||
//are we at the end of the input
|
||||
if data_index >= self.springs.len(){
|
||||
// when group_index is greater we are here then golden
|
||||
if group_index >= self.broken_spans.len() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
//we haven't satisfied groups but the end is failing
|
||||
if group_index == self.broken_spans.len() - 1 && self.broken_spans[group_index] == group_size {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
match self.springs.as_bytes()[data_index] {
|
||||
b'.' => {
|
||||
//previous was also working just go to next data point
|
||||
if group_size == 0 {
|
||||
return self.dynamic_search(cache, (data_index + 1, group_index, 0));
|
||||
}
|
||||
|
||||
//we failed to match the group
|
||||
if group_index >= self.broken_spans.len() || self.broken_spans[group_index] != group_size{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//completed a group keep going
|
||||
self.dynamic_search(cache,(data_index+1, group_index +1, 0))
|
||||
},
|
||||
b'#' => {
|
||||
//too many for our group
|
||||
if group_index >= self.broken_spans.len() || group_size + 1 > self.broken_spans[group_index] {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//haven't completed group yet keep looking
|
||||
self.dynamic_search(cache, (data_index+1, group_index, group_size + 1))
|
||||
},
|
||||
b'?' => {
|
||||
if let Some(res) = cache.get(&(data_index,group_index,group_size)).copied() {
|
||||
return res;
|
||||
}
|
||||
|
||||
let mut perms = 0_usize;
|
||||
|
||||
//pretend to be a undamaged, if in a working group
|
||||
if 0 == group_size {
|
||||
perms += self.dynamic_search(cache, (data_index +1, group_index, 0));
|
||||
}
|
||||
|
||||
//pretend to be damaged
|
||||
if group_index < self.broken_spans.len() && group_size < self.broken_spans[group_index] {
|
||||
perms += self.dynamic_search(cache, (data_index + 1, group_index, group_size +1));
|
||||
}
|
||||
|
||||
//pretend to be undamage, thus ending a damaged group
|
||||
if group_index < self.broken_spans.len() && group_size == self.broken_spans[group_index] {
|
||||
perms += self.dynamic_search(cache, (data_index +1, group_index+1, 0));
|
||||
}
|
||||
|
||||
cache.insert((data_index, group_index, group_size),perms);
|
||||
perms
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// day 12 part 2 of aoc 2023
|
||||
///
|
||||
/// # Arguments
|
||||
/// - input the input for today's puzzle
|
||||
///
|
||||
/// # Panics
|
||||
/// panics whne it cannot parse the input OR when ever the number of game numbers is greater than
|
||||
/// usize
|
||||
#[must_use]
|
||||
pub fn part2 (_input: &str) -> String {
|
||||
"Not Finished".to_string()
|
||||
pub fn part2(input: &str) -> String {
|
||||
let (_, spas) = parse_input(input).expect("AOC always has valid input");
|
||||
spas.iter()
|
||||
.map(|x| x.process() as u64)
|
||||
.sum::<u64>()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn parse_spa_spots(input: &str) -> IResult<&str, &str> {
|
||||
is_a(".#?")(input)
|
||||
}
|
||||
|
||||
fn parse_spa_spans(input: &str) -> IResult<&str, Vec<u32>> {
|
||||
separated_list1(tag(","), complete::u32)(input)
|
||||
}
|
||||
|
||||
fn parse_spa_rows(input: &str) -> IResult<&str, Row> {
|
||||
separated_pair(parse_spa_spots, complete::space1, parse_spa_spans)(input).map(
|
||||
|(input, (springs, broken_spans))| {
|
||||
(
|
||||
input,
|
||||
Row {
|
||||
springs: std::iter::repeat(springs).take(5).join("?"),
|
||||
broken_spans: repeat(broken_spans.iter())
|
||||
.take(5)
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect(),
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_input(input: &str) -> IResult<&str, Vec<Row>> {
|
||||
separated_list1(complete::line_ending, parse_spa_rows)(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
const INPUT: &str = "";
|
||||
#[rstest]
|
||||
#[case("???.### 1,1,3", 1)]
|
||||
#[case(".??..??...?##. 1,1,3", 16_384)]
|
||||
#[case("?#?#?#?#?#?#?#? 1,3,1,6", 1)]
|
||||
#[case("????.#...#... 4,1,1", 16)]
|
||||
#[case("????.######..#####. 1,6,5", 2_500)]
|
||||
#[case("?###???????? 3,2,1", 506_250)]
|
||||
fn line_test(#[case] input: &str, #[case] expected: usize) {
|
||||
let (_, row) = parse_spa_rows(input).expect("should parse");
|
||||
assert_eq!(row.process(), expected);
|
||||
}
|
||||
|
||||
const INPUT: &str = "???.### 1,1,3
|
||||
.??..??...?##. 1,1,3
|
||||
?#?#?#?#?#?#?#? 1,3,1,6
|
||||
????.#...#... 4,1,1
|
||||
????.######..#####. 1,6,5
|
||||
?###???????? 3,2,1";
|
||||
|
||||
#[test]
|
||||
fn part2_works() {
|
||||
let result = part2(INPUT);
|
||||
assert_eq!(result, "Not Finished".to_string());
|
||||
assert_eq!(result, "525152".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user