This commit is contained in:
Maxime Augier 2023-12-14 17:34:34 +01:00
parent d0a7ddea55
commit 82aa5ccf03
6 changed files with 1636 additions and 0 deletions

16
day12/Cargo.lock generated Normal file
View File

@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "day12"
version = "0.1.0"
dependencies = [
"anyhow",
]

9
day12/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "day12"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.75"

1000
day12/input Normal file

File diff suppressed because it is too large Load Diff

363
day12/log Normal file
View File

@ -0,0 +1,363 @@
Compiling day12 v0.1.0 (/home/max/aoc2023/day12)
warning: unused imports: `Deref`, `Index`, `RangeBounds`, `Range`, `borrow::Borrow`
--> src/main.rs:1:50
|
1 | use std::{str::FromStr, slice::SliceIndex, ops::{Index, Deref, RangeBounds, Range, RangeFrom}, borrow::Borrow, io::stdin, collections::Ha...
| ^^^^^ ^^^^^ ^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: unused import: `slice::SliceIndex`
--> src/main.rs:1:25
|
1 | use std::{str::FromStr, slice::SliceIndex, ops::{Index, Deref, RangeBounds, Range, RangeFrom}, borrow::Borrow, io::stdin, collections::Ha...
| ^^^^^^^^^^^^^^^^^
warning: variable does not need to be mutable
--> src/main.rs:194:9
|
194 | let mut rows: Vec<Row> = stdin().lines().map(|l| {
| ----^^^^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
warning: methods `enumerate`, `optimize`, and `multiply` are never used
--> src/main.rs:25:8
|
20 | impl Row {
| -------- methods in this implementation
...
25 | fn enumerate<F>(&self, f: &mut F)
| ^^^^^^^^^
...
32 | fn optimize(&mut self) {
| ^^^^^^^^
...
44 | fn multiply(self, n: usize) -> Self {
| ^^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: methods `compatible` and `enumerate` are never used
--> src/main.rs:96:8
|
94 | impl <'r> RowView<'r> {
| --------------------- methods in this implementation
95 |
96 | fn compatible(&self, buffer: &[bool]) -> bool {
| ^^^^^^^^^^
...
148 | fn enumerate<F>(&self, buf: &mut [bool], window: RangeFrom<usize>, f: &mut F)
| ^^^^^^^^^
warning: function `pretty` is never used
--> src/main.rs:188:4
|
188 | fn pretty(row: &[bool]) -> String {
| ^^^^^^
warning: `day12` (bin "day12") generated 6 warnings (run `cargo fix --bin "day12"` to apply 2 suggestions)
Finished release [optimized] target(s) in 0.32s
Running `target/release/day12`
Counting solutions for RowView { bits: [None, None, None, Some(false), Some(true), Some(true), Some(true)], pattern: [1, 1, 3], freedom: 0 }
Descending p=0 consumed=0 freedom=0
Trying offset 0
Descending...
Descending p=2 consumed=1 freedom=0
Trying offset 0
Descending...
Descending p=4 consumed=2 freedom=0
Trying offset 0
Descending...
Descending p=8 consumed=3 freedom=0
no blocks remain
1 at position 5
1 at position 3
1 at position 1
Counting solutions for RowView { bits: [Some(false), None, None, Some(false), Some(false), None, None, Some(false), Some(false), Some(false), None, Some(true), Some(true), Some(false)], pattern: [1, 1, 3], freedom: 7 }
Descending p=0 consumed=0 freedom=7
Trying offset 0
Bit cleared in range 0..1, continuing
Trying offset 1
Descending...
Descending p=3 consumed=1 freedom=6
Trying offset 0
Bit cleared in range 3..4, continuing
Trying offset 1
Bit cleared in range 4..5, continuing
Trying offset 2
Descending...
Descending p=7 consumed=2 freedom=4
Trying offset 0
Bit cleared in range 7..10, continuing
Trying offset 1
Bit cleared in range 8..11, continuing
Trying offset 2
Bit cleared in range 9..12, continuing
Trying offset 3
Descending...
Descending p=14 consumed=3 freedom=1
no blocks remain
1 at position 11
Trying offset 4
Bit cleared in range 11..14, continuing
1 at position 6
Trying offset 3
Descending...
Descending p=8 consumed=2 freedom=3
Trying offset 0
Bit cleared in range 8..11, continuing
Trying offset 1
Bit cleared in range 9..12, continuing
Trying offset 2
Descending...
Cache hit
1 at position 11
Trying offset 3
Bit cleared in range 11..14, continuing
2 at position 7
Trying offset 4
Bit cleared in range 7..8, continuing
Trying offset 5
Bit cleared in range 8..9, continuing
Trying offset 6
Bit cleared in range 9..10, continuing
2 at position 2
Trying offset 2
Descending...
Descending p=4 consumed=1 freedom=5
Trying offset 0
Bit cleared in range 4..5, continuing
Trying offset 1
Descending...
Cache hit
1 at position 6
Trying offset 2
Descending...
Cache hit
2 at position 7
Trying offset 3
Bit cleared in range 7..8, continuing
Trying offset 4
Bit cleared in range 8..9, continuing
Trying offset 5
Bit cleared in range 9..10, continuing
4 at position 3
Trying offset 3
Bit cleared in range 3..4, continuing
Trying offset 4
Bit cleared in range 4..5, continuing
Trying offset 5
Descending...
Descending p=7 consumed=1 freedom=2
Trying offset 0
Bit cleared in range 7..8, continuing
Trying offset 1
Bit cleared in range 8..9, continuing
Trying offset 2
Bit cleared in range 9..10, continuing
4 at position 6
Trying offset 6
Descending...
Descending p=8 consumed=1 freedom=1
Trying offset 0
Bit cleared in range 8..9, continuing
Trying offset 1
Bit cleared in range 9..10, continuing
4 at position 7
Trying offset 7
Bit cleared in range 7..8, continuing
Counting solutions for RowView { bits: [None, Some(true), None, Some(true), None, Some(true), None, Some(true), None, Some(true), None, Some(true), None, Some(true), None], pattern: [1, 3, 1, 6], freedom: 1 }
Descending p=0 consumed=0 freedom=1
Trying offset 0
found blocker at 1
Trying offset 1
Descending...
Descending p=3 consumed=1 freedom=0
Trying offset 0
Descending...
Descending p=7 consumed=2 freedom=0
Trying offset 0
Descending...
Descending p=9 consumed=3 freedom=0
Trying offset 0
Descending...
Descending p=16 consumed=4 freedom=0
no blocks remain
1 at position 10
1 at position 8
1 at position 4
1 at position 2
Counting solutions for RowView { bits: [None, None, None, None, Some(false), Some(true), Some(false), Some(false), Some(false), Some(true), Some(false), Some(false), Some(false)], pattern: [4, 1, 1], freedom: 5 }
Descending p=0 consumed=0 freedom=5
Trying offset 0
Descending...
Descending p=5 consumed=1 freedom=5
Trying offset 0
Descending...
Descending p=7 consumed=2 freedom=5
Trying offset 0
Bit cleared in range 7..8, continuing
Trying offset 1
Bit cleared in range 8..9, continuing
Trying offset 2
Descending...
Descending p=11 consumed=3 freedom=3
no blocks remain
1 at position 10
Trying offset 3
Bit 9 set, giving up
1 at position 6
Trying offset 1
Bit 5 set, giving up
1 at position 1
Trying offset 1
Bit cleared in range 1..5, continuing
Trying offset 2
Bit cleared in range 2..6, continuing
Trying offset 3
Bit cleared in range 3..7, continuing
Trying offset 4
Bit cleared in range 4..8, continuing
Trying offset 5
Bit cleared in range 5..9, continuing
Counting solutions for RowView { bits: [None, None, None, None, Some(false), Some(true), Some(true), Some(true), Some(true), Some(true), Some(true), Some(false), Some(false), Some(true), Some(true), Some(true), Some(true), Some(true), Some(false)], pattern: [1, 6, 5], freedom: 5 }
Descending p=0 consumed=0 freedom=5
Trying offset 0
Descending...
Descending p=2 consumed=1 freedom=5
Trying offset 0
Bit cleared in range 2..8, continuing
Trying offset 1
Bit cleared in range 3..9, continuing
Trying offset 2
Bit cleared in range 4..10, continuing
Trying offset 3
Descending...
Descending p=12 consumed=2 freedom=2
Trying offset 0
Bit cleared in range 12..17, continuing
Trying offset 1
Descending...
Descending p=19 consumed=3 freedom=1
no blocks remain
1 at position 14
Trying offset 2
Bit 13 set, giving up
1 at position 6
Trying offset 4
Bit 5 set, giving up
1 at position 1
Trying offset 1
Descending...
Descending p=3 consumed=1 freedom=4
Trying offset 0
Bit cleared in range 3..9, continuing
Trying offset 1
Bit cleared in range 4..10, continuing
Trying offset 2
Descending...
Cache hit
1 at position 6
Trying offset 3
Bit 5 set, giving up
2 at position 2
Trying offset 2
Descending...
Descending p=4 consumed=1 freedom=3
Trying offset 0
Bit cleared in range 4..10, continuing
Trying offset 1
Descending...
Cache hit
1 at position 6
Trying offset 2
Bit 5 set, giving up
3 at position 3
Trying offset 3
Descending...
Descending p=5 consumed=1 freedom=2
Trying offset 0
Descending...
Cache hit
1 at position 6
Trying offset 1
Bit 5 set, giving up
4 at position 4
Trying offset 4
Bit cleared in range 4..5, continuing
Trying offset 5
found blocker at 6
Counting solutions for RowView { bits: [None, Some(true), Some(true), Some(true), None, None, None, None, None, None, None, None], pattern: [3, 2, 1], freedom: 4 }
Descending p=0 consumed=0 freedom=4
Trying offset 0
found blocker at 3
Trying offset 1
Descending...
Descending p=5 consumed=1 freedom=3
Trying offset 0
Descending...
Descending p=8 consumed=2 freedom=3
Trying offset 0
Descending...
Descending p=10 consumed=3 freedom=3
no blocks remain
1 at position 9
Trying offset 1
Descending...
Descending p=11 consumed=3 freedom=2
no blocks remain
2 at position 10
Trying offset 2
Descending...
Descending p=12 consumed=3 freedom=1
no blocks remain
3 at position 11
Trying offset 3
Descending...
Descending p=13 consumed=3 freedom=0
no blocks remain
4 at position 12
4 at position 6
Trying offset 1
Descending...
Descending p=9 consumed=2 freedom=2
Trying offset 0
Descending...
Cache hit
1 at position 10
Trying offset 1
Descending...
Cache hit
2 at position 11
Trying offset 2
Descending...
Cache hit
3 at position 12
7 at position 7
Trying offset 2
Descending...
Descending p=10 consumed=2 freedom=1
Trying offset 0
Descending...
Cache hit
1 at position 11
Trying offset 1
Descending...
Cache hit
2 at position 12
9 at position 8
Trying offset 3
Descending...
Descending p=11 consumed=2 freedom=0
Trying offset 0
Descending...
Cache hit
1 at position 12
10 at position 9
10 at position 2
Trying offset 2
Bit 1 set, giving up
Part 1: Total: 21

242
day12/src/main.rs Normal file
View File

@ -0,0 +1,242 @@
use std::{str::FromStr, ops::RangeFrom, io::stdin, collections::HashMap, fmt::Display};
use anyhow::{Result, Error, anyhow};
#[derive(Debug,Clone,PartialEq,Eq)]
struct Row {
bits: Vec<Option<bool>>,
pattern: Vec<usize>,
freedom: usize,
}
#[derive(Debug)]
struct RowView<'r> {
bits: &'r [Option<bool>],
pattern: &'r [usize],
freedom: usize,
}
type Cache = HashMap<(usize,usize), usize>;
impl Row {
fn view(&self) -> RowView<'_> {
RowView { bits: &self.bits, pattern: &self.pattern, freedom: self.freedom }
}
fn enumerate<F>(&self, f: &mut F)
where F: FnMut(&[bool])
{
let mut buf = vec![false; self.bits.len()];
self.view().enumerate(&mut buf, 0.., f);
}
fn optimize(&mut self) {
let mut offset = 0;
for &block in &self.pattern {
if block > self.freedom {
for cell in &mut self.bits[offset + self.freedom .. offset + block] {
*cell = Some(true)
}
}
offset += block + 1;
}
}
fn multiply(self, n: usize) -> Self {
let mut bits = self.bits;
bits.push(None);
bits = bits.repeat(n);
bits.pop();
let pattern = self.pattern.repeat(n);
Row { bits, pattern, freedom: self.freedom * n}
}
fn count(&self) -> usize {
self.view().count()
}
fn exhaustive_count(&self) -> usize {
self.view().exhaustive_count()
}
}
impl FromStr for Row {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (bits, nums) = s.split_once(' ')
.ok_or_else(|| anyhow!("Bad line {s}"))?;
let bits = bits.chars()
.map(|c| match c {
'.' => Ok(Some(false)),
'#' => Ok(Some(true)),
'?' => Ok(None),
oth => Err(anyhow!("Invalid character {oth}")),
})
.collect::<Result<Vec<_>>>()?;
let pattern = nums.split(',')
.map(str::parse)
.collect::<Result<Vec<_>, _>>()?;
let size = (pattern.iter().sum::<usize>() + pattern.len())
.saturating_sub(1);
let freedom = bits.len()
.checked_sub(size).ok_or_else(|| anyhow!("Unsolvable row {s}"))?;
Ok(Self { bits, pattern, freedom })
}
}
impl Display for Row {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for bit in &self.bits {
write!(f, "{}", match bit {
None => '?',
Some(false) => '.',
Some(true) => '#',
})?;
}
write!(f, "{:?}", &self.pattern)?;
Ok(())
}
}
impl <'r> RowView<'r> {
fn exhaustive_count(&self) -> usize {
let mut c = 0;
let mut buf = vec![false; self.bits.len()];
self.enumerate(&mut buf, 0.., &mut |_| c += 1);
c
}
fn count(&self) -> usize {
self.dfs(0, 0, self.freedom, &mut HashMap::new())
}
fn dfs(&self, position: usize, consumed: usize, freedom: usize, cache: &mut Cache) -> usize {
//eprintln!("DFS enter position={position} consumed={consumed} freedom={freedom}");
fn cached_dfs(this: &RowView, position: usize, consumed: usize, freedom: usize, cache: &mut Cache) -> usize {
if let Some(&solutions) = cache.get(&(consumed, position)) {
//eprintln!("Cache hit for position={position} consumed={consumed} freedom={freedom} = {solutions}");
return solutions
}
let solutions = this.dfs(position, consumed, freedom, cache);
cache.insert((consumed, position), solutions);
solutions
}
if consumed == self.pattern.len() {
if position <= self.bits.len() && self.bits[position..].iter().any(|b| b == &Some(true)) {
//eprintln!("Terminal conflict");
0
} else {
//eprintln!("Valid solution");
1
}
} else {
let current = self.pattern[consumed];
let mut solutions = 0;
for offset in 0..=freedom {
// Uncovered bit in prefix space, no point in continuing
if offset > 0 && self.bits[position+offset-1] == Some(true) { break }
// Block collides with empty cell, try next offset
if self.bits[position+offset..][..current].iter().any(|b| b == &Some(false)) { continue }
// Block is not last and cell following block is not empty
if position+offset+current < self.bits.len() && self.bits[position+offset+current] == Some(true) { continue }
solutions += cached_dfs(&self, position+offset+current+1, consumed+1, freedom - offset, cache);
}
//eprintln!("DFS leave position={position} consumed={consumed} freedom={freedom} -> {solutions}");
solutions
}
}
fn enumerate<F>(&self, buf: &mut [bool], window: RangeFrom<usize>, f: &mut F)
where F: FnMut(&[bool])
{
if let Some((&h, rest)) = self.pattern.split_first() {
for offset in 0..=self.freedom {
if self.bits[..offset].iter().any(|b| b == &Some(true)) { continue }
for b in &mut buf[window.clone()][..offset] {
*b = false;
}
if self.bits[offset..][..h].iter().any(|b| b == &Some(false)) { continue }
for b in &mut buf[window.clone()][offset..][..h] {
*b = true;
}
if rest.len() == 0 {
RowView { pattern: rest, bits: &self.bits[offset+h..], freedom: self.freedom - offset}
.enumerate(buf, window.start + offset + h .., f);
} else {
if self.bits[offset + h] == Some(true) { continue; }
buf[window.clone()][offset + h] = false;
RowView { pattern: rest, bits: &self.bits[offset+h+1..], freedom: self.freedom - offset }
.enumerate(buf, window.start + offset + h + 1 .., f);
}
}
} else {
for b in buf[window.clone()].iter_mut() { *b = false }
if self.compatible(&buf[window]) {
f(&buf)
}
}
}
}
/*
fn pretty(row: &[bool]) -> String {
row.iter().map(|&f| if f {'#'} else {'.'}).collect()
}
*/
#[test]
fn test_optimization() {
let row: Row = "#??##????#??? 8,1,1".parse().unwrap();
let row2 = row.clone();
assert_eq!(row.count(), row2.count());
}
fn main() -> Result<()> {
let rows: Vec<Row> = stdin().lines().map(|l| {
l.unwrap().parse().unwrap()
}).collect();
{
let mut rows = rows.clone();
for row in &mut rows {
row.optimize();
}
let total: usize = rows.iter().map(Row::count).sum();
println!("Part 1: Total: {total}");
}
let mut rows: Vec<Row> = rows.into_iter().map(|r| r.multiply(5)).collect();
for r in &mut rows { r.optimize() }
let total: usize = rows.iter().map(Row::count).sum();
println!("Part 2: Total: {total}");
Ok(())
}

6
day12/test Normal file
View File

@ -0,0 +1,6 @@
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1