Reputation: 511
I need some explanation into the unexpected result of the code below, seemingly, due to some bug.
reverse' :: [b] -> [b]
reverse' [] = []
reverse' [x] = [x]
reverse'(x:xs) = last (x:xs) : reverse' xs
*Main> reverse' [0,8,2,5,6,1,20,99,91,1]
[1,1,1,1,1,1,1,1,1,1]
Is this because of some bug?
Upvotes: 8
Views: 8305
Reputation: 29
reverse2 :: [a] -> [a]
reverse2 [] = []
reverse2 [x] = [x]
reverse2 x = (last x) : (reverse2 (init x))
Upvotes: 0
Reputation: 54584
While playing around with Data.Monoid
I found the following alternative, slightly rube-goldbergish solution:
import Data.Foldable
import Data.Monoid
reverse = getDual . foldMap (Dual . (:[]))
Upvotes: 2
Reputation: 71065
The minimal correction to your original code is arguably
-- reverse'(x:xs) = last (x:xs) : reverse' xs
reverse' (x:xs) = reverse' xs ++ [x]
i.e. you were combining your list's sub-parts in a wrong order.
This is of course still a quadratic algorithm. You get an iterative version out of it by first observing
reverse' (a:b:c:d:xs) = (((reverse' xs ++ [d]) ++ [c]) ++ [b]) ++ [a]
and then regrouping and keeping thus formed intermediate results in a separate, accumulator argument, as was pointed out in a previous response:
rev (x:xs) acc = rev xs (x:acc)
Having found your workhorse, you set it up with an appropriate interface,
reverse' xs = rev xs []
(plus few edge cases).
Upvotes: 6
Reputation: 54584
As others already pointed out the mistake, let me show you a useful and elegant technique which can be often applied in such cases and leads often to efficient algorithms: Use an accumulator.
rev xs = rev' xs [] where
rev' [] acc = acc
rev' (x:xs) acc = rev' xs (x:acc)
So you have a sub-function with an additional argument (the "accumulator") which collects what you already have. Obviously in the base case you need to give this result back, because you are done. The recursive case is very simple here: It's just like having a stack of plates, which you take one by one from the top, and build a new stack by adding one by one at the top. This resulting stack is reversed, just like we need it.
Note that for some other applications of this technique you don't want that reversion, which can be fixed by inserting a reverse
in the base case.
Upvotes: 10
Reputation: 2571
When you get a totally unexpected result, especially with relatively simple function like this, it can be helpful to follow the logic through by hand. So let's see what happens here:
reverse' (0:[8,2,5,6,1,20,99,91,1]) = 1 : reverse' xs ==>
1 : (reverse' (8:[2,5,6,1,20,99,91,1]) = 1 : reverse' xs ==>
1 : 1 : (reverse' (2:[5,6,1,20,99,91,1]) = 1 : reverse' xs ==>
...
You can see where this is going. The problem is simple; you're just reversing the wrong part of the list in the recursive step. Instead of reversing the tail like you're doing now, you want to reverse everything but the last element. So you could revise it to something like this:
reverse' :: [b] -> [b]
reverse' [] = []
reverse' [x] = [x]
reverse' xs = last xs : reverse' (init xs)
which returns what you'd expect: reverse' [1,91,99,20,1,6,5,2,8,0] = [0,8,2,5,6,1,20,99,91,1]
Upvotes: 18