Хвост Рекурсивное Расстояние Левенштейна

я реализовал расстояние Левенштейна довольно стандартным способом в F# в качестве упражнения

let lastchar (s:string) = s.Substring(s.Length-1, 1)
let lastchar_substring (s:string) len = s.Substring(len-1, 1)

let rec levdist (sa:string) (sb:string) alen blen = match alen, blen with
    | -1, -1 -> levdist sa sb sa.Length sb.Length
    | 0, 0 -> 0
    | _ , 0 -> alen
    | 0, _  -> blen
    | _ -> List.min [ (* How do I make this tail recursive...? *)
            (levdist sa sb (alen-1) blen) + 1;
            (levdist sa sb alen (blen-1)) + 1;
            (levdist sa sb (alen-1) (blen-1)) + 
                 match (lastchar_substring  sa alen), (lastchar_substring sb blen) with 
                      | x, y when x = y -> 0 
                      | _ -> 1
        ])

однако я не вижу простого способа преобразовать список.min вызов быть хвост рекурсивным. Мы не просто выполняем некоторые дополнительные независимые вычисления после рекурсивного вызова; вместо этого мы выбираем результат нескольких рекурсивных вызовов.

есть ли способ элегантно преобразовать это в хвост рекурсивный?

(Я могу легко преобразовать +1 быть хвост рекурсивной)

3 ответов


В общем случае, когда вы хотите превратить код в хвостовую рекурсивную форму, у вас есть два варианта:

  • если ваша рекурсивная функция вызывает только после, вы можете использовать аккумулятор paramter.
  • если он называет себя несколько раз, вы должны использовать продолжения

Как говорит Джеффри, стиль продолжения передачи выглядит немного уродливым, потому что вам нужно преобразовать все функции, чтобы взять другую функцию и верните результат, вызвав его. Однако вы можете сделать это немного приятнее, потому что продолжения-это монады, и поэтому вы можете использовать вычислительные выражения.

Если вы определяете следующий построитель вычислений:

// Computation that uses CPS - when given a continuation
// it does some computation and return the result
type Cont<'T, 'R> = (('T -> 'R) -> 'R)

type ContBuilder() = 
  member x.Return(v) : Cont<'T, 'R> = fun k -> k v
  member x.ReturnFrom(r) = r
  member x.Bind(vf:Cont<'T1, 'R>, f:'T1 -> Cont<'T2, 'R>) : Cont<'T2, 'R> = 
    fun k -> vf (fun v -> f v k)

let cont = ContBuilder()

затем вы можете переписать решение из @gradbot следующим образом (и избавиться от явного построения лямбда-функций):

let levdist (sa:string) (sb:string) = 
    let rec levdist_cont (sa:string) (sb:string) alen blen = cont {
        match alen, blen with
        | -1, -1 -> return! levdist_cont sa sb sa.Length sb.Length 
        |  0,  0 -> return 0
        |  _,  0 -> return alen
        |  0,  _ -> return blen
        |  _ -> 
            let! l1 = levdist_cont sa sb (alen - 1) (blen    )
            let! l2 = levdist_cont sa sb (alen    ) (blen - 1) 
            let! l3 = levdist_cont sa sb (alen - 1) (blen - 1) 
            let d = if (lastchar_substring sa alen) = (lastchar_substring sb blen) then 0 else 1
            return (min (l1 + 1) (min (l2 + 1) (l3 + d))) }

    levdist_cont sa sb -1 -1 (fun x -> x)

Если вы хотите взять минимум над набором рекурсивных вызовов, вы не можете сделать этот хвост рекурсивно. Вам нужно сделать min операция после всех звонков.

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

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


основная идея продолжения передачи заключается в том, что вы" скрываете " будущую работу в функции.

let lastchar (s:string) = s.Substring(s.Length-1, 1)
let lastchar_substring (s:string) len = s.Substring(len-1, 1)

let levdist (sa:string) (sb:string) = 
    let rec levdist_cont (sa:string) (sb:string) alen blen cont =
        match alen, blen with
        | -1, -1 -> levdist_cont sa sb sa.Length sb.Length cont
        |  0,  0 -> cont 0
        |  _,  0 -> cont alen
        |  0,  _ -> cont blen
        |  _ -> 
            levdist_cont sa sb (alen - 1) (blen    ) (fun l1 ->
            levdist_cont sa sb (alen    ) (blen - 1) (fun l2 ->
            levdist_cont sa sb (alen - 1) (blen - 1) (fun l3 -> 
                let d = if (lastchar_substring sa alen) = (lastchar_substring sb blen) then 0 else 1
                cont (min (l1 + 1) (min (l2 + 1) (l3 + d)))
                )))

    levdist_cont sa sb -1 -1 (fun x -> x)

levdist "guisuifgh" "sfg"
|> printf "%A"

выход

6