pub(crate) fn str_succ(s: &str) -> String {
let mut chars: Vec<char> = s.chars().collect();
let index: Option<usize> = chars
.iter()
.enumerate()
.rev()
.filter_map(|(i, c)| CharRange::from_char(*c).map(|_| i))
.next();
match index {
Some(index) => {
let mut carry: char = '1';
for i in (0..=index).rev() {
let ch = chars[i];
let range = match CharRange::from_char(ch) {
None => {
chars.insert(i + 1, carry);
break;
}
Some(range) => range,
};
carry = range.carry();
let (start, end) = range.bound();
if ch == end {
chars[i] = start;
if i == 0 {
chars.insert(0, range.carry());
}
} else {
chars[i] = ((ch as u8) + 1) as char;
break;
}
}
chars.into_iter().collect()
}
None => format!("{}1", s),
}
}
#[derive(Copy, Clone, PartialEq)]
enum CharRange {
Digit,
LowerLetter,
UpperLetter,
}
impl CharRange {
const ALL: &'static [Self] = &[Self::Digit, Self::LowerLetter, Self::UpperLetter];
fn bound(self) -> (char, char) {
match self {
CharRange::Digit => ('0', '9'),
CharRange::LowerLetter => ('a', 'z'),
CharRange::UpperLetter => ('A', 'Z'),
}
}
fn carry(self) -> char {
match self {
CharRange::Digit => '1',
CharRange::LowerLetter => 'a',
CharRange::UpperLetter => 'A',
}
}
fn from_char(ch: char) -> Option<CharRange> {
Self::ALL.iter().copied().find(|range| {
let (start, end) = range.bound();
ch >= start && ch <= end
})
}
}
#[cfg(test)]
#[test]
fn test_str_succ() {
assert_eq!(str_succ("0"), "1");
assert_eq!(str_succ("1"), "2");
assert_eq!(str_succ("9"), "10");
assert_eq!(str_succ("233"), "234");
assert_eq!(str_succ("999"), "1000");
assert_eq!(str_succ("0099"), "0100");
assert_eq!(str_succ("1180591620717411303424"), "1180591620717411303425");
assert_eq!(str_succ("a-99"), "a-100");
assert_eq!(str_succ("aa99"), "ab00");
assert_eq!(str_succ("aa99.."), "ab00..");
assert_eq!(str_succ("a"), "b");
assert_eq!(str_succ("C"), "D");
assert_eq!(str_succ("ASDF"), "ASDG");
assert_eq!(str_succ("zz"), "aaa");
assert_eq!(str_succ("Zz"), "AAa");
assert_eq!(str_succ("9z9Z"), "10a0A");
assert_eq!(str_succ(""), "1");
assert_eq!(str_succ("."), ".1");
}