Perfectly Spherical Houses in a Vacuum
AoC 2015 with Rust - Day 3
Jul 17 23 • 9 min read
Link to problem
Source of the final solution
Other posts in the series
Part 1
Santa is delivering gifts in a two-dimensional grid of houses. He gets instructions over the radio by arrow characters (>
, <
, v
, ^
) and moves according to these. He delivers a gift in each move, but sometimes he gets wrong instructions and revisits some of the locations he has already visited.
For the first part, I want to solve how many houses get at least 1 present. Starting location will also count as one present delivered.
For example,
>
delivers presents to 2 houses: one at the starting location, and one to the east.^>v<
delivers presents to 4 houses in a square, including twice to the house at his starting/ending location.
If there is a data type that can hold non-duplicate items in Rust, like some sort of set, I can use it. I can iterate over the instructions, call a move()
function that takes the direction character and either returns the next coordinate which can then be added to hash set. These coordinates can maybe be some sort of struct. I will also copy file reading stuff from the previous challenge.
struct Coordinate {
x: i32,
y: i32,
}
fn main() {
let starting_coordinates = Coordinate { x: 0, y: 0 };
let new_coord = move_santa('v', starting_coordinates);
println!("{:?} {:?}", new_coord.x, new_coord.y)
}
fn move_santa(direction: char, current_coordinate: Coordinate) -> Coordinate {
match direction {
'^' => Coordinate {
x: current_coordinate.x,
y: current_coordinate.y + 1,
},
'>' => Coordinate {
x: current_coordinate.x + 1,
y: current_coordinate.y,
},
'v' => Coordinate {
x: current_coordinate.x,
y: current_coordinate.y - 1,
},
'<' => Coordinate {
x: current_coordinate.x - 1,
y: current_coordinate.y,
},
_ => unreachable!(),
}
}
This succesfully moved the character one line down, giving us 0 -1
for x y
. I can move on to hash sets:
let starting_coordinates = Coordinate { x: 0, y: 0 };
let mut visited_coordinates: HashSet<Coordinate> = HashSet::from([starting_coordinates]);
let mut current_coordinates = starting_coordinates;
for direction in directions {
current_coordinates = move_santa(direction, current_coordinates);
visited_coordinates.insert(current_coordinates);
}
But insert()
function of hash set gives me the error the method `insert` exists for struct `HashSet<Coordinate>`, but its trait bounds were not satisfied
. Naturally, it needs some way to compare Coordinate
s so Rust can decide which ones are duplicates.
#[derive(Eq, Clone, Copy)]
struct Coordinate {
x: i32,
y: i32,
}
impl PartialEq for Coordinate {
fn eq(&self, other: &Coordinate) -> bool {
self.x == other.x && self.y == other.y
}
}
impl std::hash::Hash for Coordinate {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
state.write_i32(self.x);
state.finish();
}
}
I had to “extend” the functionality of Coordinate
by adding hash and PartialEq
functions and add three traits, Eq
, Clone
, and Copy
to make it work.
With everything put together, first part of the puzzle is now solved.
use std::{collections::HashSet, fs};
#[derive(Eq, Clone, Copy)]
struct Coordinate {
x: i32,
y: i32,
}
impl PartialEq for Coordinate {
fn eq(&self, other: &Coordinate) -> bool {
self.x == other.x && self.y == other.y
}
}
impl std::hash::Hash for Coordinate {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
state.write_i32(self.x);
state.finish();
}
}
fn main() {
let filename = "data.txt";
let contents = fs::read_to_string(filename).expect("Something went wrong");
let directions = contents.chars();
let starting_coordinates = Coordinate { x: 0, y: 0 };
let mut visited_coordinates: HashSet<Coordinate> = HashSet::from([starting_coordinates]);
let mut current_coordinates = starting_coordinates;
for direction in directions {
current_coordinates = move_santa(direction, current_coordinates);
visited_coordinates.insert(current_coordinates);
}
println!("{:?}", visited_coordinates.into_iter().count())
}
fn move_santa(direction: char, current_coordinate: Coordinate) -> Coordinate {
match direction {
'^' => Coordinate {
x: current_coordinate.x,
y: current_coordinate.y + 1,
},
'>' => Coordinate {
x: current_coordinate.x + 1,
y: current_coordinate.y,
},
'v' => Coordinate {
x: current_coordinate.x,
y: current_coordinate.y - 1,
},
'<' => Coordinate {
x: current_coordinate.x - 1,
y: current_coordinate.y,
},
_ => unreachable!(),
}
}
Part 2
Santa has a robot assistant now and they follow directions in turns. How many houses do they visit?
This should be easier to implement into our current code. I just need to track one more character and move them in turns. A simple modulo operator should be enough for checking turns.
use std::{collections::HashSet, fs};
#[derive(Eq, Clone, Copy)]
struct Coordinate {
x: i32,
y: i32,
}
impl PartialEq for Coordinate {
fn eq(&self, other: &Coordinate) -> bool {
self.x == other.x && self.y == other.y
}
}
impl std::hash::Hash for Coordinate {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
state.write_i32(self.x);
state.finish();
}
}
fn main() {
let filename = "data.txt";
let contents = fs::read_to_string(filename).expect("Something went wrong");
let directions = contents.chars();
let starting_coordinates = Coordinate { x: 0, y: 0 };
let mut visited_coordinates: HashSet<Coordinate> = HashSet::from([starting_coordinates]);
let mut santa_coordinate = starting_coordinates;
let mut robot_coordinate = starting_coordinates;
for (idx, direction) in directions.enumerate() {
if idx % 2 == 0 {
santa_coordinate = move_character(direction, santa_coordinate);
visited_coordinates.insert(santa_coordinate);
} else {
robot_coordinate = move_character(direction, robot_coordinate);
visited_coordinates.insert(robot_coordinate);
}
}
println!("{:?}", visited_coordinates.into_iter().count())
}
fn move_character(direction: char, current_coordinate: Coordinate) -> Coordinate {
match direction {
'^' => Coordinate {
x: current_coordinate.x,
y: current_coordinate.y + 1,
},
'>' => Coordinate {
x: current_coordinate.x + 1,
y: current_coordinate.y,
},
'v' => Coordinate {
x: current_coordinate.x,
y: current_coordinate.y - 1,
},
'<' => Coordinate {
x: current_coordinate.x - 1,
y: current_coordinate.y,
},
_ => unreachable!(),
}
}
This works as expected and the second part of the puzzle is now solved, but I am leaving this behind puzzle a bit unsatisfied because I feel like I partly cheated my way through it, because I can’t say I completely get how traits are working here. I kind of get it, but I also don’t… I should try and write a post about it.