|
11 | 11 | //! - **Flexible and Efficient**: Designed to extend the standard string functionality without sacrificing performance. |
12 | 12 |
|
13 | 13 | use std::collections::{BTreeMap, HashMap}; |
| 14 | +use std::fmt::{Display, Write}; |
14 | 15 | use std::ops::Deref; |
15 | 16 |
|
16 | 17 | mod sailed { |
@@ -131,6 +132,14 @@ pub trait StrExt: sailed::Sailed { |
131 | 132 | /// If index happens to be on a valid char boundary then index itself is returned. |
132 | 133 | /// Note that both 0 and string's length are consedered valid char boundaries. |
133 | 134 | fn previous_char_boundary(&self, index: usize) -> usize; |
| 135 | + |
| 136 | + /// Joins an iterator of items into a single `String`, using `self` as the separator. |
| 137 | + /// |
| 138 | + /// Each item is formatted using the `Display` trait and concatenated with `self` |
| 139 | + /// placed between consecutive items. |
| 140 | + fn join<T: Display>(&self, iterable: impl IntoIterator<Item = T>) -> String |
| 141 | + where |
| 142 | + Self: Display; |
134 | 143 | } |
135 | 144 |
|
136 | 145 | /// The `StringExt` trait extends `String` with advanced in-place manipulation methods, |
@@ -194,9 +203,9 @@ pub trait StringExt: StrExt { |
194 | 203 | fn replace_in_place(&mut self, from: impl EncodeUtf8, to: impl EncodeUtf8); |
195 | 204 | } |
196 | 205 |
|
197 | | -impl<T> StrExt for T |
| 206 | +impl<S> StrExt for S |
198 | 207 | where |
199 | | - T: sailed::Sailed + Deref<Target = str>, |
| 208 | + S: sailed::Sailed + Deref<Target = str>, |
200 | 209 | { |
201 | 210 | fn fill_start(&self, fill: impl EncodeUtf8, times: usize) -> String { |
202 | 211 | let mut buf = Default::default(); |
@@ -476,6 +485,22 @@ where |
476 | 485 |
|
477 | 486 | index |
478 | 487 | } |
| 488 | + |
| 489 | + fn join<T: Display>(&self, iterable: impl IntoIterator<Item = T>) -> String |
| 490 | + where |
| 491 | + Self: Display, |
| 492 | + { |
| 493 | + let mut iter = iterable.into_iter(); |
| 494 | + let mut acc = String::new(); |
| 495 | + |
| 496 | + if let Some(value) = iter.next() { |
| 497 | + write!(acc, "{value}").unwrap(); |
| 498 | + iter.try_for_each(|item| write!(acc, "{self}{item}")) |
| 499 | + .unwrap(); |
| 500 | + } |
| 501 | + |
| 502 | + acc |
| 503 | + } |
479 | 504 | } |
480 | 505 |
|
481 | 506 | impl StringExt for String { |
@@ -1380,4 +1405,34 @@ mod tests { |
1380 | 1405 | assert_eq!(sut.longest_common_substring(other), expected); |
1381 | 1406 | } |
1382 | 1407 | } |
| 1408 | + |
| 1409 | + #[test] |
| 1410 | + fn join_strings() { |
| 1411 | + let result = ", ".join(["one", "two", "three"]); |
| 1412 | + assert_eq!(result, "one, two, three"); |
| 1413 | + } |
| 1414 | + |
| 1415 | + #[test] |
| 1416 | + fn join_integers() { |
| 1417 | + let result = "-".join([1, 2, 3, 4]); |
| 1418 | + assert_eq!(result, "1-2-3-4"); |
| 1419 | + } |
| 1420 | + |
| 1421 | + #[test] |
| 1422 | + fn join_single_item() { |
| 1423 | + let result = "/".join(["only"]); |
| 1424 | + assert_eq!(result, "only"); |
| 1425 | + } |
| 1426 | + |
| 1427 | + #[test] |
| 1428 | + fn join_empty_iterator() { |
| 1429 | + let result = ";".join(std::iter::empty::<String>()); |
| 1430 | + assert_eq!(result, ""); |
| 1431 | + } |
| 1432 | + |
| 1433 | + #[test] |
| 1434 | + fn join_non_ascii_strings() { |
| 1435 | + let result = "·".join(["Ā", "❤️", "Ȁ"]); |
| 1436 | + assert_eq!(result, "Ā·❤️·Ȁ"); |
| 1437 | + } |
1383 | 1438 | } |
0 commit comments