Skip to content

Commit

Permalink
Day 12 part 2 done
Browse files Browse the repository at this point in the history
  • Loading branch information
javorszky committed Dec 13, 2024
1 parent f334ed2 commit b6e733b
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 15 deletions.
63 changes: 63 additions & 0 deletions day12/part2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
--- Part Two ---

Fortunately, the Elves are trying to order so much fence that they qualify for a **bulk discount**!

Under the bulk discount, instead of using the perimeter to calculate the price, you need to use the **number of sides** each region has. Each straight section of fence counts as a side, regardless of how long it is.

Consider this example again:

```
AAAA
BBCD
BBCC
EEEC
```

The region containing type `A` plants has `4` sides, as does each of the regions containing plants of type `B`, `D`, and `E`. However, the more complex region containing the plants of type `C` has `8` sides!

Using the new method of calculating the per-region price by multiplying the region's area by its number of sides, regions `A` through `E` have prices `16`, `16`, `32`, `4`, and `12`, respectively, for a total price of *`80`*.

The second example above (full of type `X` and `O` plants) would have a total price of `436`.

Here's a map that includes an `E`-shaped region full of type `E` plants:

```
EEEEE
EXXXX
EEEEE
EXXXX
EEEEE
```

The `E`-shaped region has an area of `17` and `12` sides for a price of `204`. Including the two regions full of type `X` plants, this map has a total price of `236`.

This map has a total price of `368`:

```
AAAAAA
AAABBA
AAABBA
ABBAAA
ABBAAA
AAAAAA
```

It includes two regions full of type `B` plants (each with `4` sides) and a single region full of type `A` plants (with `4` sides on the outside and `8` more sides on the inside, a total of `12` sides). Be especially careful when counting the fence around regions like the one full of type `A` plants; in particular, each section of fence has an in-side and an out-side, so the fence does not connect across the middle of the region (where the two `B` regions touch diagonally). (The Elves would have used the Möbius Fencing Company instead, but their contract terms were too one-sided.)

The larger example from before now has the following updated prices:

* A region of `R` plants with price `12 * 10 = 120`.
* A region of `I` plants with price `4 * 4 = 16`.
* A region of `C` plants with price `14 * 22 = 308`.
* A region of `F` plants with price `10 * 12 = 120`.
* A region of `V` plants with price `13 * 10 = 130`.
* A region of `J` plants with price `11 * 12 = 132`.
* A region of `C` plants with price `1 * 4 = 4`.
* A region of `E` plants with price `13 * 8 = 104`.
* A region of `I` plants with price `14 * 16 = 224`.
* A region of `M` plants with price `5 * 6 = 30`.
* A region of `S` plants with price `3 * 6 = 18`.

Adding these together produces its new total price of **1206**.

**What is the new total price of fencing all regions on your map?**
9 changes: 9 additions & 0 deletions day12/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
mod part1;
mod part2;

pub fn solve_part1_example() -> u32 {
part1::solve(include_str!("../example.txt"))
}

pub fn solve_part1() -> u32 {
part1::solve(include_str!("../input.txt"))
}

pub fn solve_part2_example() -> u32 {
part2::solve(include_str!("../example.txt"))
}

pub fn solve_part2() -> u32 {
part2::solve(include_str!("../input.txt"))
}
29 changes: 14 additions & 15 deletions day12/src/part1.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use std::collections::{HashMap, HashSet};

