1use std::borrow::Cow;
2
3use ascii::AsciiChar;
4
5fn remove_ascii_from_str(s: &str, ch: AsciiChar) -> Option<String> {
7 let target_byte = ch.as_byte();
8 let bytes = s.as_bytes();
9
10 if !bytes.contains(&target_byte) {
12 return None;
13 }
14
15 let mut result = String::with_capacity(s.len());
17 for &byte in bytes {
18 if byte != target_byte {
19 result.push(byte as char);
20 }
21 }
22
23 Some(result)
24}
25
26fn replace_str_if_contains(s: &str, from: &str, to: &str) -> Option<String> {
27 if from.is_empty() || !s.contains(from) {
28 return None;
29 }
30
31 Some(s.replace(from, to))
32}
33
34pub trait ReplaceString {
40 fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str>;
73
74 fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str>;
105}
106
107pub trait ReplaceStringInPlace {
113 fn remove_all_ascii_in_place(&mut self, ch: AsciiChar);
140
141 fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar);
167}
168
169impl<T: AsRef<str>> ReplaceString for T {
170 fn remove_all_ascii(&self, ch: AsciiChar) -> Cow<'_, str> {
171 match remove_ascii_from_str(self.as_ref(), ch) {
172 Some(result) => Cow::Owned(result),
173 None => Cow::Borrowed(self.as_ref()),
174 }
175 }
176
177 fn replace_all_str(&self, from: &str, to: &str) -> Cow<'_, str> {
178 match replace_str_if_contains(self.as_ref(), from, to) {
179 Some(result) => Cow::Owned(result),
180 None => Cow::Borrowed(self.as_ref()),
181 }
182 }
183}
184impl ReplaceStringInPlace for String {
185 fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) {
186 let target_byte = ch.as_byte();
187 let bytes = unsafe { self.as_bytes_mut() };
188
189 let mut write_pos = 0;
190 let mut read_pos = 0;
191
192 while read_pos < bytes.len() {
193 if bytes[read_pos] != target_byte {
194 bytes[write_pos] = bytes[read_pos];
195 write_pos += 1;
196 }
197 read_pos += 1;
198 }
199
200 self.truncate(write_pos);
202 }
203
204 fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar) {
205 let from_byte = from.as_byte();
206 let to_byte = to.as_byte();
207 let bytes = unsafe { self.as_bytes_mut() };
208
209 for byte in bytes {
210 if *byte == from_byte {
211 *byte = to_byte;
212 }
213 }
214 }
215}
216
217impl ReplaceStringInPlace for Cow<'_, str> {
218 fn remove_all_ascii_in_place(&mut self, ch: AsciiChar) {
219 match self {
220 Cow::Borrowed(s) => {
221 if let Some(result) = remove_ascii_from_str(s, ch) {
222 *self = Cow::Owned(result);
223 }
224 }
225 Cow::Owned(s) => {
226 s.remove_all_ascii_in_place(ch);
227 }
228 }
229 }
230
231 fn replace_all_ascii_in_place(&mut self, from: AsciiChar, to: AsciiChar) {
232 match self {
233 Cow::Borrowed(s) => {
234 let from_byte = from.as_byte();
235 let bytes = s.as_bytes();
236
237 if !bytes.contains(&from_byte) {
238 return; }
240
241 let mut owned = s.to_string();
243 owned.replace_all_ascii_in_place(from, to);
244 *self = Cow::Owned(owned);
245 }
246 Cow::Owned(s) => {
247 s.replace_all_ascii_in_place(from, to);
248 }
249 }
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use std::borrow::Cow;
256
257 use super::*;
258
259 #[test]
260 fn test_str_remove_all_ascii() {
261 let s = "hello world";
262 let result = s.remove_all_ascii(AsciiChar::l);
263 assert_eq!(result, "heo word");
264
265 let s = "hello world";
267 let result = s.remove_all_ascii(AsciiChar::z);
268 assert_eq!(result, "hello world");
269 match result {
270 Cow::Borrowed(_) => {}
271 Cow::Owned(_) => panic!("Should return borrowed when no changes"),
272 }
273 }
274
275 #[test]
276 fn test_str_replace_all_str() {
277 let s = "hello world hello";
278 let result = s.replace_all_str("hello", "hi");
279 assert_eq!(result, "hi world hi");
280
281 let s = "hello world";
283 let result = s.replace_all_str("xyz", "abc");
284 match result {
285 Cow::Borrowed(_) => {}
286 Cow::Owned(_) => panic!("Should return borrowed when no changes"),
287 }
288 assert_eq!(result, "hello world");
289 }
290
291 #[test]
292 fn test_string_remove_all_ascii() {
293 let s = "hello world".to_string();
294 let result = s.remove_all_ascii(AsciiChar::l);
295 assert_eq!(result, "heo word");
296
297 let s = "hello world".to_string();
299 let result = s.remove_all_ascii(AsciiChar::z);
300 assert_eq!(result, "hello world");
301 match result {
302 Cow::Borrowed(_) => {}
303 Cow::Owned(_) => panic!("Should return borrowed when no changes"),
304 }
305 }
306
307 #[test]
308 fn test_string_remove_all_ascii_in_place() {
309 let mut s = "hello world".to_string();
310 s.remove_all_ascii_in_place(AsciiChar::l);
311 assert_eq!(s, "heo word");
312
313 let mut s = "aaaaaa".to_string();
314 s.remove_all_ascii_in_place(AsciiChar::a);
315 assert_eq!(s, "");
316 }
317
318 #[test]
319 fn test_string_replace_all_ascii_in_place() {
320 let mut s = "hello world".to_string();
321 s.replace_all_ascii_in_place(AsciiChar::l, AsciiChar::x);
322 assert_eq!(s, "hexxo worxd");
323
324 let mut s = "hello world".to_string();
325 s.replace_all_ascii_in_place(AsciiChar::z, AsciiChar::x);
326 assert_eq!(s, "hello world");
327 }
328
329 #[test]
330 fn test_string_replace_all_str() {
331 let s = "hello world hello".to_string();
332 let result = s.replace_all_str("hello", "hi");
333 assert_eq!(result, "hi world hi");
334 assert_eq!(s, "hello world hello"); let s = "hello world".to_string();
338 let result = s.replace_all_str("xyz", "abc");
339 match result {
340 Cow::Borrowed(_) => {}
341 Cow::Owned(_) => panic!("Should return borrowed when no changes"),
342 }
343 assert_eq!(result, "hello world");
344 assert_eq!(s, "hello world");
345 }
346
347 #[test]
348 fn test_cow_remove_all_ascii() {
349 let s: Cow<'_, str> = Cow::Borrowed("hello world");
350 let result = s.remove_all_ascii(AsciiChar::l);
351 assert_eq!(result, "heo word");
352
353 let s: Cow<'_, str> = Cow::Owned("hello world".to_string());
354 let result = s.remove_all_ascii(AsciiChar::l);
355 assert_eq!(result, "heo word");
356 }
357
358 #[test]
359 fn test_cow_remove_all_ascii_in_place() {
360 let mut s: Cow<'_, str> = Cow::Borrowed("hello world");
361 s.remove_all_ascii_in_place(AsciiChar::l);
362 assert_eq!(s, "heo word");
363 match s {
364 Cow::Owned(_) => {}
365 Cow::Borrowed(_) => panic!("Should be owned after modification"),
366 }
367
368 let mut s: Cow<'_, str> = Cow::Owned("hello world".to_string());
369 s.remove_all_ascii_in_place(AsciiChar::l);
370 assert_eq!(s, "heo word");
371 }
372
373 #[test]
374 fn test_cow_replace_all_ascii_in_place() {
375 let mut s: Cow<'_, str> = Cow::Borrowed("hello world");
376 s.replace_all_ascii_in_place(AsciiChar::l, AsciiChar::x);
377 assert_eq!(s, "hexxo worxd");
378 match s {
379 Cow::Owned(_) => {}
380 Cow::Borrowed(_) => panic!("Should be owned after modification"),
381 }
382 }
383
384 #[test]
385 fn test_cow_replace_all_str() {
386 let s: Cow<'_, str> = Cow::Borrowed("hello world hello");
387 let result = s.replace_all_str("hello", "hi");
388 assert_eq!(result, "hi world hi");
389 assert_eq!(s, "hello world hello"); let s: Cow<'_, str> = Cow::Borrowed("hello world");
392 let result = s.replace_all_str("xyz", "abc");
393 match result {
394 Cow::Borrowed(_) => {}
395 Cow::Owned(_) => panic!("Should return borrowed when no changes"),
396 }
397 assert_eq!(result, "hello world");
398 assert_eq!(s, "hello world");
399 }
400
401 #[test]
402 fn test_trait_separation() {
403 fn use_replace_string<T: ReplaceString>(s: &T) -> Cow<'_, str> {
405 s.remove_all_ascii(AsciiChar::l)
406 }
407
408 fn use_replace_string_in_place<T: ReplaceStringInPlace>(s: &mut T) {
409 s.remove_all_ascii_in_place(AsciiChar::l);
410 }
411
412 let s1 = "hello world";
413 let result = use_replace_string(&s1);
414 assert_eq!(result, "heo word");
415
416 let s2 = "hello world".to_string();
417 let result = use_replace_string(&s2);
418 assert_eq!(result, "heo word");
419
420 let mut s3 = "hello world".to_string();
421 use_replace_string_in_place(&mut s3);
422 assert_eq!(s3, "heo word");
423
424 let mut s4: Cow<'_, str> = Cow::Borrowed("hello world");
425 use_replace_string_in_place(&mut s4);
426 assert_eq!(s4, "heo word");
427 }
428}