Skip to content

Commit dd92b27

Browse files
authored
Merge pull request #215 from jwodder/2023-12
Solved 2023-12*
2 parents a54a9ac + a069f25 commit dd92b27

File tree

7 files changed

+270
-0
lines changed

7 files changed

+270
-0
lines changed

2023/12a/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "advent-of-code-2023-12a"
3+
edition.workspace = true
4+
rust-version.workspace = true
5+
description = "Solution to Advent of Code 2023, problem 12a"
6+
authors.workspace = true
7+
repository.workspace = true
8+
license.workspace = true
9+
keywords = ["dynamic-programming"]
10+
11+
[dependencies]
12+
adventutil = { path = "../../adventutil" }
13+
14+
[lints]
15+
workspace = true

2023/12a/src/main.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use adventutil::Input;
2+
use adventutil::pullparser::{ParseError, PullParser, Token};
3+
4+
#[derive(Clone, Debug, Eq, PartialEq)]
5+
struct SpringRow {
6+
record: Vec<Spring>,
7+
contiguous: Vec<usize>,
8+
}
9+
10+
impl SpringRow {
11+
#[allow(clippy::needless_range_loop)]
12+
fn arrangements(&self) -> u32 {
13+
let record_len = self.record.len();
14+
let contig_len = self.contiguous.len();
15+
// `tbl[i][j]` will contain the number of arrangements of
16+
// `self.contiguous[j..]` in the record `self.record[i..]`
17+
let mut tbl = vec![vec![0u32; contig_len + 1]; record_len + 1];
18+
tbl[record_len][contig_len] = 1;
19+
for j in 0..contig_len {
20+
tbl[record_len][j] = 0;
21+
}
22+
for i in (0..record_len).rev() {
23+
tbl[i][contig_len] =
24+
u32::from(tbl[i + 1][contig_len] == 1 && self.record[i] != Spring::Damaged);
25+
}
26+
for i in (0..record_len).rev() {
27+
for j in (0..contig_len).rev() {
28+
if matches!(self.record[i], Spring::Operational | Spring::Unknown) {
29+
tbl[i][j] += tbl[i + 1][j];
30+
}
31+
if matches!(self.record[i], Spring::Damaged | Spring::Unknown) {
32+
let start = i;
33+
let end = i + self.contiguous[j];
34+
if end <= record_len
35+
&& self.record[start..end]
36+
.iter()
37+
.all(|&s| s != Spring::Operational)
38+
{
39+
if end == record_len {
40+
tbl[i][j] += tbl[end][j + 1];
41+
} else if self.record[end] != Spring::Damaged {
42+
tbl[i][j] += tbl[end + 1][j + 1];
43+
}
44+
}
45+
}
46+
}
47+
}
48+
tbl[0][0]
49+
}
50+
}
51+
52+
impl std::str::FromStr for SpringRow {
53+
type Err = ParseError;
54+
55+
fn from_str(s: &str) -> Result<SpringRow, ParseError> {
56+
let mut parser = PullParser::new(s);
57+
let record = parser.scan_to(Token::Whitespace)?;
58+
let record = record
59+
.chars()
60+
.map(|c| match c {
61+
'.' => Ok(Spring::Operational),
62+
'#' => Ok(Spring::Damaged),
63+
'?' => Ok(Spring::Unknown),
64+
_ => Err(ParseError::InvalidToken(c.to_string())),
65+
})
66+
.collect::<Result<Vec<_>, _>>()?;
67+
let contiguous = parser.delimited(',', |s| s.parse::<usize>().map_err(Into::into))?;
68+
Ok(SpringRow { record, contiguous })
69+
}
70+
}
71+
72+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73+
enum Spring {
74+
Operational,
75+
Damaged,
76+
Unknown,
77+
}
78+
79+
fn solve(input: Input) -> u32 {
80+
input
81+
.parse_lines::<SpringRow>()
82+
.map(|sr| sr.arrangements())
83+
.sum()
84+
}
85+
86+
fn main() {
87+
println!("{}", solve(Input::from_env()));
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
94+
#[test]
95+
fn example1() {
96+
let input = Input::from(concat!(
97+
"???.### 1,1,3\n",
98+
".??..??...?##. 1,1,3\n",
99+
"?#?#?#?#?#?#?#? 1,3,1,6\n",
100+
"????.#...#... 4,1,1\n",
101+
"????.######..#####. 1,6,5\n",
102+
"?###???????? 3,2,1\n",
103+
));
104+
assert_eq!(solve(input), 21);
105+
}
106+
}

2023/12b/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "advent-of-code-2023-12b"
3+
edition.workspace = true
4+
rust-version.workspace = true
5+
description = "Solution to Advent of Code 2023, problem 12b"
6+
authors.workspace = true
7+
repository.workspace = true
8+
license.workspace = true
9+
keywords = ["dynamic-programming"]
10+
11+
[dependencies]
12+
adventutil = { path = "../../adventutil" }
13+
14+
[lints]
15+
workspace = true

