Are these functions tail recursive?
I'm learning tail recursion and I'm having some difficulties in determining whether my function is tail recursive or not (mainly on functions which I use another function).
I've implemented the two following functions but I'm not so sure if they are tail recursive or not.
The first one is a function to concatenate two lists.
conca list [] = list
conca [] result = result
conca (h:t) result = conca (init (h:t)) ( last(h:t):result )
concatenate::[a]->[a]->[a]
concatenate list1 list2 = conca list1 list2
The computations in the function are processed before the recursive call, but its using last and init, which aren't tail recursive ( I checked their definition in http://ww2.cs.mu.oz.au/172/Haskell/tourofprelude.html )
The second function is to remove the first occurrence of a given number in a given list.
invert [] result = result
invert (h:t) result = invert t (h:result)
remov n [] aux result = invert result []
remov n (h:t) aux result
| aux==1 = remov n t 1 (h:result)
| n==h = remov n t 1 (result)
| otherwise = remov n t 0 (h:result)
remove n list = remov n list 0 []
The parameter aux (which can assume 0 or 1 as value) is being used to mark if the ocurrence has been removed or not.
In the remove function while the partial result is passed down through the recursive call the list is being inverted, upon the end the list is without the first ocurrence but upside-down, thus it have to be inverted to be returned as a result.
conca (h:t) result = conca (init (h:t)) ( last(h:t):result )
is a tail call, but last(h:t):result
starts life as an unevaluated thunk, so it's a (hand-wavy) bit like these nested function calls are on the stack still.
conca
pattern matches on its first argument so init
will be evaluated in the recursive call.
conca
is non-strict in its second argument, so those thunks won't get evaluated while applying the recursive call of conca
.
remov
is tail recursive, yes, but....
Use True
and False
instead of 0
and 1
to make your code clearer:
remov n [] found result = invert result []
remov n (h:t) found result
| found = remov n t True (h:result)
| n==h = remov n t True (result)
| otherwise = remov n t False (h:result)
remove n list = remov n list False []
This would be better not passing around so much data, reducing the copying of n
and using two functions instead of a single function that tests a boolean parameter:
remove' n list = seek list [] where
seek [] result = invert result []
seek (h:t) result | h == n = got t result
| otherwise = seek t (h:result)
got [] result = invert result []
got (h:t) result = got t (h:result)
but got a result
just calculates reverse result ++ a
, so you could write
remove'' n list = seek list [] where
seek [] result = invert result []
seek (h:t) result | h == n = invert result [] ++ t
| otherwise = seek t (h:result)
However, it all seems rather a lot of effort, and still traverses the list twice. Why not go for a non-tail call:
removeFast n [] = []
removeFast n (h:t) | h == n = t
| otherwise = h:removeFast n t
This has the benefit of producing its first element straight away rather than running the whole list, and short cuts to return t
without further computation once it's found the element to remove. Try racing length (removeFast 1 [1..100000])
against length (remove 1 [1..100000])
(vary the number of zeros according to your processor speed).
If you want to make a more efficient tail recursive conca
, you could use the trick from remove
:
conc this result = prepend (invert this []) result
prepend [] result = result
prepend (h:t) result = prepend t (h:result)
As before, you're traversing this
twice, once invert
ing it, the other prepending
it, but that's still a linear algorithm, and much better than using init
and last
for each element, which is quadratic.
上一篇: 将递归转换为“尾递归”
下一篇: 这些函数是否递归?