简体   繁体   中英

Haskell, reversing partially a list

I am trying to create a function that takes a list and, if the length of the list is at least 4, it reverses the first and second element and the last two elements of said list. Imagine the input is [1,2,3,4,5,6] this should give me [2,1,3,4,6,5].

I am trying to figure it out but it's being a pain in the ass.

So, this is what I've got:

transf :: [a] -> [a]
transf [] = []
transf xs | length xs >= 4 = ???
          | otherwise = xs

So I thought that I could do a aux function that would only reverse those elements and then calling it on the transf function. Any help?

Usually it is better to use pattern matching over using length . If for instance a list has an infinite length, length will loop forever. So wen can define a function:

transf :: [a] -> [a]
transf (a:b:c:d:es) = ...
transf x = x

So here we define a pattern (a:b:c:d:es) which is the pattern to define a list with at least four elements ( a , b , c , and d ) and the rest of the list es .

Now in that case we know that we have to reverse the first two elements of the list, so the result is something like b : a : ... . Now the question is how we calculate the rest of the list. We can not add c and d to the list, since it is possible that es is the empty list, in which case, we want to reverse d and c . This can result in a lot of cases.

We can however use an as-pattern here, to match both the first two items, as well as the rest of the list (and the rest of the list should contain at least two items). Like:

transf :: [a] -> [a]
transf (a:b:cs@(_:_:_)) = b : a : ...
transf x = x

So now we only have to fill in the ... . This needs to reverse the last two items. Instead of letting transf have all the fun, we can define a new function swaplast :: [a] -> [a] which swaps the last items. What to do if the list has no two elements can be decided by ourselves since we know that the list with which we will call the function will have at least two elements. We can for instance decide that empty lists and singleton lists, are simply returned without a modification. So we can implement such function as:

swaplast :: [a] -> [a]
swaplast [] = []
swaplast [a, b] = [b, a]
swaplast (a:bs) = a : swaplast bs

So then we obtain the implementation:

transf :: [a] -> [a]
transf (a:b:cs@(_:_:_)) = b : a : swaplast cs
transf x = x

swaplast :: [a] -> [a]
swaplast [] = []
swaplast [a, b] = [b, a]
swaplast (a:bs) = a : swaplast bs

We write four functions. That makes reasoning a lot simpler and the functions easier to maintain:

reverseFirst :: [a] -> [a]
reverseFirst (x:y:xs) = y : x : xs
reverseFirst xs       = xs

reverseLast :: [a] -> [a]
reverseLast [x, y] = [y, x]
reverseLast (x:xs) = x : reverseLast xs
reverseLast xs     = xs

lengthAtLeast :: [a] -> Int -> Bool
lengthAtLeast xs n = not $ null $ drop (n - 1) xs

Now, with those helpers, it's easy to write the fourth and last function:

transf :: [a] -> [a]
transf xs
  | xs `lengthAtLeast` 4 = reverseLast $ reverseFirst xs
  | otherwise           = xs

We can drop lengthAtLeast if we pattern match:

transf :: [a] -> [a]
transf (a:b:xs@(_:_:_)) = b : a : reverseLast xs
transf xs               = xs

Exercise

Make the amount of characters reversed at the end and start customizeable:

transform :: Int -> Int -> [a] -> [a]
transform = ...

It should work like this:

transform 1 1 [1,2,3,4] = [1,2,3,4]
transform 1 2 [1,2,3,4] = [1,2,4,3]
transform 1 3 [1,2,3,4] = [1,4,3,2]
transform 1 4 [1,2,3,4] = [1,2,3,4] -- list isn't 5 elements long
transform 2 2 [1,2,3,4] = [2,1,4,3]

Something like this, works only for finite lists

-- works only for finite lists
f :: [a] -> [a]
f ls
    | n <= 4     = ls
    | otherwise  = let (a:b:hs, [x, y]) = splitAt (n - 2) ls 
                   in  b:a:(hs ++ [y, x])
    where
        n = length ls

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM