use std::{env::args, io::Read}; #[derive(Debug, PartialEq)] struct JumpTable { jumps: Vec, } impl JumpTable { fn resolve_char( pat: &Vec, ptr: &usize, cur_char: &char, ) -> usize { if pat.get(*ptr).unwrap() == cur_char { return *ptr + 1; } if pat.get(0).unwrap() == cur_char { return 1; } return 0; } pub fn new(pat: &Vec) -> Self { if pat.len() == 0 { return Self { jumps: vec![] }; } let mut ptr = 0_usize; let mut jumps = vec![0]; for cur_char in pat.iter().skip(1) { ptr = JumpTable::resolve_char(pat, &ptr, cur_char); jumps.push(ptr); } Self { jumps } } pub fn search(&self, pat: &Vec, needle: &char, ptr: &usize) -> usize { let mut ptr = *ptr; while ptr != 0 { if pat.get(ptr).unwrap() == needle { break; } ptr = *self.jumps.get(ptr.saturating_sub(1)).unwrap(); } ptr } } #[derive(Debug, PartialEq)] struct Pattern { pat: Vec, jt: JumpTable, ptr: usize, } impl Pattern { pub fn new(pat: String) -> Result { if pat.len() == 0 { return Err(()); } let pat = pat.chars().collect::>(); let jt = JumpTable::new(&pat); Ok(Self { pat, jt, ptr: 0, }) } pub fn search_and_update(&mut self, cur_char: &char) -> bool { if self.pat.get(self.ptr).unwrap() == cur_char { self.ptr += 1; return if self.ptr == self.pat.len() { true } else { false }; } let found = self.jt.search(&self.pat, &cur_char, &self.ptr); self.ptr = found; false } pub fn reset_ptr(&mut self) { self.ptr = 0; } pub fn len(&self) -> usize { self.pat.len() } } #[derive(Debug, PartialEq, Eq)] struct Match { pub pat_id: usize, pub start_pos: usize, pub len: usize, } impl Match { pub fn new(pat_id: usize, start_pos: usize, len: usize) -> Self { Self { pat_id, start_pos, len } } } #[derive(Debug)] struct MPSearch { pats: Vec, } impl MPSearch { pub fn new(pats: Vec) -> Self { Self { pats } } fn reset_all_ptrs(&mut self) { for pat in self.pats.iter_mut() { pat.reset_ptr(); } } fn search_at_pos(&mut self, cur_char: &char, pos: &usize) -> Option { let mut cur_match = None; for (idx, pat) in self.pats.iter_mut().enumerate() { if pat.search_and_update(cur_char) { cur_match = Some(Match::new(idx, pos.saturating_sub(pat.len() - 1), pat.len())); break; } } if let Some(_) = cur_match { self.reset_all_ptrs(); } cur_match } pub fn search(&mut self, haystack: &String) -> Vec { let mut res = Vec::new(); for (cur_idx, cur_char) in haystack.chars().enumerate() { if let Some(cur_match) = self.search_at_pos(&cur_char, &cur_idx) { res.push(cur_match); } } res } } macro_rules! skip_n { ($iter: ident, $n: expr) => { for _ in 0..$n { $iter.next().ok_or(())?; } } } macro_rules! append_n { ($iter: ident, $target: ident, $n: expr) => { for _ in 0..$n { $target.push($iter.next().ok_or(())?); } } } #[derive(Debug)] struct MPReplace { replacements: Vec, matches: Vec, } impl MPReplace { pub fn new(matches: Vec, replacements: Vec) -> Self { Self { replacements, matches } } pub fn replace(&self, target: &String) -> Result { let mut ptr = 0; let mut iter = target.chars(); let mut res = String::new(); for cur_match in self.matches.iter() { append_n!(iter, res, cur_match.start_pos - ptr); skip_n!(iter, cur_match.len); res.push_str(self.replacements.get(cur_match.pat_id).ok_or(())?); ptr = cur_match.start_pos + cur_match.len; } append_n!(iter, res, target.chars().count() - ptr); Ok(res) } } struct PatMatchArgs> { inner: I, } impl> PatMatchArgs { pub fn new(inner: I, count: usize) -> Result { if count % 2 != 0 { return Err(()); } Ok(Self { inner }) } } impl> Iterator for PatMatchArgs { type Item = (String, String); fn next(&mut self) -> Option { Some((self.inner.next()?, self.inner.next()?)) } } fn main() { let mut contents = Vec::new(); std::io::stdin().lock().read_to_end(&mut contents).unwrap(); let contents = std::str::from_utf8(&contents).unwrap().to_string(); let args = match PatMatchArgs::new(args().skip(1), args().len() - 1) { Ok(val) => val, Err(()) => { eprintln!("the number of arguments after filepath must be divisible by two"); std::process::exit(1); } }; let mut pats = Vec::new(); let mut replacements = Vec::new(); for (cur_pat, cur_replacement) in args { let Ok(cur_pat) = Pattern::new(cur_pat) else { eprintln!("the patterns can't be empty"); std::process::exit(1); }; pats.push(cur_pat); replacements.push(cur_replacement); } let matches = MPSearch::new(pats).search(&contents); let replaced = MPReplace::new(matches, replacements) .replace(&contents).unwrap(); println!("{}", replaced); } #[cfg(test)] mod test { use crate::{JumpTable, MPReplace, MPSearch, Match, Pattern}; #[test] fn jumps_01() { let src = String::from("thisthen").chars().collect::>(); let target = vec![0_usize, 0, 0, 0, 1, 2, 0, 0]; let jt = JumpTable::new(&src); assert_eq!(jt.jumps, target); } #[test] fn jumps_02() { let src = String::from("tthis").chars().collect::>(); let target = vec![0, 1, 0, 0, 0]; let jt = JumpTable::new(&src); assert_eq!(jt.jumps, target); } #[test] fn jumps_03() { let src = String::from("t").chars().collect::>(); let target = vec![0]; let jt = JumpTable::new(&src); assert_eq!(jt.jumps, target); } #[test] fn jumps_04() { let src = String::from("tt").chars().collect::>(); let target = vec![0, 1]; let jt = JumpTable::new(&src); assert_eq!(jt.jumps, target); } #[test] fn jumps_05() { let src = String::from("").chars().collect::>(); let target = vec![]; let jt = JumpTable::new(&src); assert_eq!(jt.jumps, target); } #[test] fn search_01() { let pat = String::from("tthis").chars().collect::>(); let jt = JumpTable::new(&pat); assert_eq!(jt.search(&pat, &'t', &1), 1); } #[test] fn search_02() { let pat = String::from("testtesa").chars().collect::>(); let jt = JumpTable::new(&pat); assert_eq!(jt.search(&pat, &'t', &7), 3); } #[test] fn search_03() { let pat = String::from("ahojahoj").chars().collect::>(); let jt = JumpTable::new(&pat); assert_eq!(jt.search(&pat, &'j', &7), 7); } #[test] fn search_04() { let pat = String::from("ahojahoj").chars().collect::>(); let jt = JumpTable::new(&pat); assert_eq!(jt.search(&pat, &'j', &7), 7); } #[test] fn search_05() { let pat = String::from("jojojojojojoja").chars().collect::>(); let jt = JumpTable::new(&pat); assert_eq!(jt.search(&pat, &'o', &13), 11); } #[test] fn search_and_update_01() { let pat = String::from("test"); let mut pat = Pattern::new(pat).unwrap(); let haystack = String::from("thisisatest"); for cur_haystack in haystack.chars().take(haystack.len() - 1) { assert_eq!(pat.search_and_update(&cur_haystack), false); println!("{:?}", pat); } println!("{:?}", pat); assert_eq!(pat.search_and_update(&'t'), true); } #[test] fn empty_pattern() { assert_eq!(Pattern::new("".chars().collect()), Err(())); } #[test] fn mpsearch_01() { let mut mpsearch = MPSearch::new( vec!["this", "is", "a", "test"] .iter() .map(|p| Pattern::new(p.to_string()).unwrap()) .collect() ); println!("{:?}", mpsearch); let haystack = String::from("this is a test"); let target = vec![ Match::new(0, 0, 4), Match::new(1, 5, 2), Match::new(2, 8, 1), Match::new(3, 10, 4) ]; assert_eq!(mpsearch.search(&haystack), target); } #[test] fn mpreplace_01() { let mut mpsearch = MPSearch::new( vec!["this", "is", "a", "test"] .iter() .map(|p| Pattern::new(p.to_string()).unwrap()) .collect() ); let haystack = String::from("this-is.a*test///"); let matches = mpsearch.search(&haystack); let mpreplace = MPReplace::new(matches, vec![ "that".to_string(), "isn't".to_string(), "the".to_string(), "final".to_string(), ]); let replaced = mpreplace.replace(&haystack).unwrap(); assert_eq!(replaced, "that-isn't.the*final///"); } }