rust: move section ranges to library
This commit is contained in:
parent
1207b26a46
commit
9b62590517
7 changed files with 212 additions and 51 deletions
|
@ -6,3 +6,4 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
aoc = { path = "../../../common/rust" }
|
||||||
|
|
|
@ -1,42 +1,8 @@
|
||||||
#![warn(clippy::pedantic)]
|
#![warn(clippy::pedantic)]
|
||||||
#![feature(is_some_and)]
|
|
||||||
|
|
||||||
use std::{
|
use std::io::{stdin, Read};
|
||||||
io::{stdin, Read},
|
|
||||||
ops::{BitAnd, RangeInclusive},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A range of sections. Always contains at least one element.
|
use aoc::SectionRange;
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
struct SectionRange {
|
|
||||||
start: u64,
|
|
||||||
end: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<RangeInclusive<u64>> for SectionRange {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(range: RangeInclusive<u64>) -> Result<Self, Self::Error> {
|
|
||||||
let start = *range.start();
|
|
||||||
let end = *range.end();
|
|
||||||
if start <= end {
|
|
||||||
Ok(Self { start, end })
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitAnd for SectionRange {
|
|
||||||
type Output = Option<Self>;
|
|
||||||
|
|
||||||
fn bitand(self, other: Self) -> Self::Output {
|
|
||||||
let start = self.start.max(other.start);
|
|
||||||
let end = self.end.min(other.end);
|
|
||||||
|
|
||||||
Self::try_from(start..=end).ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
|
@ -44,19 +10,10 @@ fn main() {
|
||||||
|
|
||||||
let pairs: Vec<_> = data
|
let pairs: Vec<_> = data
|
||||||
.lines()
|
.lines()
|
||||||
.map(|line| {
|
.map(|line| -> (SectionRange, SectionRange) {
|
||||||
let make_range = |s: &str| {
|
|
||||||
let (start, end) = s.split_once('-').unwrap();
|
|
||||||
|
|
||||||
let start = start.parse().unwrap();
|
|
||||||
let end = end.parse().unwrap();
|
|
||||||
|
|
||||||
SectionRange::try_from(start..=end).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (left, right) = line.split_once(',').unwrap();
|
let (left, right) = line.split_once(',').unwrap();
|
||||||
let left = make_range(left);
|
let left = left.parse().unwrap();
|
||||||
let right = make_range(right);
|
let right = right.parse().unwrap();
|
||||||
|
|
||||||
(left, right)
|
(left, right)
|
||||||
})
|
})
|
||||||
|
@ -66,9 +23,7 @@ fn main() {
|
||||||
"{}",
|
"{}",
|
||||||
pairs
|
pairs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&&(l, r)| {
|
.filter(|&&(l, r)| l.encompasses(&r) || r.encompasses(&l))
|
||||||
(l & r).is_some_and(|intersection| intersection == l || intersection == r)
|
|
||||||
})
|
|
||||||
.count()
|
.count()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -2,6 +2,10 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aoc"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -476,6 +480,9 @@ version = "0.1.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust_2022_04"
|
name = "rust_2022_04"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"aoc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"common/rust",
|
||||||
"2021/day1/rust",
|
"2021/day1/rust",
|
||||||
"2021/day3/rust",
|
"2021/day3/rust",
|
||||||
"2021/day5/rust",
|
"2021/day5/rust",
|
||||||
|
|
8
common/rust/Cargo.toml
Normal file
8
common/rust/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "aoc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
5
common/rust/src/lib.rs
Normal file
5
common/rust/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#![warn(clippy::pedantic)]
|
||||||
|
|
||||||
|
mod section_range;
|
||||||
|
|
||||||
|
pub use section_range::{EmptyRange, InvalidSectionString, SectionRange};
|
184
common/rust/src/section_range.rs
Normal file
184
common/rust/src/section_range.rs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt,
|
||||||
|
num::NonZeroU64,
|
||||||
|
ops::{BitAnd, RangeInclusive},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Error returned when an attempt is made to construct an empty `SectionRange`.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct EmptyRange;
|
||||||
|
|
||||||
|
impl fmt::Display for EmptyRange {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Range is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for EmptyRange {}
|
||||||
|
|
||||||
|
/// Error returned when a malformed string is attempted to be parsed as a `SectionRange`.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct InvalidSectionString(String);
|
||||||
|
|
||||||
|
impl fmt::Display for InvalidSectionString {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Invalid section range: {}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for InvalidSectionString {}
|
||||||
|
|
||||||
|
/// A range of sections. Always contains at least one element.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct SectionRange {
|
||||||
|
start: u64,
|
||||||
|
end: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SectionRange {
|
||||||
|
/// Constructs a new section range from a start section and an end section, both inclusive.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the range would be empty, i.e. `start > end`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use aoc::{EmptyRange, SectionRange};
|
||||||
|
/// let range = SectionRange::try_new(3, 10);
|
||||||
|
/// assert!(range.is_ok());
|
||||||
|
///
|
||||||
|
/// let range = SectionRange::try_new(3, 2);
|
||||||
|
/// assert_eq!(range, Err(EmptyRange));
|
||||||
|
/// ```
|
||||||
|
pub fn try_new(start: u64, end: u64) -> Result<Self, EmptyRange> {
|
||||||
|
if start <= end {
|
||||||
|
Ok(Self { start, end })
|
||||||
|
} else {
|
||||||
|
Err(EmptyRange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of sections contained by the range. Since the range always contains at
|
||||||
|
/// least one element, the length is never zero.
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn len(&self) -> NonZeroU64 {
|
||||||
|
debug_assert!(self.start <= self.end);
|
||||||
|
NonZeroU64::new(self.end - self.start + 1).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the range contains the given section.
|
||||||
|
#[must_use]
|
||||||
|
pub fn contains(&self, section: u64) -> bool {
|
||||||
|
debug_assert!(self.start <= self.end);
|
||||||
|
section >= self.start && section <= self.end
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the range contains the entirety of `other`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn encompasses(&self, other: &SectionRange) -> bool {
|
||||||
|
let Some(intersection) = *self & *other else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
intersection == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SectionRange {
|
||||||
|
type Err = InvalidSectionString;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
// poor man's try block
|
||||||
|
fn inner(s: &str) -> Option<SectionRange> {
|
||||||
|
let (start, end) = s.split_once('-')?;
|
||||||
|
|
||||||
|
let start = start.parse().ok()?;
|
||||||
|
let end = end.parse().ok()?;
|
||||||
|
|
||||||
|
SectionRange::try_from(start..=end).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner(s).ok_or_else(|| InvalidSectionString(s.to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SectionRange> for RangeInclusive<u64> {
|
||||||
|
fn from(r: SectionRange) -> Self {
|
||||||
|
r.start..=r.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<RangeInclusive<u64>> for SectionRange {
|
||||||
|
type Error = EmptyRange;
|
||||||
|
|
||||||
|
fn try_from(range: RangeInclusive<u64>) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_new(*range.start(), *range.end())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitAnd for SectionRange {
|
||||||
|
type Output = Option<Self>;
|
||||||
|
|
||||||
|
fn bitand(self, other: Self) -> Self::Output {
|
||||||
|
let start = self.start.max(other.start);
|
||||||
|
let end = self.end.min(other.end);
|
||||||
|
|
||||||
|
Self::try_from(start..=end).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn section_range_construction() {
|
||||||
|
let range = SectionRange::try_from(3..=10).unwrap();
|
||||||
|
assert_eq!(range, SectionRange { start: 3, end: 10 });
|
||||||
|
assert_eq!(range.len().get(), 8);
|
||||||
|
|
||||||
|
let range = SectionRange::try_from(3..=3).unwrap();
|
||||||
|
assert_eq!(range, SectionRange { start: 3, end: 3 });
|
||||||
|
assert_eq!(range.len().get(), 1);
|
||||||
|
|
||||||
|
#[allow(clippy::reversed_empty_ranges)]
|
||||||
|
let range = SectionRange::try_from(3..=2);
|
||||||
|
assert_eq!(range, Err(EmptyRange));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn section_range_intersection() {
|
||||||
|
fn check_intersection(a: SectionRange, b: SectionRange, expected: Option<SectionRange>) {
|
||||||
|
let x = a & b;
|
||||||
|
let y = b & a;
|
||||||
|
|
||||||
|
assert_eq!(x, y, "intersection not commutative");
|
||||||
|
assert_eq!(x, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
let range1 = SectionRange::try_from(3..=5).unwrap();
|
||||||
|
let range2 = SectionRange::try_from(4..=10).unwrap();
|
||||||
|
let range3 = SectionRange::try_from(6..=9).unwrap();
|
||||||
|
|
||||||
|
check_intersection(range1, range2, Some(SectionRange { start: 4, end: 5 }));
|
||||||
|
check_intersection(range1, range3, None);
|
||||||
|
check_intersection(range2, range3, Some(range3));
|
||||||
|
|
||||||
|
assert!(range1.encompasses(&range1));
|
||||||
|
assert!(range2.encompasses(&range2));
|
||||||
|
assert!(range3.encompasses(&range3));
|
||||||
|
|
||||||
|
assert!(!range1.encompasses(&range2));
|
||||||
|
assert!(!range1.encompasses(&range3));
|
||||||
|
|
||||||
|
assert!(!range2.encompasses(&range1));
|
||||||
|
assert!(range2.encompasses(&range3));
|
||||||
|
|
||||||
|
assert!(!range3.encompasses(&range1));
|
||||||
|
assert!(!range3.encompasses(&range2));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue