I am new to Haskell and I am trying to figure out how to make a function:
shift:: Eq a => a -> [a] -> Int -> [a]
shift x (h:t) z
<
For you are new to Haskell, it's better to break the problem into simpler subproblems. One trick to solve your problem is:
thinking of "insert" instead of "shift". Pseudo code:
- remove x from the list
- insert x back to the list at the proper position based on given n
You already implement delete, lets take advantage of it. Enjoy simple code:
-- assume x is in the list, otherwise your problem would not make sense
shift :: Eq a => a->[a]->Int->[a]
shift x lst n = if n == 0 then delete x lst
else insert x (n + (position x lst)) (delete x lst)
Sub-problems are:
1. Delete x from the list: you did that
delete :: Eq a => a->[a]->[a]
delete x [] = []
delete x (head:tail) = if x == head then tail else head : delete x tail
2. find the position of x in the list
-- assume x in the list
position :: Eq a => a->[a]->Int
position x (head:tail) = if x == head then 1 else 1 + position x tail
3. -- insert x into the list at nth position
insert :: a->Int->[a]->[a]
insert x n (head:tail) = if n <= 1 then x : (head:tail)
else head : (insert x (n-1) tail)
insert x n lst = if n >= length lst then lst ++ [x]
else insert x n lst
Your precondition is that x is in the list, otherwise it does not make sense. If you want to include the case x is not in the list, twist the code.
Have fun.
As @WillemVanOnsem suggests, you may want to try writing a function that shifts a target element one space to the right. Even this simplified problem might very well be challenging!
See if you can implement a direct recursive version. It could be similar in structure to your delete
function, except it will swap two elements instead of dropping an element at the critical point. (Answer at the bottom -- look for the definition of simpleShiftRight
.)
Once you've done that, try working through this alternative approach which has the advantage that it will more easily generalize to solving your original problem.
First, using delete
isn't very helpful, because delete
"forgets" where the element originally was. For example, both of the following:
delete '.' "abc.def"
delete '.' "abcde.f"
yield "abcdef"
, and it's not clear how to use this result to, say, shift the period one position to the right of where it was.
Instead, what you'd really like to do is break a string up into the parts before and after the target element. That is, you'd like to define a function split
that works like this:
> split '.' "abc.def"
("abc","def")
> split '.' "abcde.f"
("abcde","f")
With this result, shifting the period becomes much easier.
For example, if we wanted to shift the period one position to the right, we could start by defining a function
pairRight :: ([a], [a]) -> ([a], [a])
that works like this:
> pairRight ("abc","def")
("abcd","ef")
> pairRight ("abcde","f")
("abcdef","")
and a function
rejoin :: a -> ([a], [a]) -> [a]
that works like this:
> rejoin '.' ("abcd","ef")
"abcd.ef"
> rejoin '.' ("abcdef","")
"abcdef."
and combine them:
> rejoin '.' (pairRight (split '.' "abc.def"))
"abcd.ef"
> rejoin '.' (pairRight (split '.' "abcde.f"))
"abcdef."
to get a function that shifts a character one space to the right.
Now, split
can be defined in terms of the library function break
, like so:
split :: Eq a => a -> [a] -> ([a], [a])
split x xs = let (a, _:b) = break (==x) xs in (a,b)
Can you implement the functions pairRight
and rejoin
? They shouldn't be too hard, but if you get stuck the answer's at the bottom.
You might also want to try defining split
from scratch without using break
. It's a slightly tricky recursive function. If you start with an "obvious" approach:
split :: Eq a => a -> [a] -> ([a], [a])
split x (y:ys) | x == y = (..., ys)
| otherwise = split x ys
split _ [] = error "split: target not found"
you'll run into a problem. It's not clear how to fill in the ...
because you've sort of thrown away the start of the list in the recursion. Hopefully you've already learned that one way around this is to introduce an extra parameter to keep track of the list elements already processed and define a function:
split' :: Eq a => a -> [a] -> [a] -> ([a], [a])
split' x ls (r:rs) = ...
where x
is the element we're looking for, ls
is the set of elements on the left side of the list that we've already processed (where we didn't find a copy of x
), and (r:rs)
is the right side of the list that we're still processing.
If you need a further hint, here's a template:
split' x ls (r:rs) | x == r = (..., ...)
| otherwise = split' x (...) rs
split' _ _ [] = error "split: target not found"
Can you fill in the ...
here? If you can, then you can define:
split :: Eq a => a -> [a] -> ([a], [a])
split x xs = split' x [] xs
Once you have split
, pairRight
, and rejoin
defined, you should be able to combine them into a function:
shiftRight :: Eq a => a -> [a] -> [a]
that can shift a target element one position to the right.
If you get stuck, here's a complete definition of shiftRight
and its
helpers:
shiftRight :: (Eq a) => a -> [a] -> [a]
shiftRight x xs = rejoin x (pairRight (split x xs))
-- alternative definition of split:
-- split :: Eq a => a -> [a] -> ([a], [a])
-- split x xs = let (a, _:b) = break (==x) xs in (a,b)
split :: Eq a => a -> [a] -> ([a], [a])
split x xs = split' x [] xs
split' :: Eq a => a -> [a] -> [a] -> ([a], [a])
split' x ls (r:rs) | x == r = (ls, rs)
| otherwise = split' x (ls ++ [r]) rs
split' _ _ [] = error "split: target not found"
pairRight :: ([a], [a]) -> ([a], [a])
pairRight (ls, r:rs) = (ls ++ [r], rs)
rejoin :: a -> ([a], [a]) -> [a]
rejoin x (ls, rs) = ls ++ [x] ++ rs
In this version, trying to shiftRight
a target that isn't in the list or that is already in the rightmost position will give an error. You might want to try fixing that. (Note that split
could return a Maybe [a]
, yielding Nothing
if the target isn't found; it also shouldn't be too tough to modify pairRight
so that it does nothing if the pair is already shifted right as far as it can go.)
If this seems like a lot of bother for a simple problem, I guess it is. In real code, an experienced Haskell programmer would probably write a direct recursive version:
simpleShiftRight :: (Eq a) => a -> [a] -> [a]
simpleShiftRight x (y:z:rest) | x == y = z:y:rest
| otherwise = y : simpleShiftRight x (z:rest)
simpleShiftRight _ rest = rest
or this one that uses break
:
simpleShiftRight :: (Eq a) => a -> [a] -> [a]
simpleShiftRight x xs = case break (==x) xs of
(ls, y:z:rs) -> ls ++ z:y:rs
_ -> xs
Both versions are succinct, handle all the corner cases, and are "obviously correct". The downside, as previously mentioned, is that this version isn't quite as easy to generalize to your original problem.
The version above does generalize pretty easily -- you just need to replace pairRight
with a more sophisticated pair shifting function. For example, defining:
pairRightN :: Int -> ([a],[a]) -> ([a],[a])
pairRightN n (ls, r:rs) | n > 0 = pairRightN (n-1) (ls ++ [r], rs)
pairRightN _ (ls, rs) = (ls, rs)
allows you to handle all positive values for n
(i.e., all right shifts, no matter how big). It's not too hard to further generalize it to handle left shifts, too.