Как определить, имеют ли две строки только один другой символ в функциональном стиле с итераторами? - PullRequest
1 голос
/ 01 апреля 2019

Мне нужно написать функцию, которая будет проверять, отличаются ли два String последовательно ровно на один символ, т.е. только diff("aba", "abc") == true, diff("aab", "cab") == false, длина строк равна).

Я написал код в императивном стиле, но он кажется уродливым, и я хочу написать его в функциональном стиле с помощью итератора.

Я полагаю, что это должно быть что-то с s1.chars()..enumerate() + некоторым закрытием, который обнаруживает один другой символ в двух строках.

fn has_one_difference(s1: &String, s2: &String) -> bool {
    let mut diff_chars_limit = false;
    let mut s1_chars = s1.chars();
    let mut s2_chars = s2.chars();

    for index in 0..s1.len() {
        if s1_chars.nth(index).unwrap() != s2_chars.nth(index).unwrap() {
            if diff_chars_limit {
                return false
            } else {
                diff_chars_limit = true
            }
        }
    }

    return diff_chars_limit;
}

Я получаю эту ошибку:

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:345:21

на последнем символе итерации строки.

1 Ответ

3 голосов
/ 01 апреля 2019

Во-первых, я исправил ваш императивный код до

  • убрать крайне неэффективный индексный доступ в итераторы символов,
  • удалите сбой, если s1 длиннее, но в остальном равно s2, и замените его тем же поведением "игнорировать хвост более длинной строки", которое ваш код демонстрирует наоборот,
  • используйте str вместо String, поскольку почти никогда не существует веской причины для передачи &String в функцию и
  • исправлены некоторые незначительные проблемы со стилем; в частности добавление точек с запятой к возвращаемым значениям, но с использованием выражения хвостового возврата без возврата Это более идиоматичный Rust.

Это выглядит так:

fn has_one_difference(s1: &str, s2: &str) -> bool {
    let mut found_one_difference = false;

    for (c1, c2) in s1.chars().zip(s2.chars()) {
        if c1 != c2 {
            if found_one_difference {
                return false;
            } else {
                found_one_difference = true
            }
        }
    }

    found_one_difference
}

Теперь для функциональной версии я бы просто написал итератор и посмотрел, смогу ли я вызвать next() дважды:

fn has_one_difference_functional(s1: &str, s2: &str) -> bool {
    // An iterator over different char pairs.
    let mut iter = s1.chars().zip(s2.chars())
        .filter(|(c1, c2)| c1 != c2);

    // First call to next() must succeed (one difference), second must fail.   
    iter.next().is_some() && iter.next().is_none()
}

Это не полностью функционально, но я думаю, что в целом это лучшее сочетание краткости и читабельности. Простая полностью функциональная версия вызовет count() для составного итератора и сравнит его с 1, но это не является коротким замыканием и, следовательно, менее эффективным, чем необходимо. Более эффективная версия может быть написана с try_fold, но она теряет читабельность из-за сложности, поэтому я рассмотрю ее только для has_n_differences функции.

...