Solved day 1

This commit is contained in:
2024-12-03 00:33:28 +01:00
commit 9b6584b0cf
14 changed files with 1640 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target
flamegraph.svg
perf.data
perf.data.*

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

11
.idea/advent-of-code-2024-rust.iml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/advent-of-code-2024-rust.iml" filepath="$PROJECT_DIR$/.idea/advent-of-code-2024-rust.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

105
Cargo.lock generated Normal file
View File

@@ -0,0 +1,105 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "advent-of-code-2024-rust"
version = "0.1.0"
dependencies = [
"itertools",
"num",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]

14
Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "advent-of-code-2024-rust"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
num = "0.4"
itertools = "0.13.0"
# For flamegraph, enable this:
[profile.release]
debug = true

1000
input/day01.txt Normal file

File diff suppressed because it is too large Load Diff

6
input/day1_example.txt Normal file
View File

@@ -0,0 +1,6 @@
3 4
4 3
2 5
1 3
3 9
3 3

59
src/day1.rs Normal file
View File

@@ -0,0 +1,59 @@
use std::arch::x86_64::_addcarryx_u32;
use itertools::Itertools;
use crate::day_solver::DaySolver;
#[cfg(test)]
use crate::util::read_file;
pub struct Day1 {
list1: Vec<u32>,
list2: Vec<u32>,
}
impl Day1 {
pub fn create(input: String) -> Self {
let lines = input.lines()
.map(|l| l.split_whitespace().map(|n| n.parse().unwrap()).collect())
.collect::<Vec<Vec<u32>>>();
let list1 = lines.iter().map(|l| l[0]).collect();
let list2 = lines.iter().map(|l| l[1]).collect();
// Put the input into the day struct
Day1 {
list1, list2
}
}
fn sort_list(list: &[u32]) -> Vec<u32> {
list.iter().sorted().map(|n| n.to_owned()).collect::<Vec<u32>>()
}
}
impl DaySolver for Day1 {
fn solve_part1(&mut self) -> String {
let list1_sorted = Day1::sort_list(&self.list1);
let list2_sorted = Day1::sort_list(&self.list2);
(0..list1_sorted.len()).map(|i| u32::abs_diff(list1_sorted[i], list2_sorted[i]))
.sum::<u32>().to_string()
}
fn solve_part2(&mut self) -> String {
self.list1.iter().map(|n1| self.list2.iter().filter(|n2| n1.eq(n2)).count() as u32 * n1)
.sum::<u32>().to_string()
}
}
#[test]
fn test_part1() {
let mut day = Day1::create(read_file("input/day1_example.txt"));
assert_eq!("11", day.solve_part1());
}
#[test]
fn test_part2() {
let mut day = Day1::create(read_file("input/day1_example.txt"));
assert_eq!("31", day.solve_part2());
}

40
src/dayX.rs Normal file
View File

@@ -0,0 +1,40 @@
use crate::day_solver::DaySolver;
#[cfg(test)]
use crate::util::read_file;
pub struct DayX {
}
impl DayX {
pub fn create(input: String) -> Self {
// let lines = input.lines();
// Put the input into the day struct
return DayX {}
}
}
impl DaySolver for DayX {
fn solve_part1(&mut self) -> String {
return 0.to_string();
}
fn solve_part2(&mut self) -> String {
return 0.to_string();
}
}
#[test]
fn test_part1() {
let mut day = DayX::create(read_file("input/dayX_example.txt"));
assert_eq!("EXAMPLE_ANSWER", day.solve_part1());
}
#[test]
fn test_part2() {
let mut day = DayX::create(read_file("input/dayX_example.txt"));
assert_eq!("EXAMPLE_ANSWER", day.solve_part2());
}

6
src/day_solver.rs Normal file
View File

@@ -0,0 +1,6 @@
pub trait DaySolver {
// fn create() -> Self;
fn solve_part1(&mut self) -> String;
fn solve_part2(&mut self) -> String;
}

