Ржавчина `split` разделяется до совпадения с образцом

Я пытаюсь разделить String, сохраняя каждое совпадение с шаблоном, но сохраняя совпавшую часть как начало следующей подстроки, а не как терминатор последней.

Текущая реализация:

let s = String::from("AaaBbCc");
for block in s.split_inclusive(|c: char| c.is_uppercase()) {
    println!("{block}")
}

Токовый выход

A
aaB
bC
c

Желаемый результат

Aaa
Bb
Cc

Как этого можно достичь?

@DanielA.White, пожалуйста, хотя бы прочитайте вопрос, прежде чем отправлять запрос на закрытие. Если бы вы прочитали любой вопрос, было бы ясно, что я знаю, как сохранить разделители. Текущий код делает это, и текущие выходные данные показывают, что он делает это, он также использует split_inclusive, что является решением, данным в вопросе, на который вы ссылаетесь. Вопрос в том, как изменить местоположение разделения (до и после), а не в том, как сохранить совпадающий символ.

Pioneer_11 15.07.2024 04:39
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
1
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я считал, что этого невозможно добиться каким-то одним методом на String. Но есть обходной путь:

let s = String::from("AaaBbCc");
let s_reversed = s.chars().rev().collect::<String>();
let blocks = s_reversed
    .split_inclusive(|c: char| c.is_uppercase())
    // reverse each block in the iterator...
    .map(|block| block.chars().rev().collect::<String>())
    // and then reverse the whole iterator
    .rev();
for block in blocks {
    println!("{}", block)
}

Это работает, но очень неэффективно. Итераторы разделения в Rust ленивы и не требуют выделения памяти, поэтому они чрезвычайно эффективны. Напротив, ваш код обрабатывает строку дважды и дважды выделяет для нее память (и немного больше маленьких String's менее эффективны, чем один большой String из-за необходимости хранить их размер и указатель каждый), это работает, но это легко сделать лучше . например:

Pioneer_11 15.07.2024 04:59
let s = String::from("AaaBbCc"); let blocks = { let mut blocks = Vec::new(); let mut last_split_at = 0; for (index, c) in s.chars().enumerate() { if c.is_uppercase() { blocks.push(&s[last_split_at..index]); last_split_at = index; } } if last_split_at != s.len() - 1 { blocks.push(&s[last_split_at..s.len()]) } blocks }; for block in blocks { println!("{block}") }
Pioneer_11 15.07.2024 04:59

который создает Vec из &str, он по-прежнему гораздо менее эффективен, чем split, поскольку он не ленив и ему нужно выделить место для Vec, но он немного быстрее и эффективнее использует память, чем реверсирование, сбор, разделение, реверсирование, собирать, а затем снова разделять. Возможно, вы правы в том, что это невозможно сделать с помощью split, но я пока оставлю вопрос открытым на случай, если нам чего-то не хватает (или есть более эффективный обходной путь)

Pioneer_11 15.07.2024 05:05

«Очень эффективно» и «очень неэффективно» без конкретных критериев бессмысленно. В зависимости от варианта использования это также может быть просто «достаточно хорошо».

Richard Neumann 16.07.2024 07:51

@RichardNeumann это очень неэффективно по сравнению с оптимальной реализацией, поскольку требует большого количества ненужного выделения памяти. Это не обязательно означает, что это плохая реализация. Например, его преимущество заключается в том, что он немного проще, чем собственное решение итератора isaactfa, и, как вы говорите, в зависимости от варианта использования это может быть хорошо (здесь я имею дело с большими строками и множеством разделений, поэтому это не отлично), но тот факт, что эффективность может не потребоваться для некоторых проблем или вы предпочитаете простоту эффективности, не меняет того факта, что это неэффективно.

Pioneer_11 17.07.2024 11:51
Ответ принят как подходящий

Я не думаю, что стандартная библиотека сможет вам в этом помочь (хотя на String есть миллион методов, я могу ошибаться). Но если вам действительно нужна лень, это достаточно простой итератор, который можно написать самому:

struct CamelCaseSplit<'a> {
    char_indices: std::str::CharIndices<'a>,
    chunk_start: usize,
    s: &'a str
}

impl<'a> CamelCaseSplit<'a> {
    pub fn new(s: &'a str) -> Self {
        let mut char_indices = s.char_indices();
        // We'll never want to split before the first char, so skip it.
        char_indices.next();
        Self {
            char_indices,
            chunk_start: 0,
            s,
        }
    }
}

impl<'a> Iterator for CamelCaseSplit<'a> {
    type Item = &'a str;
    
    fn next(&mut self) -> Option<Self::Item> {
        // The input is exhausted
        if self.chunk_start == self.s.len() {
            return None;
        }
        // Find the next uppercase letter position OR the end of the string
        let chunk_end = if let Some((chunk_end, _)) = self.char_indices.by_ref().skip_while(|(_, c)| !c.is_uppercase()).next() {
            chunk_end
        } else {
            self.s.len()
        };
        let chunk = &self.s[self.chunk_start..chunk_end];
        self.chunk_start = chunk_end;
        return Some(chunk);
    }
}

fn main() {
    let split = CamelCaseSplit::new("AaaBbCc");
    for c in split {
        println!("{c}");
        // Aaa
        // Bb
        // Cc
    }
}

Спасибо, лень не критична на 100% (хотя это здорово, посмотрите, как это сделать). Проблема в том, что я работаю с большими строками, которые будут разбиты на множество частей, поэтому избегаю необходимости выделять новую память (особенно целая новая строка) для каждого разделения является существенным преимуществом. Спасибо за отличный ответ.

Pioneer_11 15.07.2024 19:56

Конечно. Я упомянул лень именно потому, что вы, конечно, можете просто сделать это в цикле for. Но если вам нужно часто использовать его повторно или разделить итерацию, итератор может пригодиться. Обратите внимание, что в исходном коде была ошибка. Вначале он всегда будет создавать пустой кусок. Исправление — это просто новые изменения в первых двух строках конструктора.

isaactfa 15.07.2024 22:28

Другие вопросы по теме