Reputation: 61
(this is a followup question to this one, which has the missing definitions)
Trying to create a function that can swap two elements in a list of tuples. for example if I had:
[(NW, Just 1),(N, Just 2),(NE, Just 3),(W, Just 4),(M, Just 5),
(E, Just 6),(SW, Just 7),(S, Just 8),(SE, Nothing)]
and I wanted to swap the second elements of S and SE to get
[(NW, Just 1),(N, Just 2),(NE, Just 3),(W, Just 4),(M, Just 5),
(E, Just 6),(SW, Just 7),(S, Nothing),(SE, Just 8)]
How would I go about doing this? I tried creating a function swapper
,
swapper :: (Eq a, Eq b) => a -> b -> b -> [(a,b)] -> [(a,b)]
swapper x y z ((a,b):xs) | x == a = ((a,z):xs)
| z == b = ((a,y):xs)
| otherwise = swapper x y z xs
But this returns
[(S,Nothing),(SE,Nothing)]
I can see that I'm not storing the initial tuples or looping after finding one of my goal states. However I'm not sure how to go about doing this in Haskell.
To give a little more context in case required, the four inputs of the function are
(swapper p (label p (Grid b)) (Nothing) b)
where
p = position I want to move to the Nothing space -- (S -> SE)
label p (Grid b) = returns current label of p -- (S = Just 8)
Nothing = passing in Nothing because I was getting errors
when trying to do z == Nothing
b = current board -- ([(NW, Just 1),(N, Just 2),(NE, Just 3),
-- (W, Just 4),(M, Just 5),(E, Just 6),
-- (SW, Just 7),(S, Just 8),(SE, Nothing)])
Upvotes: 3
Views: 307
Reputation: 20873
How about creating a function that can split a list into predecessors, matching element and successors?
splitOn :: (a -> Bool) -> [a] -> ([a], Maybe a, [a])
splitOn predicate xs =
case break predicate xs of
(us, []) -> (us, Nothing, [])
(us, (m:vs)) -> (us, Just m, vs)
> splitOn (== 3) [1..5]
([1,2],Just 3,[4,5])
> splitOn (== 7) [1..5]
([1,2,3,4,5],Nothing,[])
Then you can split, swap and recombine:
import Data.Maybe (maybeToList)
swap :: Eq a => a -> a -> [a] -> [a]
swap a b xs =
let (us, m, xs') = splitOn predicate xs
(vs, n, ws) = splitOn predicate xs'
in us ++ (maybeToList n) ++ vs ++ (maybeToList m) ++ ws
where
predicate x = x == a || x == b
> swap 3 4 [1..5]
[1,2,4,3,5]
> swap 7 4 [1..5]
[1,2,3,5,4]
Not that this version assumes that an element is contained in the list at most once.
An updated version which swaps the first element (if the element is contained more than once) is
swap' :: Eq a => a -> a -> [a] -> [a]
swap' a b xs =
let (us, m, xs') = splitOn isFirst xs
(vs, n, ws) = case m of
Nothing -> (xs', Nothing, [])
Just m' -> splitOn (== (other m')) xs'
in us ++ (maybeToList n) ++ vs ++ (maybeToList m) ++ ws
where
isFirst x = x == a || x == b
other x = if x == a then b else a
> swap' 3 4 (3:[1..5])
[4,1,2,3,3,5]
> swap' 7 4 (3:[1..5])
[3,1,2,3,5,4]
> swap' 3 3 (3:[1..5])
[3,1,2,3,4,5]
Upvotes: 3
Reputation: 1229
As you wrote, you don't want return ((a,z):xs)
if x==a
. You want to process the rest of the list: ((a,z):swapper x y z xs)
. Same with z==b
. Then the algorithm ended with an error: Non-exhaustive patterned in function swapper
.
So I added pattern for empty list: swapper x y z [] = []
. Then the swapper
returned only part of the list. So I added (a,b):
also before the swapper
to the clause otherwise
.
Now your algorithm looks like this:
swapper :: (Eq a, Eq b) => a -> b -> b -> [(a,b)] -> [(a,b)]
swapper x y z ((a,b):xs) | x == a = ((a,z):swapper x y z xs)
| z == b = ((a,y):swapper x y z xs)
| otherwise = (a,b):swapper x y z xs
swapper x y z [] = []
And returns the list as you expected:
[(NW,Just 1),(N,Just 2),(NE,Just 3),(W,Just 4),
(M,Just 5),(E,Just 6),(SW,Just 7),(S,Nothing),(SE,Just 8)]
I assume that we speak about something like this:
Upvotes: 2