#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
struct Coordinate {
horizontal: i32,
vertical: i32,
pub(crate) struct Coordinate {
pub(crate) horizontal: i32,
pub(crate) vertical: i32,
}

impl Coordinate {
fn new(horizontal: i32, vertical: i32) -> Self {
pub(crate) fn new(horizontal: i32, vertical: i32) -> Self {
Self { horizontal, vertical }
}

fn neighbours(&self) -> Vec<Self> {
pub(crate) fn neighbours(&self) -> Vec<Self> {
vec![
Self::new(self.horizontal-1, self.vertical),
Self::new(self.horizontal+1, self.vertical),
Expand All @@ -21,25 +21,22 @@ impl Coordinate {
}
}

struct Map {
map: HashMap<Coordinate, String>
}

#[derive(Debug)]
struct Region {
plots: HashSet<Coordinate>
pub(crate) struct Region {
pub(crate) plots: HashSet<Coordinate>
}

impl Region {
fn new(plots: HashSet<Coordinate>) -> Self {
pub(crate) fn new(plots: HashSet<Coordinate>) -> Self {
Self { plots }
}

fn area(&self) -> i32 {
pub(crate) fn area(&self) -> i32 {
self.plots.len() as i32
}

fn perimeter(&self) -> i32 {
pub(crate) fn perimeter(&self) -> i32 {
let mut visited: HashSet<Coordinate> = HashSet::new();

let mut perimeter = 0;
Expand All @@ -49,6 +46,8 @@ impl Region {
continue;
}

visited.insert(*c);

for n in c.neighbours() {
if !self.plots.contains(&n) {
perimeter += 1;
Expand Down Expand Up @@ -78,7 +77,7 @@ pub(crate) fn solve(input: &str) -> u32 {
for coord in map.keys() {
let region = flood_fill(&map, *coord, map.get(coord).unwrap(), &mut visited);

regions.push(Region { plots: region });
regions.push(Region::new(region));
}

let regions: Vec<&Region> = regions.iter().filter(|&r| !r.plots.is_empty()).collect();
Expand All @@ -91,7 +90,7 @@ pub(crate) fn solve(input: &str) -> u32 {
price as u32
}

fn flood_fill(map: &HashMap<Coordinate, String>, origin: Coordinate, plot_type: &String, visited: &mut HashSet<Coordinate>) -> HashSet<Coordinate> {
pub(crate) fn flood_fill(map: &HashMap<Coordinate, String>, origin: Coordinate, plot_type: &String, visited: &mut HashSet<Coordinate>) -> HashSet<Coordinate> {
let unknown: &String = &"1".to_string();
// println!("flood fill on coordinate {:?}", origin);
let mut local_set = HashSet::new();
Expand Down
156 changes: 156 additions & 0 deletions day12/src/part2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use std::collections::{HashMap, HashSet};
use crate::part1::{Coordinate, Region};

#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
enum Side {
Up,
Down,
Left,
Right,
}

impl Coordinate {
fn neighbour_on_side(&self, side: Side) -> Self {
match side {
Side::Up => {Coordinate::new(self.horizontal, self.vertical-1)}
Side::Down => {Coordinate::new(self.horizontal, self.vertical+1)}
Side::Left => {Coordinate::new(self.horizontal-1, self.vertical)}
Side::Right => {Coordinate::new(self.horizontal+1, self.vertical)}
}
}
}

impl Region {
fn sides(&self) -> u32 {
let mut side_set: HashSet<(Coordinate, Side)> = HashSet::new();

for plot in &self.plots {
for s in [Side::Up, Side::Down, Side::Left, Side::Right] {
if !self.plots.contains(&plot.neighbour_on_side(s)) {
side_set.insert((*plot, s));
}
}
}

let mut grouped_sides: HashMap<(Coordinate,Side), i32> = HashMap::new();
for side_entry in side_set.iter() {
let mut current = side_entry.0;

match side_entry.1 {
Side::Up|Side::Down => {
let mut to_the_left = current.neighbour_on_side(Side::Left);
// find leftmost
while side_set.contains(&(to_the_left, side_entry.1)) {
current = to_the_left;
to_the_left = current.neighbour_on_side(Side::Left);
}

// current is the leftmost coordinate for the side. side_entry.1 is either
// Up or Down.
let key = (current, side_entry.1);

// if we have found one of the other wall pieces of the same wall, we should get
// to the same key because everything is normalised to the left and to bottom
if grouped_sides.contains_key(&key) {
continue;
}

let mut side_length = 1;
let mut to_the_right = current.neighbour_on_side(Side::Right);

// once we moved all the way to the left, check that if the space to the right
// also has the same border.
while side_set.contains(&(to_the_right, side_entry.1)) {
current = to_the_right;
to_the_right = current.neighbour_on_side(Side::Right);
side_length += 1;
}

grouped_sides.insert(key, side_length);
}
Side::Left|Side::Right => {
// find the bottom most
let mut below = current.neighbour_on_side(Side::Down);
while side_set.contains(&(below, side_entry.1)) {
current = below;
below = current.neighbour_on_side(Side::Down);
}

// current is the leftmost coordinate for the side. side_entry.1 is either
// Left or Right.
let key = (current, side_entry.1);

// if we have found one of the other wall pieces of the same wall, we should get
// to the same key because everything is normalised to the left and to bottom
if grouped_sides.contains_key(&key) {
continue;
}

let mut side_length = 1;
let mut above = current.neighbour_on_side(Side::Up);
while side_set.contains(&(above, side_entry.1)) {
current = above;
above = current.neighbour_on_side(Side::Up);
side_length += 1;
}

grouped_sides.insert(key, side_length);
}
}
}

grouped_sides.len() as u32
}
}

pub(crate) fn solve(input: &str) -> u32 {
let lines = input.trim().lines();
let mut map: HashMap<Coordinate, String> = HashMap::new();

for (height, line) in lines.enumerate() {
for (width, c) in line.chars().enumerate() {
map.insert(Coordinate::new(width as i32, height as i32), c.to_string());
}
}

let mut visited: HashSet<Coordinate> = HashSet::new();
let mut regions : Vec<Region> = Vec::new();

for coord in map.keys() {
let region = crate::part1::flood_fill(&map, *coord, map.get(coord).unwrap(), &mut visited);

regions.push(Region::new(region));
}

let regions: Vec<&Region> = regions.iter().filter(|&r| !r.plots.is_empty()).collect();

let mut sum = 0;

for region in regions {
sum += region.sides() * region.area() as u32
}
//
// if let Some(&ref region) = regions.into_iter().next() {
// if let Some(plot) = region.plots.clone().into_iter().next() {
// println!("looking at region for {:?}:\ncoords:\n{:?}", plot, region.plots);
//
// }
// let sides = region.sides();
//
// println!("these are the sides: {:?}", sides);
// }
// //c
sum
}


#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_group_sides() {
let input = "XX\nXY\n";

}
}
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,7 @@ fn main() {
// Day 12
println!("\nDay 12 part 1 example: {}", day12::solve_part1_example());
println!("Day 12 part 1: Total price for perimeter * area for plots is {}", day12::solve_part1());

println!("Day 12 part 2 example: {}", day12::solve_part2_example());
println!("Day 12 part 2: The new price using bulk pricing is {}", day12::solve_part2());
}

0 comments on commit b6e733b

Please sign in to comment.