This commit is contained in:
Acvaxoort
2023-12-18 17:14:10 +01:00
parent 865f4cf564
commit 049a2181ad
4 changed files with 908 additions and 0 deletions

181
day18/src/main.rs Normal file
View File

@@ -0,0 +1,181 @@
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::fs::read_to_string;
use std::time::Instant;
#[derive(Copy, Clone, PartialEq)]
#[repr(u8)]
enum Direction {
Left = 0,
Right = 1,
Up = 2,
Down = 3,
}
const DIRECTION_OFFSET: [(i32, i32); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
fn get_area(horizontal_edges: &BTreeMap<i32, Vec<(i32, i32)>>) -> u64 {
struct Segment {
x0: i32,
x1: i32,
// coverage refers to coverage in current row if the segment shrinks in this row,
// if the segment shrinks in this row, coverage remains its previous bigger version
coverage: i32,
to_delete: bool, // whether it needs to be deleted after this row
}
// Segments present in currently processed row
let mut current_segments: Vec<Segment> = vec![];
// Current vertical position, start doesn't matter but must be very low
let mut current_y = i32::MIN;
// Result
let mut area: u64 = 0;
// Going through the edge map
for (&y, edges) in horizontal_edges.iter() {
// Catch up from the previous vertical position
for segment in current_segments.iter() {
area += (segment.coverage as i64 * (y - current_y - 1) as i64) as u64;
}
current_y = y;
// Going through all the edges
for &edge in edges {
let mut found_intersection = false;
// Segment to insert for splitting
let mut segment_to_insert: Option<Segment> = None;
// Checking if the processed edge intersects with some segment and acting accordingly
for segment in current_segments.iter_mut() {
if segment.x1 == edge.0 {
segment.x1 = edge.1;
segment.coverage += edge.1 - edge.0;
found_intersection = true;
} else if segment.x0 == edge.1 {
segment.x0 = edge.0;
segment.coverage += edge.1 - edge.0;
found_intersection = true;
} else if segment.x0 == edge.0 && segment.x1 == edge.1 {
// Here the edge is exactly the same like one segment, so it's going to end
segment.to_delete = true;
found_intersection = true;
} else if segment.x0 == edge.0 {
segment.x0 = edge.1;
found_intersection = true;
} else if segment.x1 == edge.1 {
segment.x1 = edge.0;
found_intersection = true;
} else if segment.x0 < edge.0 && segment.x1 > edge.1 {
// Here the edge is fully inside one of current segments, need to split
segment_to_insert = Some(Segment {
x0: edge.1,
x1: segment.x1,
coverage: 0, // Don't care how coverage is split, it will be recalculated
to_delete: false,
});
segment.x1 = edge.0;
found_intersection = true;
}
if found_intersection {
break;
}
}
if !found_intersection {
// No intersection, add new segment
current_segments.push(Segment {
x0: edge.0,
x1: edge.1,
coverage: edge.1 - edge.0 + 1,
to_delete: false,
});
} else if let Some(segment) = segment_to_insert {
// Do the splitting
current_segments.push(segment);
}
}
// Sort by lower bound and then merge segments which have overlapping bounds
current_segments.sort_by(|a, b| a.x0.cmp(&b.x0));
let mut i = 1;
while i < current_segments.len() {
if current_segments[i - 1].x1 == current_segments[i].x0 {
current_segments[i - 1].x1 = current_segments[i].x1;
current_segments[i - 1].coverage += current_segments[i].coverage - 1;
current_segments.remove(i);
} else {
i += 1;
}
}
// Add up current row coverage and update coverages for catching up
for segment in current_segments.iter_mut() {
area += segment.coverage as u64;
segment.coverage = segment.x1 - segment.x0 + 1;
}
// Remove segments which were removed this iteration
current_segments.retain(|segment| !segment.to_delete);
}
area
}
fn main() {
let time_start = Instant::now();
let input_str = read_to_string("input.txt").unwrap();
let time_start_no_io = Instant::now();
// edge maps, keys - y (vertical) position, values: list of horizontal edges (x1, x2)
let mut horizontal_edges1: BTreeMap<i32, Vec<(i32, i32)>> = BTreeMap::new();
let mut horizontal_edges2: BTreeMap<i32, Vec<(i32, i32)>> = BTreeMap::new();
// Current position for parsing
let mut current_pos1 = (0, 0);
let mut current_pos2 = (0, 0);
// Takes current_pos, direction and distance of the next step
// Inserts horizontal edges into the map, ignores vertical, modifies current_pos
let process_edge = |horizontal_edges: &mut BTreeMap<i32, Vec<(i32, i32)>>,
current_pos: &mut (i32, i32), dir: Direction, dist: i32| {
let mut offset = DIRECTION_OFFSET[dir as usize];
offset.0 *= dist;
offset.1 *= dist;
let new_pos = (current_pos.0 + offset.0, current_pos.1 + offset.1);
if dir == Direction::Left || dir == Direction::Right {
let vec;
match horizontal_edges.entry(new_pos.1) {
Entry::Vacant(vacant_entry) => {
vec = vacant_entry.insert(vec![]);
}
Entry::Occupied(occupied_entry) => {
vec = occupied_entry.into_mut();
}
}
if dir == Direction::Left {
vec.push((new_pos.0, current_pos.0));
} else {
vec.push((current_pos.0, new_pos.0));
}
}
*current_pos = new_pos;
};
// Populate both edge maps
for line in input_str.lines() {
let mut split_whitespace = line.split_whitespace();
let dir1 = match split_whitespace.next().unwrap().as_bytes()[0] {
b'L' => Direction::Left,
b'R' => Direction::Right,
b'U' => Direction::Up,
_ => Direction::Down
};
let dist1 = split_whitespace.next().unwrap().parse::<u8>().unwrap();
let code = &split_whitespace.next().unwrap()[2..8];
let dir2 = match code.as_bytes()[5] {
b'0' => Direction::Right,
b'1' => Direction::Down,
b'2' => Direction::Left,
_ => Direction::Up
};
let dist2 = i32::from_str_radix(&code[0..5], 16).unwrap();
process_edge(&mut horizontal_edges1, &mut current_pos1, dir1, dist1 as i32);
process_edge(&mut horizontal_edges2, &mut current_pos2, dir2, dist2);
}
// Compute areas based on the edge maps
let area1 = get_area(&horizontal_edges1);
let area2 = get_area(&horizontal_edges2);
let elapsed = time_start.elapsed().as_micros();
let elapsed_no_io = time_start_no_io.elapsed().as_micros();
println!("Time: {}us", elapsed);
println!("Time without file i/o: {}us", elapsed_no_io);
println!("Area1: {}", area1);
println!("Area2: {}", area2);
}