#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct CountAccumulator {
buffer: u32,
}
impl CountAccumulator {
pub const fn new() -> Self {
Self { buffer: 0 }
}
pub const fn is_empty(&self) -> bool {
self.buffer == 0
}
pub const fn peek(&self) -> u32 {
self.buffer
}
pub fn try_accumulate(&mut self, ch: char) -> bool {
if !ch.is_ascii_digit() {
return false;
}
if ch == '0' && self.buffer == 0 {
return false;
}
let d = (ch as u8 - b'0') as u32;
self.buffer = self.buffer.saturating_mul(10).saturating_add(d);
true
}
pub fn take_or(&mut self, default: u32) -> u32 {
let c = if self.buffer == 0 {
default
} else {
self.buffer
};
self.buffer = 0;
c
}
pub fn reset(&mut self) {
self.buffer = 0;
}
pub fn drain_as_digits(&mut self) -> String {
let s = if self.buffer == 0 {
String::new()
} else {
self.buffer.to_string()
};
self.buffer = 0;
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_is_empty() {
let acc = CountAccumulator::new();
assert!(acc.is_empty());
assert_eq!(acc.peek(), 0);
}
#[test]
fn try_accumulate_digit_increments() {
let mut acc = CountAccumulator::new();
assert!(acc.try_accumulate('5'));
assert_eq!(acc.peek(), 5);
assert!(!acc.is_empty());
}
#[test]
fn try_accumulate_zero_with_empty_buffer_returns_false() {
let mut acc = CountAccumulator::new();
assert!(!acc.try_accumulate('0'));
assert!(acc.is_empty());
}
#[test]
fn try_accumulate_zero_with_non_empty_buffer_appends() {
let mut acc = CountAccumulator::new();
assert!(acc.try_accumulate('1'));
assert!(acc.try_accumulate('0'));
assert_eq!(acc.peek(), 10);
}
#[test]
fn try_accumulate_non_digit_returns_false() {
let mut acc = CountAccumulator::new();
assert!(!acc.try_accumulate('j'));
assert!(!acc.try_accumulate(' '));
assert!(!acc.try_accumulate('g'));
assert!(acc.is_empty());
}
#[test]
fn take_or_drains_and_returns_count() {
let mut acc = CountAccumulator::new();
acc.try_accumulate('5');
assert_eq!(acc.take_or(1), 5);
assert!(acc.is_empty());
assert_eq!(acc.take_or(1), 1);
}
#[test]
fn take_or_returns_default_when_empty() {
let mut acc = CountAccumulator::new();
assert_eq!(acc.take_or(1), 1);
assert_eq!(acc.take_or(42), 42);
}
#[test]
fn drain_as_digits_returns_typed_chars_in_order() {
let mut acc = CountAccumulator::new();
acc.try_accumulate('1');
acc.try_accumulate('2');
acc.try_accumulate('3');
let s = acc.drain_as_digits();
assert_eq!(s, "123");
assert!(acc.is_empty());
}
#[test]
fn drain_as_digits_empty_returns_empty_string() {
let mut acc = CountAccumulator::new();
let s = acc.drain_as_digits();
assert_eq!(s, "");
}
#[test]
fn try_accumulate_saturates_on_overflow() {
let mut acc = CountAccumulator::new();
for _ in 0..20 {
acc.try_accumulate('9');
}
assert_eq!(acc.peek(), u32::MAX);
}
#[test]
fn reset_clears_without_returning() {
let mut acc = CountAccumulator::new();
acc.try_accumulate('7');
assert!(!acc.is_empty());
acc.reset();
assert!(acc.is_empty());
assert_eq!(acc.peek(), 0);
}
}