use crate::guard_empty;
pub trait StringIndex {
#[must_use]
fn index_of_any_char(&self, search_chars: &[char]) -> Option<usize>;
#[must_use]
fn index_of_any_in(&self, search_chars: &str) -> Option<usize>;
#[must_use]
fn index_of_any(&self, search: &[&str]) -> Option<usize>;
#[must_use]
fn index_of_any_but_char(&self, search_chars: &[char]) -> Option<usize>;
#[must_use]
fn index_of_any_but(&self, search_chars: &str) -> Option<usize>;
#[must_use]
fn last_index_of_any(&self, search: &[&str]) -> Option<usize>;
#[must_use]
fn ordinal_index_of(&self, search: &str, ordinal: usize) -> Option<usize>;
#[must_use]
fn last_ordinal_index_of(&self, search: &str, ordinal: usize) -> Option<usize>;
#[must_use]
fn index_of_ignore_case(&self, search: &str) -> Option<usize>;
#[must_use]
fn index_of_ignore_case_from(&self, search: &str, start: usize) -> Option<usize>;
#[must_use]
fn last_index_of_ignore_case(&self, search: &str) -> Option<usize>;
#[must_use]
fn last_index_of_ignore_case_from(&self, search: &str, start: usize) -> Option<usize>;
#[must_use]
fn index_of_difference(&self, other: &str) -> Option<usize>;
#[must_use]
fn count_matches_char(&self, ch: char) -> usize;
#[must_use]
fn count_matches(&self, sub: &str) -> usize;
}
impl StringIndex for str {
fn index_of_any_char(&self, search_chars: &[char]) -> Option<usize> {
guard_empty!(self, search_chars, None);
self.char_indices()
.find(|(_, c)| search_chars.contains(c))
.map(|(i, _)| i)
}
fn index_of_any_in(&self, search_chars: &str) -> Option<usize> {
guard_empty!(self, search_chars, None);
self.char_indices()
.find(|(_, c)| search_chars.chars().any(|sc| sc == *c))
.map(|(i, _)| i)
}
fn index_of_any(&self, search: &[&str]) -> Option<usize> {
guard_empty!(self, search, None);
search
.iter()
.filter(|s| !s.is_empty())
.filter_map(|s| self.find(s))
.min()
}
fn index_of_any_but_char(&self, search_chars: &[char]) -> Option<usize> {
guard_empty!(self, search_chars, None);
self.char_indices()
.find(|(_, c)| !search_chars.contains(c))
.map(|(i, _)| i)
}
fn index_of_any_but(&self, search_chars: &str) -> Option<usize> {
guard_empty!(self, search_chars, None);
self.char_indices()
.find(|(_, c)| !search_chars.chars().any(|sc| sc == *c))
.map(|(i, _)| i)
}
fn last_index_of_any(&self, search: &[&str]) -> Option<usize> {
guard_empty!(self, search, None);
search
.iter()
.filter(|s| !s.is_empty())
.filter_map(|s| self.rfind(s))
.max()
}
fn ordinal_index_of(&self, search: &str, ordinal: usize) -> Option<usize> {
guard_empty!(self, search, None);
if ordinal == 0 {
return None;
}
let mut count = 0;
let mut start = 0;
while let Some(pos) = self[start..].find(search) {
count += 1;
let abs_pos = start + pos;
if count == ordinal {
return Some(abs_pos);
}
start = abs_pos + search.len();
}
None
}
fn last_ordinal_index_of(&self, search: &str, ordinal: usize) -> Option<usize> {
guard_empty!(self, search, None);
if ordinal == 0 {
return None;
}
let mut count = 0;
let mut end = self.len();
while let Some(pos) = self[..end].rfind(search) {
count += 1;
if count == ordinal {
return Some(pos);
}
if pos == 0 {
break;
}
end = pos;
}
None
}
fn index_of_ignore_case(&self, search: &str) -> Option<usize> {
self.index_of_ignore_case_from(search, 0)
}
fn index_of_ignore_case_from(&self, search: &str, start: usize) -> Option<usize> {
guard_empty!(self, search, None);
if start >= self.len() {
return None;
}
let haystack = self.to_lowercase();
let needle = search.to_lowercase();
haystack[start..].find(&needle).map(|pos| start + pos)
}
fn last_index_of_ignore_case(&self, search: &str) -> Option<usize> {
guard_empty!(self, search, None);
let haystack = self.to_lowercase();
let needle = search.to_lowercase();
haystack.rfind(&needle)
}
fn last_index_of_ignore_case_from(&self, search: &str, start: usize) -> Option<usize> {
guard_empty!(self, search, None);
let haystack = self.to_lowercase();
let needle = search.to_lowercase();
let search_end = (start + 1).min(haystack.len());
haystack[..search_end].rfind(&needle)
}
fn index_of_difference(&self, other: &str) -> Option<usize> {
if self == other {
return None;
}
if self.is_empty() || other.is_empty() {
return Some(0);
}
for ((i, c1), c2) in self.char_indices().zip(other.chars()) {
if c1 != c2 {
return Some(i);
}
}
Some(self.len().min(other.len()))
}
fn count_matches_char(&self, ch: char) -> usize {
self.chars().filter(|c| *c == ch).count()
}
fn count_matches(&self, sub: &str) -> usize {
if self.is_empty() || sub.is_empty() {
return 0;
}
self.matches(sub).count()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod index_of_any_char {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".index_of_any_char(&['a', 'b']), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".index_of_any_char(&[]), None);
}
#[test]
fn both_empty() {
assert_eq!("".index_of_any_char(&[]), None);
}
#[test]
fn found_first() {
assert_eq!("zzabyycdxx".index_of_any_char(&['z', 'a']), Some(0));
}
#[test]
fn found_middle() {
assert_eq!("zzabyycdxx".index_of_any_char(&['b', 'y']), Some(3));
}
#[test]
fn found_later_char_first_in_list() {
assert_eq!("zzabyycdxx".index_of_any_char(&['z', 'y']), Some(0));
}
#[test]
fn not_found() {
assert_eq!("aba".index_of_any_char(&['z']), None);
}
#[test]
fn unicode() {
assert_eq!("日本語".index_of_any_char(&['本', 'x']), Some(3));
assert_eq!("日本語".index_of_any_char(&['x', 'y']), None);
}
}
mod index_of_any_in {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".index_of_any_in("ab"), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".index_of_any_in(""), None);
}
#[test]
fn both_empty() {
assert_eq!("".index_of_any_in(""), None);
}
#[test]
fn found() {
assert_eq!("zzabyycdxx".index_of_any_in("za"), Some(0));
}
#[test]
fn found_middle() {
assert_eq!("zzabyycdxx".index_of_any_in("by"), Some(3));
}
#[test]
fn not_found() {
assert_eq!("aba".index_of_any_in("z"), None);
}
#[test]
fn unicode() {
assert_eq!("日本語".index_of_any_in("本x"), Some(3));
assert_eq!("日本語".index_of_any_in("xy"), None);
}
}
mod index_of_any {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".index_of_any(&["ab", "cd"]), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".index_of_any(&[]), None);
}
#[test]
fn both_empty() {
assert_eq!("".index_of_any(&[]), None);
}
#[test]
fn found_first_search() {
assert_eq!("zzabyycdxx".index_of_any(&["ab", "cd"]), Some(2));
}
#[test]
fn found_order_independent() {
assert_eq!("zzabyycdxx".index_of_any(&["cd", "ab"]), Some(2));
}
#[test]
fn not_found() {
assert_eq!("zzabyycdxx".index_of_any(&["mn", "op"]), None);
}
#[test]
fn empty_search_string_ignored() {
assert_eq!("zzabyycdxx".index_of_any(&["mn", ""]), None);
assert_eq!("zzabyycdxx".index_of_any(&["", "ab"]), Some(2));
}
#[test]
fn unicode() {
assert_eq!("日本語".index_of_any(&["本", "x"]), Some(3));
assert_eq!("日本語".index_of_any(&["x", "y"]), None);
}
}
mod index_of_any_but_char {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".index_of_any_but_char(&['a', 'b']), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".index_of_any_but_char(&[]), None);
}
#[test]
fn both_empty() {
assert_eq!("".index_of_any_but_char(&[]), None);
}
#[test]
fn found() {
assert_eq!("zzabyycdxx".index_of_any_but_char(&['z', 'a']), Some(3));
}
#[test]
fn all_in_set() {
assert_eq!("aba".index_of_any_but_char(&['a', 'b']), None);
}
#[test]
fn first_not_in_set() {
assert_eq!("aba".index_of_any_but_char(&['z']), Some(0));
}
#[test]
fn unicode() {
assert_eq!("日本語".index_of_any_but_char(&['日']), Some(3));
assert_eq!("日本語".index_of_any_but_char(&['日', '本', '語']), None);
}
}
mod index_of_any_but {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".index_of_any_but("ab"), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".index_of_any_but(""), None);
}
#[test]
fn both_empty() {
assert_eq!("".index_of_any_but(""), None);
}
#[test]
fn found() {
assert_eq!("zzabyycdxx".index_of_any_but("za"), Some(3));
}
#[test]
fn all_in_set() {
assert_eq!("aba".index_of_any_but("ab"), None);
}
#[test]
fn first_not_in_set() {
assert_eq!("aba".index_of_any_but("z"), Some(0));
}
#[test]
fn unicode() {
assert_eq!("日本語".index_of_any_but("日"), Some(3));
assert_eq!("日本語".index_of_any_but("日本語"), None);
}
}
mod last_index_of_any {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".last_index_of_any(&["ab", "cd"]), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".last_index_of_any(&[]), None);
}
#[test]
fn both_empty() {
assert_eq!("".last_index_of_any(&[]), None);
}
#[test]
fn found_last() {
assert_eq!("zzabyycdxx".last_index_of_any(&["ab", "cd"]), Some(6));
}
#[test]
fn found_order_independent() {
assert_eq!("zzabyycdxx".last_index_of_any(&["cd", "ab"]), Some(6));
}
#[test]
fn not_found() {
assert_eq!("zzabyycdxx".last_index_of_any(&["mn", "op"]), None);
}
#[test]
fn empty_search_string_ignored() {
assert_eq!("zzabyycdxx".last_index_of_any(&["mn", ""]), None);
}
#[test]
fn unicode() {
assert_eq!("日本語日本".last_index_of_any(&["本", "語"]), Some(12));
}
}
mod ordinal_index_of {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".ordinal_index_of("a", 1), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".ordinal_index_of("", 1), None);
}
#[test]
fn zero_ordinal() {
assert_eq!("aabaabaa".ordinal_index_of("a", 0), None);
}
#[test]
fn first_occurrence() {
assert_eq!("aabaabaa".ordinal_index_of("a", 1), Some(0));
}
#[test]
fn second_occurrence() {
assert_eq!("aabaabaa".ordinal_index_of("a", 2), Some(1));
}
#[test]
fn first_b() {
assert_eq!("aabaabaa".ordinal_index_of("b", 1), Some(2));
}
#[test]
fn second_b() {
assert_eq!("aabaabaa".ordinal_index_of("b", 2), Some(5));
}
#[test]
fn multi_char_first() {
assert_eq!("aabaabaa".ordinal_index_of("ab", 1), Some(1));
}
#[test]
fn multi_char_second() {
assert_eq!("aabaabaa".ordinal_index_of("ab", 2), Some(4));
}
#[test]
fn ordinal_too_high() {
assert_eq!("aabaabaa".ordinal_index_of("b", 3), None);
}
#[test]
fn not_found() {
assert_eq!("aabaabaa".ordinal_index_of("z", 1), None);
}
#[test]
fn unicode() {
assert_eq!("日本語日本語".ordinal_index_of("本", 1), Some(3));
assert_eq!("日本語日本語".ordinal_index_of("本", 2), Some(12));
}
}
mod last_ordinal_index_of {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".last_ordinal_index_of("a", 1), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".last_ordinal_index_of("", 1), None);
}
#[test]
fn zero_ordinal() {
assert_eq!("aabaabaa".last_ordinal_index_of("a", 0), None);
}
#[test]
fn first_last() {
assert_eq!("aabaabaa".last_ordinal_index_of("a", 1), Some(7));
}
#[test]
fn second_last() {
assert_eq!("aabaabaa".last_ordinal_index_of("a", 2), Some(6));
}
#[test]
fn first_last_b() {
assert_eq!("aabaabaa".last_ordinal_index_of("b", 1), Some(5));
}
#[test]
fn second_last_b() {
assert_eq!("aabaabaa".last_ordinal_index_of("b", 2), Some(2));
}
#[test]
fn multi_char_first_last() {
assert_eq!("aabaabaa".last_ordinal_index_of("ab", 1), Some(4));
}
#[test]
fn multi_char_second_last() {
assert_eq!("aabaabaa".last_ordinal_index_of("ab", 2), Some(1));
}
#[test]
fn ordinal_too_high() {
assert_eq!("aabaabaa".last_ordinal_index_of("b", 3), None);
}
#[test]
fn not_found() {
assert_eq!("aabaabaa".last_ordinal_index_of("z", 1), None);
}
#[test]
fn unicode() {
assert_eq!("日本語日本語".last_ordinal_index_of("本", 1), Some(12));
assert_eq!("日本語日本語".last_ordinal_index_of("本", 2), Some(3));
}
}
mod index_of_ignore_case {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".index_of_ignore_case("a"), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".index_of_ignore_case(""), None);
}
#[test]
fn both_empty() {
assert_eq!("".index_of_ignore_case(""), None);
}
#[test]
fn found_uppercase_search() {
assert_eq!("aabaabaa".index_of_ignore_case("A"), Some(0));
}
#[test]
fn found_uppercase_b() {
assert_eq!("aabaabaa".index_of_ignore_case("B"), Some(2));
}
#[test]
fn found_uppercase_multi() {
assert_eq!("aabaabaa".index_of_ignore_case("AB"), Some(1));
}
#[test]
fn not_found() {
assert_eq!("aabaabaa".index_of_ignore_case("z"), None);
}
#[test]
fn unicode() {
assert_eq!("Héllo".index_of_ignore_case("héllo"), Some(0));
assert_eq!("HÉLLO".index_of_ignore_case("héllo"), Some(0));
}
}
mod index_of_ignore_case_from {
use super::*;
#[test]
fn from_start() {
assert_eq!("aabaabaa".index_of_ignore_case_from("A", 0), Some(0));
}
#[test]
fn from_one() {
assert_eq!("aabaabaa".index_of_ignore_case_from("A", 1), Some(1));
}
#[test]
fn b_from_start() {
assert_eq!("aabaabaa".index_of_ignore_case_from("B", 0), Some(2));
}
#[test]
fn b_from_middle() {
assert_eq!("aabaabaa".index_of_ignore_case_from("B", 3), Some(5));
}
#[test]
fn start_beyond_length() {
assert_eq!("aabaabaa".index_of_ignore_case_from("B", 9), None);
}
#[test]
fn empty_search() {
assert_eq!("aabaabaa".index_of_ignore_case_from("", 0), None);
}
}
mod last_index_of_ignore_case {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".last_index_of_ignore_case("a"), None);
}
#[test]
fn empty_search() {
assert_eq!("abc".last_index_of_ignore_case(""), None);
}
#[test]
fn both_empty() {
assert_eq!("".last_index_of_ignore_case(""), None);
}
#[test]
fn found_uppercase_search() {
assert_eq!("aabaabaa".last_index_of_ignore_case("A"), Some(7));
}
#[test]
fn found_uppercase_b() {
assert_eq!("aabaabaa".last_index_of_ignore_case("B"), Some(5));
}
#[test]
fn found_uppercase_multi() {
assert_eq!("aabaabaa".last_index_of_ignore_case("AB"), Some(4));
}
#[test]
fn not_found() {
assert_eq!("aabaabaa".last_index_of_ignore_case("z"), None);
}
#[test]
fn unicode() {
assert_eq!("HélloHéllo".last_index_of_ignore_case("héllo"), Some(6));
}
}
mod last_index_of_ignore_case_from {
use super::*;
#[test]
fn from_end() {
assert_eq!("aabaabaa".last_index_of_ignore_case_from("A", 8), Some(7));
}
#[test]
fn from_seven() {
assert_eq!("aabaabaa".last_index_of_ignore_case_from("A", 7), Some(7));
}
#[test]
fn b_from_end() {
assert_eq!("aabaabaa".last_index_of_ignore_case_from("B", 8), Some(5));
}
#[test]
fn b_from_middle() {
assert_eq!("aabaabaa".last_index_of_ignore_case_from("B", 4), Some(2));
}
#[test]
fn not_found_before_start() {
assert_eq!("aabaabaa".last_index_of_ignore_case_from("B", 1), None);
}
#[test]
fn empty_search() {
assert_eq!("aabaabaa".last_index_of_ignore_case_from("", 0), None);
}
}
mod index_of_difference {
use super::*;
#[test]
fn both_empty() {
assert_eq!("".index_of_difference(""), None);
}
#[test]
fn first_empty() {
assert_eq!("".index_of_difference("abc"), Some(0));
}
#[test]
fn second_empty() {
assert_eq!("abc".index_of_difference(""), Some(0));
}
#[test]
fn equal() {
assert_eq!("abc".index_of_difference("abc"), None);
}
#[test]
fn shorter_prefix() {
assert_eq!("ab".index_of_difference("abxyz"), Some(2));
}
#[test]
fn differ_in_middle() {
assert_eq!("abcde".index_of_difference("abxyz"), Some(2));
}
#[test]
fn differ_at_start() {
assert_eq!("abcde".index_of_difference("xyz"), Some(0));
}
#[test]
fn i_am_a_machine() {
assert_eq!(
"i am a machine".index_of_difference("i am a robot"),
Some(7)
);
}
#[test]
fn unicode() {
assert_eq!("日本語".index_of_difference("日本人"), Some(6));
assert_eq!("日本語".index_of_difference("日本語"), None);
}
}
mod count_matches_char {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".count_matches_char('a'), 0);
}
#[test]
fn found_multiple() {
assert_eq!("abba".count_matches_char('a'), 2);
}
#[test]
fn found_middle() {
assert_eq!("abba".count_matches_char('b'), 2);
}
#[test]
fn not_found() {
assert_eq!("abba".count_matches_char('x'), 0);
}
#[test]
fn unicode() {
assert_eq!("日本語日本語".count_matches_char('本'), 2);
}
}
mod count_matches {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".count_matches("a"), 0);
}
#[test]
fn empty_search() {
assert_eq!("abc".count_matches(""), 0);
}
#[test]
fn both_empty() {
assert_eq!("".count_matches(""), 0);
}
#[test]
fn found_single_char() {
assert_eq!("abba".count_matches("a"), 2);
}
#[test]
fn found_multi_char() {
assert_eq!("abba".count_matches("ab"), 1);
}
#[test]
fn not_found() {
assert_eq!("abba".count_matches("xxx"), 0);
}
#[test]
fn non_overlapping() {
assert_eq!("aaaa".count_matches("aa"), 2);
}
#[test]
fn unicode() {
assert_eq!("日本語日本語".count_matches("本語"), 2);
}
}
mod string_types {
use super::*;
#[test]
fn string_type() {
assert_eq!(String::from("abc").index_of_any_char(&['a']), Some(0));
}
#[test]
fn string_ref() {
let s = String::from("abba");
assert_eq!(s.count_matches("a"), 2);
}
#[test]
fn boxed_str() {
let s: Box<str> = "aabaabaa".into();
assert_eq!(s.ordinal_index_of("ab", 2), Some(4));
}
}
}