common/rust: make SectionRange generic over content

This commit is contained in:
Xiretza 2022-12-15 21:17:35 +01:00
parent 633591acf8
commit 146e0d594c
4 changed files with 50 additions and 38 deletions

View file

@ -10,7 +10,7 @@ fn main() {
let pairs: Vec<_> = data let pairs: Vec<_> = data
.lines() .lines()
.map(|line| -> (SectionRange, SectionRange) { .map(|line| -> (SectionRange<u64>, SectionRange<u64>) {
let (left, right) = line.split_once(',').unwrap(); let (left, right) = line.split_once(',').unwrap();
let left = left.parse().unwrap(); let left = left.parse().unwrap();
let right = right.parse().unwrap(); let right = right.parse().unwrap();

3
Cargo.lock generated
View file

@ -14,6 +14,9 @@ dependencies = [
[[package]] [[package]]
name = "aoc" name = "aoc"
version = "0.1.0" version = "0.1.0"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"

View file

@ -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]
num-traits = "0.2.15"

View file

@ -1,11 +1,13 @@
use std::{ use std::{
error::Error, error::Error,
fmt, fmt,
num::NonZeroU64, num::NonZeroUsize,
ops::{BitAnd, BitOr, RangeInclusive, Sub}, ops::{Add, BitAnd, BitOr, RangeInclusive, Sub},
str::FromStr, str::FromStr,
}; };
use num_traits::{one, One};
use crate::UpToTwo; use crate::UpToTwo;
/// Error returned when an attempt is made to construct an empty `SectionRange`. /// Error returned when an attempt is made to construct an empty `SectionRange`.
@ -34,12 +36,12 @@ impl Error for InvalidSectionString {}
/// A range of sections. Always contains at least one element. /// A range of sections. Always contains at least one element.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SectionRange { pub struct SectionRange<T> {
start: u64, start: T,
end: u64, end: T,
} }
impl SectionRange { impl<T: Ord + Copy> SectionRange<T> {
/// Constructs a new section range from a start section and an end section, both inclusive. /// Constructs a new section range from a start section and an end section, both inclusive.
/// ///
/// # Errors /// # Errors
@ -56,7 +58,7 @@ impl SectionRange {
/// let range = SectionRange::try_new(3, 2); /// let range = SectionRange::try_new(3, 2);
/// assert_eq!(range, Err(EmptyRange)); /// assert_eq!(range, Err(EmptyRange));
/// ``` /// ```
pub fn try_new(start: u64, end: u64) -> Result<Self, EmptyRange> { pub fn try_new(start: T, end: T) -> Result<Self, EmptyRange> {
if start <= end { if start <= end {
Ok(Self { start, end }) Ok(Self { start, end })
} else { } else {
@ -64,25 +66,16 @@ impl SectionRange {
} }
} }
/// 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. /// Returns true if and only if the range contains the given section.
#[must_use] #[must_use]
pub fn contains(&self, section: u64) -> bool { pub fn contains(&self, section: T) -> bool {
debug_assert!(self.start <= self.end); debug_assert!(self.start <= self.end);
section >= self.start && section <= self.end section >= self.start && section <= self.end
} }
/// Returns true if and only if the range contains the entirety of `other`. /// Returns true if and only if the range contains the entirety of `other`.
#[must_use] #[must_use]
pub fn encompasses(&self, other: &SectionRange) -> bool { pub fn encompasses(&self, other: &Self) -> bool {
let Some(intersection) = *self & *other else { let Some(intersection) = *self & *other else {
return false; return false;
}; };
@ -90,12 +83,23 @@ impl SectionRange {
} }
} }
impl FromStr for SectionRange { impl<T: Ord + Copy + Sub<Output = L>, L: TryInto<usize>> SectionRange<T> {
/// 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) -> NonZeroUsize {
debug_assert!(self.start <= self.end);
NonZeroUsize::new((self.end - self.start).try_into().ok().unwrap() + 1).unwrap()
}
}
impl<T: Ord + Copy + FromStr> FromStr for SectionRange<T> {
type Err = InvalidSectionString; type Err = InvalidSectionString;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
// poor man's try block // poor man's try block
fn inner(s: &str) -> Option<SectionRange> { fn inner<T: Ord + Copy + FromStr>(s: &str) -> Option<SectionRange<T>> {
let (start, end) = s.split_once('-')?; let (start, end) = s.split_once('-')?;
let start = start.parse().ok()?; let start = start.parse().ok()?;
@ -108,21 +112,21 @@ impl FromStr for SectionRange {
} }
} }
impl From<SectionRange> for RangeInclusive<u64> { impl<T> From<SectionRange<T>> for RangeInclusive<T> {
fn from(r: SectionRange) -> Self { fn from(r: SectionRange<T>) -> Self {
r.start..=r.end r.start..=r.end
} }
} }
impl TryFrom<RangeInclusive<u64>> for SectionRange { impl<T: Ord + Copy> TryFrom<RangeInclusive<T>> for SectionRange<T> {
type Error = EmptyRange; type Error = EmptyRange;
fn try_from(range: RangeInclusive<u64>) -> Result<Self, Self::Error> { fn try_from(range: RangeInclusive<T>) -> Result<Self, Self::Error> {
Self::try_new(*range.start(), *range.end()) Self::try_new(*range.start(), *range.end())
} }
} }
impl BitAnd for SectionRange { impl<T: Ord + Copy> BitAnd for SectionRange<T> {
type Output = Option<Self>; type Output = Option<Self>;
fn bitand(self, other: Self) -> Self::Output { fn bitand(self, other: Self) -> Self::Output {
@ -133,15 +137,15 @@ impl BitAnd for SectionRange {
} }
} }
impl BitOr for SectionRange { impl<T: Ord + Copy> BitOr for SectionRange<T> {
type Output = UpToTwo<Self>; type Output = UpToTwo<Self>;
fn bitor(self, other: Self) -> Self::Output { fn bitor(self, other: Self) -> Self::Output {
let first_start = u64::min(self.start, other.start); let first_start = T::min(self.start, other.start);
let first_end = u64::min(self.end, other.end); let first_end = T::min(self.end, other.end);
let second_start = u64::max(self.start, other.start); let second_start = T::max(self.start, other.start);
let second_end = u64::max(self.end, other.end); let second_end = T::max(self.end, other.end);
if first_end < second_start { if first_end < second_start {
let first = Self { let first = Self {
@ -162,7 +166,7 @@ impl BitOr for SectionRange {
} }
} }
impl Sub for SectionRange { impl<T: Ord + Copy + Sub<Output = T> + Add<Output = T> + One> Sub for SectionRange<T> {
type Output = UpToTwo<Self>; type Output = UpToTwo<Self>;
fn sub(self, other: Self) -> Self::Output { fn sub(self, other: Self) -> Self::Output {
@ -173,10 +177,10 @@ impl Sub for SectionRange {
// Closures to prevent integer overflow // Closures to prevent integer overflow
let first = || Self { let first = || Self {
start: self.start, start: self.start,
end: u64::min(self.end, other.start - 1), end: T::min(self.end, other.start - one()),
}; };
let second = || Self { let second = || Self {
start: u64::max(self.start, other.end + 1), start: T::max(self.start, other.end + one()),
end: self.end, end: self.end,
}; };
@ -213,7 +217,11 @@ mod tests {
#[test] #[test]
fn section_range_intersection() { fn section_range_intersection() {
fn check_intersection(a: SectionRange, b: SectionRange, expected: Option<SectionRange>) { fn check_intersection(
a: SectionRange<u64>,
b: SectionRange<u64>,
expected: Option<SectionRange<u64>>,
) {
let x = a & b; let x = a & b;
let y = b & a; let y = b & a;
@ -247,11 +255,11 @@ mod tests {
#[test] #[test]
fn section_range_operations() { fn section_range_operations() {
fn elements(r: SectionRange) -> Vec<u64> { fn elements(r: SectionRange<u64>) -> Vec<u64> {
RangeInclusive::<_>::from(r).collect() RangeInclusive::<_>::from(r).collect()
} }
fn elements_multi(r: UpToTwo<SectionRange>) -> Vec<u64> { fn elements_multi(r: UpToTwo<SectionRange<u64>>) -> Vec<u64> {
let is_empty = r.is_empty(); let is_empty = r.is_empty();
let sections: Vec<_> = r.into_iter().flat_map(elements).collect(); let sections: Vec<_> = r.into_iter().flat_map(elements).collect();
@ -288,7 +296,7 @@ mod tests {
// len() // len()
assert_eq!( assert_eq!(
left.len().get(), left.len().get(),
*range_left.end() - *range_left.start() + 1 usize::try_from(*range_left.end() - *range_left.start() + 1).unwrap()
); );
// contains() // contains()