multirep/
lib.rs

1use std::collections::BTreeMap;
2
3/// Multiple version of `str::replace` which replaces multiple patterns at a time.
4///
5///
6/// ```
7/// use multirep::multi_replace;
8///
9/// let s = "Hana is cute";
10/// let r = multi_replace(s, &[("Hana", "Minami"), ("cute", "kawaii")]);
11/// assert_eq!(r, "Minami is kawaii");
12/// ```
13///
14/// The replacement takes place in order of `pats`
15///
16/// ```
17/// use multirep::multi_replace;
18/// assert_eq!("Minami is kawaii", multi_replace("Hana is cute", &[("Hana", "Minami"), ("cute", "kawaii"), ("na", "no")]));
19/// ```
20///
21/// Replacement will not be interfere with previosly replaced strings.
22///
23/// ```
24/// use multirep::multi_replace;
25/// assert_eq!("Minami is kawaii", multi_replace("Hana is cute", &[("Hana", "Minami"), ("cute", "kawaii"), ("kawaii", "hot")]));
26/// ```
27///
28pub fn multi_replace(s: &str, pats: &[(&str, &str)]) -> String {
29    let mut indices = BTreeMap::new();
30
31    for (pat, new) in pats {
32        for (i, p) in s.match_indices(pat) {
33            if indices
34                .range(..=i)
35                .next_back()
36                .map(|(pos, (len, _))| pos + len <= i)
37                .unwrap_or(true)
38            {
39                indices.insert(i, (p.len(), *new));
40            }
41        }
42    }
43
44    let mut result = String::new();
45    let mut end = 0usize;
46
47    for (pos, (len, new)) in indices {
48        // SAFETY: pos is returned by `str::match_indices`, which is valid
49        // end >= 0 since it starts at 0 and only increases
50        // end < pos since `str::match_indices` doesn't overlap
51        // len is the length of one pattern string, so `pos + len`(`end`) should be on unicode boundaries.
52        result.push_str(unsafe { s.get_unchecked(end..pos) });
53        result.push_str(new);
54        end = pos + len;
55    }
56
57    if end < s.len() {
58        // SAFETY: end >= 0 and is on unicode boundaries as above
59        // end < s.len()
60        result.push_str(unsafe { s.get_unchecked(end..) });
61    }
62
63    result
64}
65
66/// Exchanges two patterns in a string
67/// ```
68/// use multirep::exchange;
69/// assert_eq!("foo bar", exchange("bar foo", "foo", "bar"));
70/// ```
71pub fn exchange(s: &str, a: &str, b: &str) -> String {
72    // if a contains b, searching b first will also match a substring of a.
73    // search the longer one to avoid such a situation.
74    let pat = if a.len() > b.len() {
75        [(a, b), (b, a)]
76    } else {
77        [(b, a), (a, b)]
78    };
79    multi_replace(s, &pat)
80}
81
82#[cfg(test)]
83mod test {
84    use super::*;
85
86    #[test]
87    fn replace() {
88        let s = "Hana is cute";
89
90        let r = multi_replace(s, &[("Hana", "Minami"), ("cute", "kawaii")]);
91        assert_eq!(r, "Minami is kawaii");
92    }
93
94    #[test]
95    fn not_match() {
96        assert_eq!(
97            "Hana is kawaii",
98            multi_replace("Hana is cute", &[("Rica", "Minami"), ("cute", "kawaii")])
99        )
100    }
101
102    #[test]
103    fn remain() {
104        assert_eq!(
105            "Hana is kawaii",
106            multi_replace("Minami is kawaii", &[("Minami", "Hana")])
107        )
108    }
109
110    #[test]
111    fn overlap() {
112        assert_eq!(
113            "Both Minami and Hana are kawaii",
114            multi_replace(
115                "Bouh Aoi and Hana are kawaii",
116                &[("Bouh", "Both"), ("Aoi", "Minami"), ("oi", "io")]
117            )
118        )
119    }
120
121    #[test]
122    fn exchange() {
123        let s = "Both Hana and Minami are kawaii";
124
125        assert_eq!(
126            "Both Minami and Hana are kawaii",
127            super::exchange(s, "Minami", "Hana")
128        );
129        assert_eq!(
130            "Both Minami and Hana are kawaii",
131            super::exchange(s, "Hana", "Minami")
132        );
133        assert_eq!(
134            "Both Hinata and Hina are kawaii",
135            super::exchange("Both Hina and Hinata are kawaii", "Hina", "Hinata")
136        );
137        assert_eq!(
138            "Both Hinata and Hina are kawaii",
139            super::exchange("Both Hina and Hinata are kawaii", "Hinata", "Hina")
140        );
141    }
142}