day 18
This commit is contained in:
181
day18/src/main.rs
Normal file
181
day18/src/main.rs
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user