use std::sync::LazyLock;
use regex::Regex;
static CONSECUTIVE_SPACES: LazyLock<Regex> = LazyLock::new(|| Regex::new(r" +").unwrap());
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum NewlineMode {
Delete,
ReplaceWithSpace,
}
fn normalize_whitespace(s: &str, mode: NewlineMode) -> String {
let mut buf = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'\t' => buf.push(' '),
'\n' | '\r' => match mode {
NewlineMode::Delete => {}
NewlineMode::ReplaceWithSpace => buf.push(' '),
},
other => buf.push(other),
}
}
CONSECUTIVE_SPACES.replace_all(&buf, " ").into_owned()
}
pub fn clean_spaces(s: &str) -> String {
normalize_whitespace(s, NewlineMode::Delete)
}
pub fn clean_whitespace(s: &str) -> String {
normalize_whitespace(s, NewlineMode::ReplaceWithSpace)
}
pub fn flatten<T>(nested: impl IntoIterator<Item = impl IntoIterator<Item = T>>) -> Vec<T> {
nested.into_iter().flatten().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clean_spaces_basic() {
assert_eq!(clean_spaces("hello world"), "hello world");
assert_eq!(clean_spaces("a\tb\nc\rd"), "a bcd");
assert_eq!(clean_spaces(" lots of space "), " lots of space ");
}
#[test]
fn clean_spaces_empty() {
assert_eq!(clean_spaces(""), "");
assert_eq!(clean_spaces("\n\r\t"), " ");
}
#[test]
fn clean_whitespace_basic() {
assert_eq!(clean_whitespace("a\tb\nc\rd"), "a b c d");
assert_eq!(clean_whitespace("hello\t\tworld\n\nfoo"), "hello world foo");
}
#[test]
fn clean_spaces_idempotent() {
let input = "hello\t\tworld\n\nfoo";
let once = clean_spaces(input);
let twice = clean_spaces(&once);
assert_eq!(once, twice);
}
#[test]
fn flatten_vecs() {
let nested = vec![vec![1, 2], vec![3], vec![4, 5, 6]];
assert_eq!(flatten(nested), vec![1, 2, 3, 4, 5, 6]);
}
#[test]
fn flatten_empty() {
let nested: Vec<Vec<i32>> = vec![vec![], vec![]];
assert_eq!(flatten(nested), Vec::<i32>::new());
}
}