2023/12b/src/main.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use adventutil::Input;
2+
use adventutil::pullparser::{ParseError, PullParser, Token};
3+
4+
#[derive(Clone, Debug, Eq, PartialEq)]
5+
struct SpringRow {
6+
record: Vec<Spring>,
7+
contiguous: Vec<usize>,
8+
}
9+
10+
impl SpringRow {
11+
fn unfold(&self) -> SpringRow {
12+
let mut record = Vec::with_capacity(self.record.len() * 5 + 4);
13+
record.extend(self.record.clone());
14+
for _ in 0..4 {
15+
record.push(Spring::Unknown);
16+
record.extend(self.record.clone());
17+
}
18+
let contiguous = std::iter::repeat_n(self.contiguous.iter(), 5)
19+
.flatten()
20+
.copied()
21+
.collect();
22+
SpringRow { record, contiguous }
23+
}
24+
25+
#[allow(clippy::needless_range_loop)]
26+
fn arrangements(&self) -> u64 {
27+
let record_len = self.record.len();
28+
let contig_len = self.contiguous.len();
29+
// `tbl[i][j]` will contain the number of arrangements of
30+
// `self.contiguous[j..]` in the record `self.record[i..]`
31+
let mut tbl = vec![vec![0u64; contig_len + 1]; record_len + 1];
32+
tbl[record_len][contig_len] = 1;
33+
for j in 0..contig_len {
34+
tbl[record_len][j] = 0;
35+
}
36+
for i in (0..record_len).rev() {
37+
tbl[i][contig_len] =
38+
u64::from(tbl[i + 1][contig_len] == 1 && self.record[i] != Spring::Damaged);
39+
}
40+
for i in (0..record_len).rev() {
41+
for j in (0..contig_len).rev() {
42+
if matches!(self.record[i], Spring::Operational | Spring::Unknown) {
43+
tbl[i][j] += tbl[i + 1][j];
44+
}
45+
if matches!(self.record[i], Spring::Damaged | Spring::Unknown) {
46+
let start = i;
47+
let end = i + self.contiguous[j];
48+
if end <= record_len
49+
&& self.record[start..end]
50+
.iter()
51+
.all(|&s| s != Spring::Operational)
52+
{
53+
if end == record_len {
54+
tbl[i][j] += tbl[end][j + 1];
55+
} else if self.record[end] != Spring::Damaged {
56+
tbl[i][j] += tbl[end + 1][j + 1];
57+
}
58+
}
59+
}
60+
}
61+
}
62+
tbl[0][0]
63+
}
64+
}
65+
66+
impl std::str::FromStr for SpringRow {
67+
type Err = ParseError;
68+
69+
fn from_str(s: &str) -> Result<SpringRow, ParseError> {
70+
let mut parser = PullParser::new(s);
71+
let record = parser.scan_to(Token::Whitespace)?;
72+
let record = record
73+
.chars()
74+
.map(|c| match c {
75+
'.' => Ok(Spring::Operational),
76+
'#' => Ok(Spring::Damaged),
77+
'?' => Ok(Spring::Unknown),
78+
_ => Err(ParseError::InvalidToken(c.to_string())),
79+
})
80+
.collect::<Result<Vec<_>, _>>()?;
81+
let contiguous = parser.delimited(',', |s| s.parse::<usize>().map_err(Into::into))?;
82+
Ok(SpringRow { record, contiguous })
83+
}
84+
}
85+
86+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87+
enum Spring {
88+
Operational,
89+
Damaged,
90+
Unknown,
91+
}
92+
93+
fn solve(input: Input) -> u64 {
94+
input
95+
.parse_lines::<SpringRow>()
96+
.map(|sr| sr.unfold().arrangements())
97+
.sum()
98+
}
99+
100+
fn main() {
101+
println!("{}", solve(Input::from_env()));
102+
}
103+
104+
#[cfg(test)]
105+
mod tests {
106+
use super::*;
107+
108+
#[test]
109+
fn example1() {
110+
let input = Input::from(concat!(
111+
"???.### 1,1,3\n",
112+
".??..??...?##. 1,1,3\n",
113+
"?#?#?#?#?#?#?#? 1,3,1,6\n",
114+
"????.#...#... 4,1,1\n",
115+
"????.######..#####. 1,6,5\n",
116+
"?###???????? 3,2,1\n",
117+
));
118+
assert_eq!(solve(input), 525152);
119+
}
120+
}

2023/answers.csv

43 Bytes
Binary file not shown.

2023/inputs/12.txt

22.2 KB
Binary file not shown.

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)