Reputation: 37
I need to swap last 2 elements of a list in Haskell
Here is what I've got so far and it doesn't compile.
swapLastTwo :: [a] -> Maybe [a]
swapLastTwo list = case list of
xs:x:y -> Just (xs:y:x)
_ -> Nothing
However this one compiles:
swapFirstTwo :: [a] -> Maybe [a]
swapFirstTwo list = case list of
x:y:xs -> Just (y:x:xs)
_ -> Nothing
What is the difference? I guess there is a problem with these x y xs etc. I am not quite certain with them.
Compiler gives this error
• Couldn't match expected type ‘[[a]]’ with actual type ‘a’
‘a’ is a rigid type variable bound by
the type signature for:
swapLastTwo :: forall a. [a] -> Maybe [a]
at BuiltInList.hs:69:16
• In the second argument of ‘(:)’, namely ‘x’
In the second argument of ‘(:)’, namely ‘y : x’
In the first argument of ‘Just’, namely ‘(xs : y : x)’
• Relevant bindings include
y :: [a] (bound at BuiltInList.hs:71:10)
x :: a (bound at BuiltInList.hs:71:8)
xs :: a (bound at BuiltInList.hs:71:5)
list :: [a] (bound at BuiltInList.hs:70:13)
swapLastTwo :: [a] -> Maybe [a] (bound at BuiltInList.hs:70:1) Failed, modules loaded: none.
Thank you.
Upvotes: 1
Views: 1497
Reputation: 120711
First, note that there's no reason to use case
for this kind of pattern-matching – just match right in the function arguments:
swapFirstTwo :: [a] -> Maybe [a]
swapFirstTwo (x:y:xs) = Just (y:x:xs)
swapFirstTwo _ = Nothing
Now... Haskell lists aren't symmetric. At all. Anything you do with pattern-matching will always focus on the left end on the list, that's why your swapLastTwo
doesn't work. You can only get at the right end by disassembling the whole list (if at all, namely, this only works for finite lists). This can be done through recursion. First write out the base cases:
swapLastTwo [] = Nothing
swapLastTwo [_] = Nothing
swapLastTwo [x,y] = Just [y,x]
For any list longer than that, you need to first pop off elements and then later put them back. Hence the recursive clause
swapLastTwo (x:xs) = (x:) <$> swapLastTwo xs
In case you're wondering what <$>
is: in this case, it simply prepends x
to whatever Just
result the recursive call may yield.
An alternative, kind of brute-force solution would be
swapLastTwo = fmap reverse . swapFirstTwo . reverse
This actually has much worse runtime characteristics in this example, butSee Luke's comment generally, such “compose simple functions instead of doing manual recursion” solutions are often much more effective.
Upvotes: 8
Reputation: 60463
Here's the productive version I mentioned for fun.
swapLastTwo :: [a] -> [a]
swapLastTwo [] = []
swapLastTwo [x] = [x]
swapLastTwo [x,y] = [y,x]
swapLastTwo (x:xs) = x : swapLastTwo xs
It's lazy:
ghci> swapLastTwo [1,2,3,4,5]
[1,2,3,5,4]
>>> swapLastTwo (1:2:3:4:5:undefined)
[1,2,3*** Exception: Prelude.undefined
And you can also do it with Maybe
(which is why I wrote this answer, because I enjoy the asJust
pattern, and the way it can make strict things productive when properly placed — essentially we are codifying some extra structural information that is not clear from the types alone).
swapLastTwo' :: [a] -> Maybe [a]
swapLastTwo' [] = Nothing
swapLastTwo' [x] = Nothing
swapLastTwo' [x,y] = Just [y,x]
swapLastTwo' (x:xs) = (x:) <$> asJust (swapLastTwo' xs)
where
assertJust ~(Just x) = Just x
Which is also lazy
ghci> swapLastTwo' (1:2:3:4:5:undefined)
Just [1,2,3*** Exception: Prelude.undefined
ghci> swapLastTwo [1]
Nothing
In a lazy dependent-typed world, would swapLastTwo'
(with an appropriately accurate type) be lazy without any extra effort, the way swapLastTwo
is here?
Upvotes: 3
Reputation: 26161
I can not make sure if this more efficient than @leftaroundabout solution but just for a variety you may use lists as an applicative functor such as;
swapLastTwo :: [a] -> Maybe [a]
swapLastTwo [] = Nothing
swapLastTwo [_] = Nothing
swapLastTwo xs = Just (concat $ [reverse . drop 2 . reverse, take 2 . reverse] <*> [xs])
*Main> swapLastTwo []
Nothing
*Main> swapLastTwo [1]
Nothing
*Main> swapLastTwo [1,2,3,4,5]
Just [1,2,3,5,4]
Upvotes: 0
Reputation: 36375
When using the cons operator :
, the last item on the right of the colon is a list, while everything to the left is a single item in the list. That is why swapFirstTwo
compiles successfully: the x
and y
from x:y:xs
are individual items, while xs
is a list.
You can confine a pattern match to a two element List by either of these methods:
x:y:[] -> ...
-- or
[x,y] -> ...
Upvotes: 1