192
src/main.rs Normal file
View File

@@ -0,0 +1,192 @@
extern crate core;
use std::time::Instant;
use crate::day1::Day1;
use crate::day_solver::DaySolver;
use crate::util::read_file;
mod util;
mod day_solver;
mod day1;
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
fn main() {
let args: Vec<String> = std::env::args().collect();
let day_arg_idx = args.iter().position(|a| a == "-d");
let single_day = day_arg_idx.is_some();
let day = if single_day { args[day_arg_idx.unwrap() + 1].parse::<u8>().unwrap() } else { 0 };
let benchmark_arg_idx_option = args.iter().position(|a| a == "--bench").or(
args.iter().position(|a| a == "-b"));
let benchmark = benchmark_arg_idx_option.is_some();
let bench_amount: u32 = if let Some(benchmark_arg_idx) = benchmark_arg_idx_option {
args.get(benchmark_arg_idx + 1)
.map_or(DEFAULT_BENCHMARK_AMOUNT, |r| r.parse::<u32>().unwrap_or(DEFAULT_BENCHMARK_AMOUNT))
} else {
DEFAULT_BENCHMARK_AMOUNT
};
let mut bench_results: Vec<AocBenchResult> = Vec::new();
// This is essentially the warmup for the benchmark:
run_once(single_day, day, false, &mut bench_results);
let first_run_bench = bench_results[0].to_owned();
let (_, total_time) = bench(||
if benchmark {
// Ignore the warmup run in the rest of the benchmark:
bench_results.clear();
for _ in 0..bench_amount {
run_once(single_day, day, true, &mut bench_results);
}
});
if benchmark {
println!("Executed {} rounds in {:.3} s ({} μs, or {} μs on average per round)", bench_results.len(), total_time as f64 / 1000000f64, total_time, total_time / bench_amount as u128);
print_bench_result(&bench_results, |b| b.total, "Execution");
print_bench_result(&bench_results, |b| b.read_file, "Reading file");
print_bench_result(&bench_results, |b| b.init, "Initialization");
print_bench_result(&bench_results, |b| b.part1, "Part 1");
print_bench_result(&bench_results, |b| b.part2, "Part 2");
print_bench_result(&bench_results, |b| b.part1 + b.part2, "Part 1 & 2 together");
println!("First time took {} μs (read {} μs, init {} μs, part 1: {} μs, part 2: {} μs)", first_run_bench.total, first_run_bench.read_file, first_run_bench.init, first_run_bench.part1, first_run_bench.part2);
println!("Average: {} μs (read {} μs, init {} μs, part 1: {} μs, part 2: {} μs)",
avg_bench(&bench_results, |b| b.total),
avg_bench(&bench_results, |b| b.read_file),
avg_bench(&bench_results, |b| b.init),
avg_bench(&bench_results, |b| b.part1),
avg_bench(&bench_results, |b| b.part2));
} else {
println!("Execution took {} μs (read {} μs, init {} μs, part 1: {} μs, part 2: {} μs)", first_run_bench.total, first_run_bench.read_file, first_run_bench.init, first_run_bench.part1, first_run_bench.part2);
}
}
#[derive(Copy, Clone)]
struct AocBenchResult {
read_file: u128,
init: u128,
part1: u128,
part2: u128,
total: u128
}
fn build_day_solver(day: u8, input: String) -> Option<Box<dyn DaySolver>> {
match day {
1 => Some(Box::new(Day1::create(input))),
// 2 => Some(Box::new(Day2::create(input))),
// 3 => Some(Box::new(Day3::create(input))),
// 4 => Some(Box::new(Day4::create(input))),
// 5 => Some(Box::new(Day5::create(input))),
// 6 => Some(Box::new(Day6::create(input))),
// 7 => Some(Box::new(Day7::create(input))),
// 8 => Some(Box::new(Day8::create(input))),
// 9 => Some(Box::new(Day9::create(input))),
// 10 => Some(Box::new(Day10::create(input))),
// 11 => Some(Box::new(Day11::create(input))),
// 12 => Some(Box::new(Day12::create(input))),
_ => None
}
}
fn solve(day: u8, silent: bool) -> AocBenchResult {
let now = Instant::now();
let (input, read_file_time) = bench(|| {
let input_filename = format!("input/day{:02}.txt",day);
read_file(&input_filename)
});
let (solver, init_time) = bench(|| build_day_solver(day, input.to_owned()));
let part1_time: u128;
let part2_time: u128;
match solver {
Some(mut s) => {
let(part1, pt1_time) = bench(|| s.solve_part1());
part1_time = pt1_time;
if !silent {
println!("Day {} Part 1: {}", day, part1);
}
let (part2, pt2_time) = bench(|| s.solve_part2());
part2_time = pt2_time;
if !silent {
println!("Day {} Part 2: {}", day, part2);
}
},
None => panic!("This day is not yet implemented")
}
AocBenchResult{
read_file: read_file_time,
init: init_time,
part1: part1_time,
part2: part2_time,
total: now.elapsed().as_micros()
}
}
fn print_bench_result<F>(bench_results: &[AocBenchResult], f: F, bench_part_description: &str)
where
F: FnMut(&AocBenchResult) -> u128 {
let mut benches: Vec<u128> = bench_results.iter().map(f).collect();
benches.sort();
let avg_runtime: u128 = benches.iter().sum::<u128>() / (bench_results.len() as u128);
println!("{} took {} μs {} (Min: {} μs, Max: {} μs, Median: {} μs)", bench_part_description, avg_runtime, if bench_results.len() > 1 { "on average"} else {""},
benches[0], benches[benches.len() - 1], benches[benches.len() / 2])
}
fn avg_bench<F>(bench_results: &[AocBenchResult], f: F) -> u128
where
F: FnMut(&AocBenchResult) -> u128 {
let benches: Vec<u128> = bench_results.iter().map(f).collect();
return benches.iter().sum::<u128>() / (bench_results.len() as u128)
}
fn run_once(single_day: bool, day: u8, silent: bool, bench_results: &mut Vec<AocBenchResult>) {
let bench_result = if single_day {
solve(day, silent)
} else {
solve_all(silent)
};
bench_results.push(bench_result);
}
fn solve_all(silent: bool) -> AocBenchResult {
let mut bench_results = Vec::new();
let max_day = (1..).take_while(|d| build_day_solver(*d, "".to_string()).is_some()).last().unwrap();
for day in 1..(max_day + 1) {
bench_results.push(solve(day, silent));
}
return AocBenchResult {
read_file: bench_results.iter().map(|t| t.read_file).sum(),
init: bench_results.iter().map(|t| t.init).sum(),
part1: bench_results.iter().map(|t| t.part1).sum(),
part2: bench_results.iter().map(|t| t.part2).sum(),
total: bench_results.iter().map(|t| t.total).sum(),
}
}
fn bench<F, K>(mut f: F) -> (K, u128)
where F: FnMut() -> K {
let now = Instant::now();
let res = f();
(res, now.elapsed().as_micros())
}

