There is no readme
use std::fs;
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// A file containing the input
input_file: PathBuf,
#[arg(short, long)]
part: Option,
}
fn main() {
// Parse CLI arguments
let cli = Cli::parse();
// Read file
let input_text = fs::read_to_string(&cli.input_file)
.expect(format!("File \"{}\" not found", cli.input_file.display()).as_str());
let (run_part_1, run_part_2) = if let Some(part) = cli.part {
match part {
1 => (true, false),
2 => (false, true),
_ => unimplemented!(),
}
} else {
(true, true)
};
let (it1, it2) = preprocess(&input_text);
if run_part_1 {
let solution = solve(it1);
println!("Part 1: {solution}");
}
if run_part_2 {
let solution = solve(it2);
println!("Part 2: {solution}");
}
}
/// Preprocessing for solving
/// Extracts important information from the input
/// `part` specifies which part to preprocess for.
/// Returns a vector for each part containing a direction and amount
fn preprocess(input: &str) -> (Vec<((isize, isize), isize)>, Vec<((isize, isize), isize)>) {
let it = input.lines().map(|l| {
let line: Vec<_> = l.split(' ').collect();
let direction: char = line[0].chars().nth(0).unwrap();
let amount: isize = line[1].parse().unwrap();
let color: &str = &line[2][2..8];
let direction = match direction {
'R' => (1, 0),
'L' => (-1, 0),
'U' => (0, 1),
'D' => (0, -1),
_ => unreachable!(),
};
((direction, amount), {
let amount: isize = isize::from_str_radix(&color[..5], 16).unwrap();
let direction = match &color[5..] {
"0" => (1, 0),
"1" => (0, -1),
"2" => (-1, 0),
"3" => (0, 1),
_ => unreachable!(),
};
(direction, amount)
})
});
let it1 = it.clone().map(|(i1, _i2)| i1).collect();
let it2 = it.map(|(_i1, i2)| i2).collect();
(it1, it2)
}
/// Finds the area using the shoelace formula
fn solve(it: Vec<((isize, isize), isize)>) -> isize {
// Get coordinates from it
let mut coords: Vec<(isize, isize)> = Vec::with_capacity(it.len() + 1);
// Start at (0, 0)
coords.push((0, 0)); // Needs to be at both begining and end
let (mut x, mut y) = (0, 0);
let mut perimeter_length = 0;
// Compute and add the coords
for (direction, amount) in it {
let translation = (direction.0 * amount, direction.1 * amount);
x += translation.0;
y += translation.1;
coords.push((x, y));
perimeter_length += amount;
}
// Should end where it started
assert_eq!(coords.last().unwrap(), &(0, 0));
// Shoelace formula
let a = coords
.iter()
.zip(coords.iter().skip(1))
.map(|((x1, y1), (x2, y2))| (x1 * y2) - (x2 * y1))
.sum::()
/ 2;
// Found by drawing, then trial and error
// Shoelace area missing 1/2 of perimeter
// Inside and outside corners cancel out except for one
a.abs() + perimeter_length / 2 + 1
}
args ← {1↓⍵/⍨∨\⍵∊⊂'--'} ⎕ARG
inputs ← ⎕FIO[49]¨ args
words ← 'one' 'two' 'three' 'four' 'five' 'six' 'seven' 'eight' 'nine'
digits ← '123456789'
part1 ← {↑↑+/{(10×↑⍵)+¯1↑⍵}¨{⍵~0}¨+⊃(⍳9)+.×digits∘.⍷⍵}
"Part 1: ", part1¨ inputs
part2 ← {↑↑+/{(10×↑⍵)+¯1↑⍵}¨{⍵~0}¨+⊃(⍳9)+.×(words∘.⍷⍵)+digits∘.⍷⍵}
"Part 2: ", part2¨ inputs
)OFF
use std::fs;
use std::path::PathBuf;
use clap::Parser;
use rayon::prelude::*;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
input_file: PathBuf,
}
#[derive(Copy, Clone)]
enum TileState {
None,
Energized(BeamState),
}
#[derive(Default, Copy, Clone)]
struct BeamState {
up: bool,
down: bool,
left: bool,
right: bool,
}
fn main() {
// Parse CLI arguments
let cli = Cli::parse();
// Read file
let input_text = fs::read_to_string(&cli.input_file)
.expect(format!("File \"{}\" not found", cli.input_file.display()).as_str());
let tiles: Vec> = input_text.lines().map(|l| l.chars().collect()).collect();
// Part 1
let part_1 = test_beam(&tiles, (0, 0), (0, 1));
println!("Part 1: {}", part_1);
// Part 2
let part_2: usize = (0..4)
.into_par_iter()
.map(|dir| {
(0..tiles.len())
.into_par_iter()
.map(move |x| (dir.clone(), x))
})
.flatten()
.map(|(dir, x)| match dir {
0 => ((0, x), (1, 0)),
1 => ((x, tiles[0].len() - 1), (0, -1)),
2 => ((tiles.len() - 1, x), (-1, 0)),
3 => ((x, 0), (0, 1)),
_ => unreachable!(),
})
.map(|(loc, dir)| test_beam(&tiles, loc, dir))
.max()
.unwrap();
println!("Part 2: {}", part_2);
}
fn test_beam(
tiles: &Vec>,
start_location: (usize, usize),
start_direction: (i64, i64),
) -> usize {
let mut energized: Vec> =
vec![vec![TileState::None; tiles[0].len()]; tiles.len()];
continue_beam(
&mut energized,
&tiles,
start_location,
start_direction,
true,
0,
);
energized
.iter()
.map(|r| {
r.iter()
.filter(|t| matches!(t, TileState::Energized(_)))
.count()
})
.sum()
}
fn continue_beam(
energized: &mut Vec>,
tiles: &Vec>,
beam_location: (usize, usize),
beam_direction: (i64, i64),
start_hack: bool,
depth: usize,
) {
assert_ne!(beam_direction, (0, 0));
// Set current tile to energized with the direction
let current_state = energized[beam_location.0][beam_location.1];
if !start_hack {
energized[beam_location.0][beam_location.1] = match current_state {
TileState::None => TileState::Energized(match beam_direction {
(0, 1) => BeamState {
right: true,
..BeamState::default()
},
(0, -1) => BeamState {
left: true,
..BeamState::default()
},
(1, 0) => BeamState {
down: true,
..BeamState::default()
},
(-1, 0) => BeamState {
up: true,
..BeamState::default()
},
_ => unreachable!(),
}),
TileState::Energized(state) => TileState::Energized(match beam_direction {
(0, 1) => {
if state.right {
return;
}
BeamState {
right: true,
..state
}
}
(0, -1) => {
if state.left {
return;
}
BeamState {
left: true,
..state
}
}
(1, 0) => {
if state.down {
return;
}
BeamState {
down: true,
..state
}
}
(-1, 0) => {
if state.up {
return;
}
BeamState { up: true, ..state }
}
_ => unreachable!(),
}),
};
}
// energized[beam_location.0][beam_location.1] = TileState::Energized(BeamState { up: , down: , left: , right: });
let next_beam_location = {
let loc = (
(beam_location.0 as i64 + beam_direction.0),
(beam_location.1 as i64 + beam_direction.1),
);
if start_hack {
beam_location
} else if loc.0 < 0
|| loc.0 >= tiles.len() as i64
|| loc.1 < 0
|| loc.1 >= tiles[0].len() as i64
{
return;
} else {
(loc.0 as usize, loc.1 as usize)
}
};
let next_beam_tile = tiles[next_beam_location.0][next_beam_location.1];
let next_beam_directions: Vec<(i64, i64)> = match next_beam_tile {
'.' => vec![beam_direction],
'/' => match beam_direction {
(0, 1) => vec![(-1, 0)],
(0, -1) => vec![(1, 0)],
(1, 0) => vec![(0, -1)],
(-1, 0) => vec![(0, 1)],
_ => unreachable!(),
},
'\\' => match beam_direction {
(0, 1) => vec![(1, 0)],
(0, -1) => vec![(-1, 0)],
(1, 0) => vec![(0, 1)],
(-1, 0) => vec![(0, -1)],
_ => unreachable!(),
},
'|' => match beam_direction {
(0, 1) => vec![(1, 0), (-1, 0)],
(0, -1) => vec![(1, 0), (-1, 0)],
(1, 0) => vec![(1, 0)],
(-1, 0) => vec![(-1, 0)],
_ => unreachable!(),
},
'-' => match beam_direction {
(0, 1) => vec![(0, 1)],
(0, -1) => vec![(0, -1)],
(1, 0) => vec![(0, 1), (0, -1)],
(-1, 0) => vec![(0, 1), (0, -1)],
_ => unreachable!(),
},
_ => unreachable!(),
};
for dir in next_beam_directions {
continue_beam(energized, tiles, next_beam_location, dir, false, depth + 1);
}
}
26.28 line-seconds
What does the line-seconds score mean?
use std::fs;
use std::path::PathBuf;
use clap::Parser;
use itertools::Itertools;
use std::ops::Range;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
input_file: PathBuf,
}
fn main() {
// Parse CLI arguments
let cli = Cli::parse();
// Read file
let input_text = fs::read_to_string(&cli.input_file)
.expect(format!("File \"{}\" not found", cli.input_file.display()).as_str());
let map: Vec> = input_text.lines().map(|r| r.chars().collect()).collect();
let mut expanding_rows = vec![];
// Expand rows
map.iter().enumerate().for_each(|(row_num, r)| {
if r.iter().filter(|c| **c == '.').count() == r.len() {
expanding_rows.push(row_num);
}
});
let mut expanding_cols = vec![];
// Find expanding cols
for col in 0..map[0].len() {
// Determine if all '.'
let expand = map.iter().filter(|row| row[col] == '.').count() == map.len();
if expand {
expanding_cols.push(col);
}
}
// Find galaxies
let mut galaxies: Vec<(usize, usize)> = vec![];
for (row_num, row) in map.iter().enumerate() {
for (col_num, char) in row.iter().enumerate() {
if *char == '#' {
galaxies.push((row_num.try_into().unwrap(), col_num.try_into().unwrap()));
}
}
}
println!("Num Galaxies: ({})", galaxies.len());
println!(
"Expanding rows: {:?}\tExpanding cols: {:?}",
expanding_rows, expanding_cols
);
// Distance between each pair
let expansion_1 = 2;
let expansion_2 = 1_000_000;
let mut sum_distances_1 = 0;
let mut sum_distances_2 = 0;
println!(
"Total combinations: {}",
galaxies.iter().tuple_combinations::<(_, _)>().count()
);
for (g1, g2) in galaxies.iter().tuple_combinations() {
let vert_range: Range = if g1.0 < g2.0 {
(g1.0)..(g2.0)
} else {
(g2.0)..(g1.0)
};
let horr_range: Range = if g1.1 < g2.1 {
(g1.1)..(g2.1)
} else {
(g2.1)..(g1.1)
};
let vert_expansions = expanding_rows
.iter()
.filter(|r| vert_range.contains(*r))
.count();
let horr_expansions = expanding_cols
.iter()
.filter(|r| horr_range.contains(*r))
.count();
let expansions = vert_expansions + horr_expansions;
let distance = vert_range.len() + horr_range.len();
sum_distances_1 += distance + (expansions * (expansion_1 - 1));
sum_distances_2 += distance + (expansions * (expansion_2 - 1));
}
println!("Total distance: {}", sum_distances_1);
println!("Part 2 distance: {}", sum_distances_2);
}
I printed the loop using box drawing characters, zoomed out, took a screenshot, and used GIMP to separate the inside and outside, and count the number of inside tiles.
For part 2, all I did was edit the input and change i32
to i64
.
use std::fs;
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
input_file: PathBuf,
}
fn main() {
// Parse CLI arguments
let cli = Cli::parse();
// Read file
let input_text = fs::read_to_string(&cli.input_file)
.expect(format!("File \"{}\" not found", cli.input_file.display()).as_str());
let input_lines: Vec<&str> = input_text.lines().collect();
let times: Vec = input_lines[0]
.split_ascii_whitespace()
.skip(1)
.map(|s| s.parse().unwrap())
.collect();
let distances: Vec = input_lines[1]
.split_ascii_whitespace()
.skip(1)
.map(|s| s.parse().unwrap())
.collect();
println!("{:?}", times);
println!("{:?}", distances);
let mut product: i64 = 1;
for (time, distance) in times.iter().zip(distances) {
let mut n = 0;
for b in 0..*time {
if time * b - b * b > distance {
n += 1;
}
}
product *= n;
}
println!("{}", product)
}
[Rust]
use std::fs;
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
input_file: PathBuf,
}
/// Start < end
#[derive(Debug, Copy, Clone)]
struct Range {
start: Idx,
end: Idx,
}
impl From> for Range {
fn from(item: std::ops::Range) -> Self {
Range {
start: item.start,
end: item.end,
}
}
}
impl std::ops::Add for Range {
type Output = Self;
fn add(self, offset: i64) -> Self {
Range {
start: self.start + offset,
end: self.end + offset,
}
}
}
#[derive(Debug, Copy, Clone)]
enum Value {
Unmapped(Range),
Mapped(Range),
}
impl Value {
fn value(self) -> Range {
match self {
Self::Mapped(n) => n,
Self::Unmapped(n) => n,
}
}
fn min(self) -> i64 {
self.value().start
}
}
fn main() {
// Parse CLI arguments
let cli = Cli::parse();
// Read file
let input_text = fs::read_to_string(&cli.input_file)
.expect(format!("File \"{}\" not found", cli.input_file.display()).as_str());
println!("{}", input_text);
// Split into seeds and individual maps
let mut maps_text = input_text.split("\n\n");
let seeds_text = maps_text.next().expect("Seeds");
// Seed numbers; will be mapped to the final locations
let seed_numbers: Vec = parse_seeds(seeds_text);
let seed_numbers = parse_seed_ranges(seed_numbers);
let mut seed_values: Vec = seed_numbers.iter().map(|n| Value::Unmapped(*n)).collect();
eprintln!("Seeds: {:?}", seed_values);
// Apply maps to seeds
for map_text in maps_text {
let mut map_ranges_text = map_text.lines();
let map_description = map_ranges_text
.next()
.expect("There should be a description of the map here");
eprintln!("Map: {}", map_description);
// Apply ranges to seeds
// Map structure:
// dest start, source start, range length
for range_text in map_ranges_text {
let range_numbers: Vec = range_text
.split_ascii_whitespace()
.map(|n| n.parse().expect("Should be a number here"))
.collect();
eprintln!("\trange: {:?}", range_numbers);
let source_range = range_numbers[1]..range_numbers[1] + range_numbers[2];
let offset = range_numbers[0] - range_numbers[1];
eprintln!("\t\tSource range: {:?}\tOffset: {}", source_range, offset);
// Apply the range map to the seeds
seed_values = seed_values
.iter()
.map(|n| match n {
Value::Unmapped(n) => map_seed_range(*n, source_range.clone().into(), offset),
Value::Mapped(_) => vec![*n],
})
.flatten()
.collect();
eprintln!("\t\tSeed values: {:?}", seed_values);
}
// Reset seed values to unmapped
seed_values = seed_values
.iter()
.map(|v| Value::Unmapped(v.value()))
.collect();
}
// Find minimum
let min_location = seed_values.iter().map(|v| v.min()).min().unwrap();
println!();
println!("Part 2: {}", min_location);
}
/// Parse string to vec of string numbers
fn parse_seeds(seeds_text: &str) -> Vec {
seeds_text
.split_ascii_whitespace()
.skip(1)
.map(|n| n.parse().expect("Should be a number here"))
.collect()
}
/// Fill out ranges of seed numbers
fn parse_seed_ranges(seed_numbers: Vec) -> Vec> {
seed_numbers
.chunks(2)
.map(|v| (v[0]..v[0] + v[1]).into())
.collect()
}
/// Maps a seed range to possibly multiple based on a source range and offset
fn map_seed_range(seed_range: Range, source_range: Range, offset: i64) -> Vec {
let start_cmp = seed_range.start < source_range.start;
let end_cmp = seed_range.end <= source_range.end;
match (start_cmp, end_cmp) {
(false, false) => {
if source_range.end <= seed_range.start {
vec![Value::Unmapped(seed_range)]
} else {
vec![
Value::Mapped((seed_range.start + offset..source_range.end + offset).into()),
Value::Unmapped((source_range.end..seed_range.end).into()),
]
}
}
(false, true) => vec![Value::Mapped(seed_range + offset)],
(true, false) => vec![
Value::Unmapped((seed_range.start..source_range.start).into()),
Value::Mapped((source_range.start + offset..source_range.end + offset).into()),
Value::Unmapped((source_range.end..seed_range.end).into()),
],
(true, true) => {
if seed_range.end <= source_range.start {
vec![Value::Unmapped(seed_range)]
} else {
vec![
Value::Unmapped((seed_range.start..source_range.start).into()),
Value::Mapped((source_range.start + offset..seed_range.end + offset).into()),
]
}
}
}
}
It would be “hiler”
At least 2