В Rust я пытаюсь получить все возможные комбинации символов az до фиксированной длины без повторяющихся букв.
Например, для ограниченного набора a-f и длины 3 я должен получить:
Азбука абд Абэ абф акб акд туз акф адб ... и т. д
Я изо всех сил пытался сделать это с помощью рекурсии и ударялся головой о владении и заимствованиях. Единственный способ, которым мне удалось это сделать, заключается в следующем, но это клонирование строк повсюду и очень неэффективно. Вероятно, в стандартной библиотеке для этого есть стандартные функции перестановки/комбинации, я не знаю, но мне интересно понять, как это можно сделать вручную.
fn main() {
run(&String::new());
}
fn run(target: &String) {
for a in 97..123 { // ASCII a..z
if !target.contains(char::from(a)) {
let next = target.clone() + char::from(a).to_string().as_str(); // Working but terrible
if next.len() == 3 { // Required string size
println!("{}", next);
} else {
run(&next);
}
}
}
}
Слишком долго объяснять подробно, но это просто функция, которая берет ссылку на строку и возвращает i32, основанный на содержимом строки. i32 может варьироваться в зависимости от строки, и я хотел бы найти ее максимальное значение.
Давайте переместим часть «вызов другой функции» в другой вопрос. На StackOverflow принято задавать один вопрос на сообщение.
ОК, отредактировано, чтобы удалить вторую часть вопроса.
Прежде всего, пара замечаний:
&String
— это своего рода анти-паттерн, который редко встречается. Это бесполезно; вся функциональность, которую String
имеет по сравнению с str
, требует изменчивости. Так что это должно быть либо &mut String
, либо &str
.97..123
редко... используйте 'a'..='z'
.Теперь к самой проблеме:
Пока вы передаете неизменяемую строку в рекурсию, вы не сможете обойти клонирование данных. Я бы сделал строку изменяемой, тогда вы можете просто добавлять и удалять из нее отдельные символы.
Так:
fn main() {
run(&mut String::new());
}
fn run(target: &mut String) {
for a in 'a'..='z' {
if !target.contains(a) {
target.push(a);
if target.len() == 3 {
// Required string size
println!("{}", target);
} else {
run(target);
}
target.pop();
}
}
}
Спасибо. Я пробовал этот подход раньше, но поставил target.pop() не в том месте :) Спасибо и за полезные замечания. Должен ли я опубликовать еще один вопрос об обработке возвратов при передаче target в другую функцию со ссылкой на этот пост?
Просто откройте новый вопрос с описанием проблемы, что вы пробовали и почему у вас возникли проблемы.
Поскольку выполнение всех перестановок для 7-буквенной строки является очень долгим процессом, мне интересно, есть ли способ запустить процесс рекурсии с помощью строки «начало». Через 20 минут (один поток) с компиляцией выпуска я получаю результаты для строки abelrst (все еще начинающейся с «a»). Есть ли способ указать эту строку в качестве начального значения, если машина выйдет из строя?
Способов много, хотя все они не по теме данного вопроса. Попробуйте реализовать это самостоятельно, и если вы столкнетесь с конкретной проблемой, с которой не знаете, как справиться, откройте новый вопрос, описывающий ее, то, что вы пробовали, и почему вы боретесь :) Научиться решать проблемы - это №1 самый важный навык для программиста, все остальное, например, языки и механизмы, менее важны. Поэтому я всегда призываю хорошенько разобраться в проблеме, прежде чем просить о помощи.
Я согласен, именно поэтому я пытался решить эту проблему рекурсии, не полагаясь на внешние ящики. Это просто самодельная, совершенно не профессиональная головоломка. Передача начальной строки в цикл a..z for казалась безумной идеей, но я попробую. Спасибо за вашу поддержку.
Не супер сумасшедший, но и не прямой. Удачи :) Одной из идей было бы проверить, будет ли последний элемент текущей run функции еще до контрольной точки, и если да, то вернуться из функции. Это должно перейти к контрольной точке всего за пару итераций.
Просто для полноты, вот альтернативный способ сделать это, когда не используется рекурсия. Он использует .permutations() из ящика Itertools.
Кроме того, вероятно, будет чище возвращать объект String из функции напрямую, вместо того, чтобы передавать изменяемую ссылку по аргументу.
use std::ops::RangeInclusive;
use itertools::Itertools;
fn main() {
println!("result: {}",combine('a'..='d', 3));
println!("result: {}",combine('a'..='g', 4));
println!("result: {}",combine('a'..='c', 3));
println!("result: {}",combine('a'..='c', 4)); // assertion fail
}
fn combine(range: RangeInclusive<char>, depth: usize) -> String
{
assert!( *range.end() as usize - *range.start() as usize + 1 >= depth);
let perms = range.permutations(depth);
let mut result = String::new();
perms.for_each(|mut item| {
item.push(' ');
result += &item.into_iter().collect::<String>();
});
result.pop(); // pop last superfluous space char
result
}
Хотя это хорошее решение imo, OP явно просил решения без .permutations().
@Finomnis, мой плохой, я пропустил эту часть вопроса. немного отредактировал ответ.
«вызвать другую функцию для этой строки, которая возвращает целое число» - подробности?