First day master
authorGreg Burri <greg.burri@gmail.com>
Fri, 1 Dec 2023 14:29:28 +0000 (15:29 +0100)
committerGreg Burri <greg.burri@gmail.com>
Fri, 1 Dec 2023 14:29:28 +0000 (15:29 +0100)
.gitignore [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
README.md [new file with mode: 0644]
src/day01.rs [new file with mode: 0644]
src/days.rs [new file with mode: 0644]
src/main.rs [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..088ba6b
--- /dev/null
@@ -0,0 +1,10 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..d596fa8
--- /dev/null
@@ -0,0 +1,18 @@
+[package]
+name = "advent_of_code_2023"
+version = "0.1.0"
+authors = ["Greg Burri <greg.burri@gmail.com>"]
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+itertools = "0.10"
+regex = "1"
+clap = { version = "4", features = ["derive"] }
+rayon = "1.6"
+
+[profile.release]
+opt-level = 3
+lto = true
+codegen-units = 1
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..cf6601d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+# AdventOfCode2023
+
+https://adventofcode.com/2023
+
+
+# Running tests
+
+Example for day 1 tests:
+
+~~~
+cargo test day01 -- --nocapture
+~~~
+
+All tests:
+
+~~~
+cargo test -- --nocapture
+~~~
+
+
+# Running a day code
+
+~~~
+cargo run -- n
+~~~
+
+Where 'n' is a number from 1 to 25
diff --git a/src/day01.rs b/src/day01.rs
new file mode 100644 (file)
index 0000000..94e535c
--- /dev/null
@@ -0,0 +1,86 @@
+use std::str::Lines;
+
+use itertools::Itertools;
+
+pub fn parse(s: &str) -> Lines {
+    s.lines()
+}
+
+fn calibration_value(line: &str) -> u32 {
+    let first = line.chars().find(|c| c.is_digit(10)).unwrap();
+    let last = line.chars().rev().find(|c| c.is_digit(10)).unwrap();
+    format!("{}{}", first, last).parse().unwrap()
+}
+
+pub fn calibration_sum(lines: &Lines) -> u32 {
+    lines.clone().map(calibration_value).sum()
+}
+
+fn calibration_value_corrected(line: &str) -> u32 {
+    let line_chars = line.chars().collect_vec();
+    let mut first: i32 = -1;
+    let mut last: i32 = -1;
+    let spelled_digits = [
+        "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
+    ];
+    let mut i = 0;
+    while i < line.len() {
+        if let Some(d) = line_chars[i].to_digit(10) {
+            if first == -1 {
+                first = d as i32;
+            }
+            last = d as i32;
+        } else {
+            for j in 0..spelled_digits.len() {
+                let d = spelled_digits[j];
+                if line[i..].starts_with(d) {
+                    if first == -1 {
+                        first = j as i32;
+                    }
+                    last = j as i32;
+                    i += 1;
+                    // We can't skip an entire word because of cases like
+                    // "twone", "nineight", etc.
+                    break;
+                }
+            }
+        }
+
+        i += 1;
+    }
+    format!("{}{}", first, last).parse().unwrap()
+}
+
+pub fn calibration_sum_corrected(lines: &Lines) -> u32 {
+    lines.clone().map(calibration_value_corrected).sum()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    static CALIBRATION_LINES: &str = "1abc2
+    pqr3stu8vwx
+    a1b2c3d4e5f
+    treb7uchet";
+
+    #[test]
+    fn part1() {
+        let lines = parse(CALIBRATION_LINES);
+        assert_eq!(calibration_sum(&lines), 142);
+    }
+
+    static CALIBRATION_LINES_2: &str = "two1nine
+    eightwothree
+    abcone2threexyz
+    xtwone3four
+    4nineeightseven2
+    zoneight234
+    7pqrstsixteen";
+
+    #[test]
+    fn part2() {
+        let lines = parse(CALIBRATION_LINES_2);
+        assert_eq!(calibration_sum_corrected(&lines), 281);
+    }
+}
diff --git a/src/days.rs b/src/days.rs
new file mode 100644 (file)
index 0000000..c1a501e
--- /dev/null
@@ -0,0 +1,13 @@
+use std::fs;
+
+use crate::*;
+
+pub fn day01() -> String {
+    let input = fs::read_to_string("data/day01.input").unwrap();
+    let lines = day01::parse(&input);
+    format!(
+        "part1: {}, part2: {}",
+        day01::calibration_sum(&lines),
+        day01::calibration_sum_corrected(&lines)
+    )
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644 (file)
index 0000000..7219d65
--- /dev/null
@@ -0,0 +1,72 @@
+use std::time::Instant;
+
+use clap::Parser;
+use rayon::prelude::*;
+
+mod day01;
+mod days;
+
+#[derive(Parser, Debug)]
+#[command(author = "Greg Burri", version = "1.0", about = "Advent of Code 2023")]
+struct Args {
+    #[arg(index(1), exclusive(true))]
+    day: Option<usize>,
+
+    #[arg(short, long)]
+    parallel: bool,
+}
+
+fn main() {
+    println!("https://adventofcode.com/2023");
+
+    let days: Vec<fn() -> String> = vec![days::day01];
+
+    let args = Args::parse();
+
+    match args.day {
+        Some(day) => {
+            if day >= 1 && day <= days.len() {
+                do_day(&days, day)
+            } else {
+                println!("Unknown day: {}", day)
+            }
+        }
+        // No argument -> execute all day problems.
+        None => {
+            let now = Instant::now();
+
+            if args.parallel {
+                (1..=days.len())
+                    .into_par_iter()
+                    .for_each(|d| do_day(&days, d));
+            } else {
+                (1..=days.len()).for_each(|d| do_day(&days, d));
+            }
+
+            println!(
+                "Time to execute all days: {}",
+                format_micros(now.elapsed().as_micros())
+            );
+        }
+    }
+}
+
+fn do_day(days: &[fn() -> String], day: usize) {
+    let now = Instant::now();
+    println!(
+        "Result of day {:02}: {} (time: {})",
+        day,
+        days[day - 1](),
+        format_micros(now.elapsed().as_micros())
+    );
+}
+
+fn format_micros(t: u128) -> String {
+    if t < 10_000 {
+        format!("{} μs", t)
+    } else if t < 10_000_000u128 {
+        format!("{:.2} ms", t as f64 / 1e3f64)
+    } else {
+        format!("{:.2} s", t as f64 / 1e6f64)
+    }
+}