Вы уже нашли очень идиоматический способ сделать это.Явная рекурсия не является самоцелью, разве что в учебной среде.То есть явная рекурсия, по сравнению с вашим текущим решением, обременена описанием механики как вы достигаете результата, но не чем результатом является.
Вот один из способов использования явной рекурсии путем преобразования в список:
fun remCharR (c, s) =
let fun rem [] = []
| rem (c'::cs) =
if c = c'
then rem cs
else c'::rem cs
in implode (rem (explode s)) end
Преобразование в список (с использованием explode
) неэффективно, поскольку вы можете перебирать элементы строки без созданиясписок одинаковых элементов.Генерирование списка не удаленных символов не обязательно является плохим выбором, так как с неизменяемыми строками вы не знаете точно, как долго будет длиться ваш конечный результат без предварительного прохождения строки.Функция String.translate
создает список строк, которые затем объединяет.Вы можете сделать что-то похожее.
Так что, если вы замените первоначальное преобразование в список обходом строки ( fold ),
fun fold_string f e0 s =
let val max = String.size s
fun aux i e =
if i < max
then let val c = String.sub (s, i)
in aux (i+1) (f (c, e))
end
else e
in aux 0 e0 end
, вы можете создать строку функция фильтра (очень похожая на функцию String.translate
, которую вы уже нашли, но менее общую):
fun string_filter p s =
implode (fold_string (fn (c, res) => if p c then c::res else res) [] s)
fun remCharR (c, s) =
string_filter (fn c' => c <> c') s
Кроме того, вы заметите, что она случайно переворачивает строку, потому что онаскладки слева;Вы можете свернуть справа (эффективная, но другая семантика) или перевернуть список (неэффективный).Я оставлю это в качестве упражнения для выбора и улучшения.
Как вы можете видеть, во избежание String.translate
я построил другие универсальные вспомогательные функции, чтобы функция remCharR
не содержалаявная рекурсия, но скорее зависит от более читаемых высокоуровневых функций.
Обновление: String.translate
на самом деле делает некоторые довольно умные вещи в отношении.использование памяти.
Вот Moscow ML версия String.translate
:
fun translate f s =
Strbase.translate f (s, 0, size s);
с Strbase.translate
, похожая на:
fun translate f (s,i,n) =
let val stop = i+n
fun h j res = if j>=stop then res
else h (j+1) (f(sub_ s j) :: res)
in revconcat(h i []) end;
и с помощью вспомогательной функции revconcat
:
fun revconcat strs =
let fun acc [] len = len
| acc (v1::vr) len = acc vr (size v1 + len)
val len = acc strs 0
val newstr = if len > maxlen then raise Size else mkstring_ len
fun copyall to [] = () (* Now: to = 0. *)
| copyall to (v1::vr) =
let val len1 = size v1
val to = to - len1
in blit_ v1 0 newstr to len1; copyall to vr end
in copyall len strs; newstr end;
Таким образом, сначала вычисляется общая длина итоговой строки путем суммирования длины каждой подстроки, генерируемой String.translate
, а затем используетсяИзменяемые внутренние функции компилятора (mkstring_
, blit_
) для копирования переведенных строк в итоговую строку результата.
Вы можете добиться аналогичной оптимизации, если знаете, что каждый символ во входной строке приведет кв 0 или 1 символов в выходной строке.Функция String.translate
не может, поскольку результатом перевода может быть несколько символов.Таким образом, альтернативная реализация использует CharArray
.Например:
Найдите количество элементов в новой строке,
fun countP p s =
fold_string (fn (c, total) => if p c
then total + 1
else total) 0 s
Создайте временное, изменяемое CharArray
, обновитеи конвертируем его в строку:
fun string_filter p s =
let val newSize = countP p s
val charArr = CharArray.array (newSize, #"x")
fun update (c, (newPos, oldPos)) =
if p c
then ( CharArray.update (charArr, newPos, c) ; (newPos+1, oldPos+1) )
else (newPos, oldPos+1)
in fold_string update (0,0) s
; CharArray.vector charArr
end
fun remCharR (c, s) =
string_filter (fn c' => c <> c') s
Вы заметите, что remCharR
- то же самое, только реализация string_filter
варьировалась, благодаря некоторой степени абстракции.Эта реализация использует рекурсию через fold_string
, но в остальном она сопоставима с для цикла , который обновляет индекс массива.Так что, хотя это и рекурсивно, но и не очень абстрактно.
Учитывая, что вы получаете сравнимые с оптимизацией оптимизации, использующие String.translate
без сложностей с изменяемыми массивами низкого уровня, я не думаю, что это стоитначать испытывать проблемы с производительностью.