181
src/util.rs Normal file
View File

@@ -0,0 +1,181 @@
use std::fmt::Display;
use std::fs;
use itertools::Itertools;
use num::Integer;
pub fn read_file(filename: &str) -> String {
fs::read_to_string(filename)
.expect("Couldn't read file!")
}
#[allow(dead_code)]
pub fn into_lines(input: String) -> Vec<String> {
input.lines().map(|l| l.to_owned()).collect()
}
#[derive(Debug, Clone)]
pub struct Grid<T> {
pub(crate) width: usize,
pub(crate) data: Vec<T>,
/** What to return if a coordinate that is out of bounds is requested */
pub(crate) default: Option<T>
}
impl<T: Clone + Sized> Grid<T> {
#[allow(dead_code)]
pub fn set_row(&mut self, y: usize, v: T) {
// let range = &mut self.data[x * self.width..(x+1) * self.width];
for i in self.width * y..self.width * (y + 1) {
self.data[i] = v.to_owned();
}
}
#[allow(dead_code)]
pub fn set_col(&mut self, x: usize, v: T) {
for y in 0..self.height() {
let idx = self.idx(&x, &y);
self.data[idx] = v.to_owned();
}
}
pub(crate) fn idx(&self, x: &usize, y: &usize) -> usize {
y * self.width + x
}
pub(crate) fn coord(&self, i: usize) -> Coord<usize> {
Coord::new(i % self.width, i / self.width)
}
pub(crate) fn height(&self) -> usize {
self.data.len() / self.width
}
pub(crate) fn get(&self, x: &usize, y: &usize) -> &T {
if self.in_bounds(x, y) {
let idx = self.idx(x, y);
&self.data[idx]
} else if let Some(x) = &self.default {
return x;
} else {
panic!("Out of grid bounds: {}, {}", x, y);
}
}
#[allow(dead_code)]
pub fn set(&mut self, x: &usize, y: &usize, v: T) {
let idx = self.idx(x, y);
self.data[idx] = v;
}
pub fn in_bounds(&self, x: &usize, y: &usize) -> bool {
return x < &self.width && y < &self.height()
}
pub fn find_positions<'a, P>(&'a self, predicate: &'a P) -> impl Iterator<Item = Coord<usize>> + 'a
where P: Fn(&'a T) -> bool + 'a {
return self.data.iter().enumerate()
.filter(|(_,x)| predicate(x).to_owned())
.map(|(i, _)| self.coord(i));
}
pub fn validate(&self) {
debug_assert!(&self.data.len() % self.width == 0)
}
}
impl <T: Display + Clone + Sized> Grid<T> {
#[allow(dead_code)]
pub fn print(&self) {
self.print_range(0, self.width, 0, self.height());
}
#[allow(dead_code)]
pub fn print_range(&self, from_x: usize, to_x: usize, from_y: usize, to_y: usize) {
for y in from_y.max(0)..to_y.min(self.height()) {
for x in from_x.max(0)..to_x.min(self.width) {
print!("{}", self.get(&x, &y))
}
println!();
}
}
}
impl <T: PartialEq> Grid<T> {
pub fn find(&self, v: &T) -> Option<Coord<usize>> {
return if let Some((i, _)) = self.data.iter().find_position(|x| x == &v) {
return Some(Coord::new(i % self.width, i / self.width))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Coord<T> where T: Sized {
pub x: T,
pub y: T
}
#[allow(dead_code)]
impl<T> Coord<T> where T: Sized {
pub(crate) fn new(x: T, y: T) -> Self {
Coord { x, y }
}
}
#[allow(dead_code)]
impl <T> Coord<T> where T: Integer + Copy {
pub fn manhattan_dist(&self, other: &Coord<T>) -> T {
self.x.max(other.x).sub(self.x.min(other.x)) + self.y.max(other.y).sub(self.y.min(other.y))
}
}
#[allow(dead_code)]
impl Coord<usize> {
pub fn north(&self) -> Option<Coord<usize>> {
if self.y == 0 { return None }
return Some(Coord::new(self.x, self.y - 1));
}
pub fn south(&self) -> Option<Coord<usize>> {
return Some(Coord::new(self.x, self.y + 1));
}
pub fn east(&self) -> Option<Coord<usize>> {
return Some(Coord::new(self.x + 1, self.y));
}
pub fn west(&self) -> Option<Coord<usize>> {
if self.x == 0 { return None }
return Some(Coord::new(self.x - 1, self.y));
}
pub fn compass(&self) -> Vec<Coord<usize>> {
let mut res = Vec::with_capacity(4);
if let Some(north) = self.north() {
res.push(north);
}
if let Some(south) = self.south() {
res.push(south);
}
if let Some(east) = self.east() {
res.push(east);
}
if let Some(west) = self.west() {
res.push(west);
}
res
}
}