advent-of-code/2017/day/3/src/main.rs

149 lines
3.6 KiB
Rust
Raw Normal View History

2017-12-06 12:54:15 +00:00
use std::f64::consts::PI;
// There might me a more elegant solution to this problem that I've missed (it was completed
// without any Internet access). At least it runs in constant time and space.
fn main() {
println!("{}", manhattan_distance(289326));
}
// Determine the dimensions of the rectangle in the spiral that the given number must be on. Will
// always be an odd number as 1 is in the middle.
fn spiral_diameter(index: i32) -> i32 {
let sqrt = (index as f64).sqrt().ceil() as i32;
if sqrt % 2 == 0 { sqrt + 1 } else { sqrt }
}
// Returns angle in radians of number on spiral
fn angle(index: i32) -> f64 {
if index == 1 { return 0. }
let diameter = spiral_diameter(index);
let area_of_inner_rectangle = (diameter - 2).pow(2);
let squares_around_rectangle = diameter.pow(2) - area_of_inner_rectangle;
let squares_per_side = squares_around_rectangle / 4;
// Divide the space around the rectangle into squares_around_rectangle segments
let angle_per_segment = 2. * PI / squares_around_rectangle as f64;
// Determine how far around the rectangle this index is.
// Offset adjusts for the last number being at the bottom right corner of any
// given rectangle.
let offset = squares_per_side / 2;
let angle = (index - area_of_inner_rectangle - offset) as f64 * angle_per_segment;
angle
}
fn manhattan_distance(index: i32) -> i32 {
if index == 1 { return 0 }
let angle = angle(index);
let radius = (spiral_diameter(index) / 2) as f64;
// Calculate the x and y coordinates, scale by √2 so that sin/cos at corners is 1
let horizontal_distance = 1f64.min((2f64.sqrt() * angle.cos()).abs());
let vertical_distance = 1f64.min((2f64.sqrt() * angle.sin()).abs());
let distance = radius * (horizontal_distance + vertical_distance);
distance.round() as i32
}
#[test]
fn test_angle_should_be_zero() {
assert_eq!(angle(1), 0.);
assert_eq!(angle(2), 0.);
assert_eq!(angle(11), 0.);
assert_eq!(angle(28), 0.);
}
#[test]
fn test_angle_should_be_half_pi() {
use std::f64::consts::FRAC_PI_2;
assert_eq!(angle(4), FRAC_PI_2);
assert_eq!(angle(15), FRAC_PI_2);
assert_eq!(angle(34), FRAC_PI_2);
}
#[test]
fn test_angle_should_be_1_point_75_pi() {
assert_eq!(angle(9), 1.75 * PI);
assert_eq!(angle(25), 1.75 * PI);
assert_eq!(angle(49), 1.75 * PI);
assert_eq!(angle(1089), 1.75 * PI);
}
#[test]
fn test_angle_37() {
assert_eq!(angle(37), 0.75 * PI);
}
// Data from square 1 is carried 0 steps, since it's at the access port.
#[test]
fn test_example1() {
assert_eq!(manhattan_distance(1), 0);
}
// Data from square 12 is carried 3 steps, such as: down, left, left.
#[test]
fn test_example2() {
assert_eq!(manhattan_distance(12), 3);
}
// Data from square 23 is carried only 2 steps: up twice.
#[test]
fn test_example3() {
assert_eq!(manhattan_distance(23), 2);
}
// Data from square 1024 must be carried 31 steps.
#[test]
fn test_example4() {
assert_eq!(manhattan_distance(1024), 31);
}
/*---*/
#[test]
fn test_example5() {
assert_eq!(manhattan_distance(46), 3);
}
#[test]
fn test_example6() {
assert_eq!(manhattan_distance(11), 2);
}
#[test]
fn test_example7() {
assert_eq!(manhattan_distance(10), 3);
}
#[test]
fn test_example8() {
assert_eq!(manhattan_distance(9), 2);
}
#[test]
fn test_example9() {
assert_eq!(manhattan_distance(49), 6);
}
#[test]
fn test_example10() {
assert_eq!(manhattan_distance(28), 3);
}
#[test]
fn test_example11() {
assert_eq!(manhattan_distance(1089), 32);
}
#[test]
fn test_example12() {
assert_eq!(manhattan_distance(37), 6);
}