--- /dev/null
+# 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
--- /dev/null
+[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
--- /dev/null
+# 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
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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)
+ )
+}
--- /dev/null
+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)
+ }
+}