Skip to content

Commit

Permalink
Feature/2024/05 (#30)
Browse files Browse the repository at this point in the history
* 2024/05/p1

* 2024/05/p2

* cargo fmt
  • Loading branch information
jstuczyn authored Dec 12, 2024
1 parent 703e99e commit fc90286
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 1 deletion.
26 changes: 26 additions & 0 deletions 2024/day05/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "day05_2024"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "day05_2024"
path = "src/lib.rs"

[dependencies]
aoc-solution = { path = "../../aoc-solution" }
aoc-common = { path = "../../common" }
anyhow = { workspace = true }
winnow = { workspace = true }

[dev-dependencies]
criterion = { workspace = true }

[[bench]]
name = "benchmarks"
harness = false

[lints]
workspace = true
18 changes: 18 additions & 0 deletions 2024/day05/benches/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2023 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use aoc_common::define_aoc_benchmark;
use day05_2024::Day05;

define_aoc_benchmark!("inputs/2024/day05", Day05);
151 changes: 151 additions & 0 deletions 2024/day05/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2024 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use aoc_common::parsing::combinators::parse_number;
use std::collections::HashMap;
use std::str::FromStr;
use winnow::ascii::line_ending;
use winnow::combinator::{alt, separated, separated_pair};
use winnow::{PResult, Parser};

#[derive(Clone, Debug)]
pub struct PrintingRules {
// map of rules where the key value must come before any of the specified values
ordering_rules: HashMap<usize, Vec<usize>>,
printing_updates: Vec<PrintingUpdate>,
}

impl PrintingRules {
fn can_be_printed_before(&self, page: usize, after: &[usize]) -> bool {
if after.is_empty() {
return true;
}

let Some(rules) = self.ordering_rules.get(&page) else {
return false;
};

for next in after {
if next == &page {
continue;
}

if !rules.contains(next) {
return false;
}
}

true
}

pub fn is_update_valid(&self, update: &PrintingUpdate) -> bool {
for (i, page) in update.pages_to_produce.iter().enumerate() {
if !self.can_be_printed_before(*page, &update.pages_to_produce[i + 1..]) {
return false;
}
}
true
}

pub fn fix_update(&self, update: &PrintingUpdate) -> PrintingUpdate {
let mut fixed = Vec::with_capacity(update.pages_to_produce.len());

let mut pages_to_insert = update.pages_to_produce.clone();
// first page is the one that comes before **all** remaining ones (there can only be one)
// second page includes all but the first, etc...
// is this the most efficient? lol, no, insertion sort is one of the worst ones,
// but given the size of the input, it's a perfectly valid solution to this problem
while !pages_to_insert.is_empty() {
if pages_to_insert.len() == 1 {
fixed.push(pages_to_insert[0]);
break;
}
for (i, page) in pages_to_insert.iter().enumerate() {
if self.can_be_printed_before(*page, &pages_to_insert) {
fixed.push(*page);
pages_to_insert.remove(i);
break;
}
}
}

PrintingUpdate {
pages_to_produce: fixed,
}
}

pub fn updates(&self) -> &[PrintingUpdate] {
&self.printing_updates
}
}

#[derive(Clone, Copy, Debug)]
pub struct RawOrderingRule {
first: usize,
second: usize,
}

#[derive(Clone, Debug)]
pub struct PrintingUpdate {
pages_to_produce: Vec<usize>,
}

impl PrintingUpdate {
pub fn middle_value(&self) -> usize {
self.pages_to_produce[self.pages_to_produce.len() / 2]
}
}

fn ordering_rule_parser(input: &mut &str) -> PResult<RawOrderingRule> {
separated_pair(parse_number, '|', parse_number)
.map(|(first, second)| RawOrderingRule { first, second })
.parse_next(input)
}

fn raw_ordering_rules_parser(input: &mut &str) -> PResult<Vec<RawOrderingRule>> {
separated(1.., ordering_rule_parser, line_ending).parse_next(input)
}

fn printing_update_parser(input: &mut &str) -> PResult<PrintingUpdate> {
separated(1.., parse_number::<usize>, ',')
.map(|pages_to_produce| PrintingUpdate { pages_to_produce })
.parse_next(input)
}

impl FromStr for PrintingRules {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let (raw_ordering_rules, printing_updates) = separated_pair(
raw_ordering_rules_parser,
alt(("\n\n", "\r\n\r\n")),
separated(1.., printing_update_parser, line_ending),
)
.parse(s.trim())
.map_err(|err| anyhow::format_err!("{err}"))?;

let mut ordering_rules = HashMap::new();
for raw in raw_ordering_rules {
ordering_rules
.entry(raw.first)
.or_insert_with(Vec::new)
.push(raw.second);
}

Ok(PrintingRules {
ordering_rules,
printing_updates,
})
}
}
95 changes: 95 additions & 0 deletions 2024/day05/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2024 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::common::PrintingRules;
use aoc_common::parsing::FromStrParser;
use aoc_solution::Aoc;

mod common;

#[derive(Aoc)]
#[aoc(input = PrintingRules)]
#[aoc(parser = FromStrParser)]
#[aoc(part1(output = usize, runner = part1))]
#[aoc(part2(output = usize, runner = part2))]
pub struct Day05;

pub fn part1(input: PrintingRules) -> usize {
input
.updates()
.iter()
.filter(|u| input.is_update_valid(u))
.map(|u| u.middle_value())
.sum()
}

pub fn part2(input: PrintingRules) -> usize {
input
.updates()
.iter()
.filter(|u| !input.is_update_valid(u))
.map(|u| input.fix_update(u).middle_value())
.sum()
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;

fn sample_input() -> PrintingRules {
r#"47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13
75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47"#
.parse()
.unwrap()
}

#[test]
fn part1_sample_input() {
let expected = 143;
assert_eq!(expected, part1(sample_input()))
}

#[test]
fn part2_sample_input() {
let expected = 123;
assert_eq!(expected, part2(sample_input()))
}
}
22 changes: 22 additions & 0 deletions 2024/day05/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2024 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use aoc_common::helpers::root_path;
use aoc_solution::AocSolutionSolver;
use day05_2024::Day05;

#[cfg(not(tarpaulin_include))]
fn main() {
Day05::try_solve_from_file(root_path("inputs/2024/day05"))
}
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ members = [
"2024/day01",
"2024/day02",
"2024/day03",
"2024/day04"
"2024/day04",
"2024/day05"
]

[workspace.dependencies]
Expand Down
1 change: 1 addition & 0 deletions solution-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ day01_2024 = { path = "../2024/day01" }
day02_2024 = { path = "../2024/day02" }
day03_2024 = { path = "../2024/day03" }
day04_2024 = { path = "../2024/day04" }
day05_2024 = { path = "../2024/day05" }
1 change: 1 addition & 0 deletions solution-runner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ fn main() {
define_solution!(args, 2024, 2, "inputs/2024/day02", day02_2024::Day02);
define_solution!(args, 2024, 3, "inputs/2024/day03", day03_2024::Day03);
define_solution!(args, 2024, 4, "inputs/2024/day04", day04_2024::Day04);
define_solution!(args, 2024, 5, "inputs/2024/day05", day05_2024::Day05);
// AUTOGENERATED SOLUTIONS END

println!("no solution found for year {}, day {}", args.year, args.day);
Expand Down

0 comments on commit fc90286

Please sign in to comment.