Reputation: 21
I have to make a function neighbours :: [((String, String), Int)] -> String -> [(String, Int)]
This is the function I came up with:
neighbours pairs@((x1,x2):xs) inputWord
| pairs == [] = []
| fst x1 == inputWord = ((snd x1), x2) : (neighbours xs inputWord)
| snd x1 == inputWord = ((fst x1), x2) : (neighbours xs inputWord)
| otherwise = (neighbours xs inputWord)
The output is supposed to be a list of tuples with the strings that were paired with inputWord in the tuple x1, together with the integer x2
The problem is that I get non-exhaustive patterns that I don't believe should be there.
I tried to replace pairs == [] = []
with xs == [] = []
This made the non-exhaustive patterns disappear for when the list was not empty, but it also prevented the function from traversing the last tuple of tuples.
Upvotes: 1
Views: 317
Reputation: 532238
If you define a helper function to do the comparison to inputWord
, the definition of neighbors
becomes much simpler:
-- You can use other, more descriptive names instead.
type Foo = ((String, String), Int)
type Bar = (String, Int)
-- If it seems odd that I'm using [Bar] instead of Maybe Bar, see below
myLookup :: String -> Foo -> [Bar]
myLookup s ((s1, s2), i) | s == s1 = [(s2, i)]
| s == s2 = [(s1, i)]
| otherwise = []
neighbors :: [Foo] -> String -> [Bar]
neighbors [] _ = []
neighbors (x:xs) inputWord = case myLookup inputWord x of
[] -> neighbors xs inputWord
[y] -> y : neighbors xs inputWord
It can be further simplified using concatMap
, which simply concatenates
the results of applying myLookup inputWord
to each element of the input.
neighbors :: [Foo] -> String -> [Bar]
neighbors xs inputWord = concatMap (myLookup inputWord) xs
With this pattern in mind, we can switch to using the stricter Maybe Bar
return type for myLookup
by using mapMaybe
from Data.Maybe
instead of concatMap
.
import Data.Maybe (mapMaybe)
type Foo = ((String, String), Int)
type Bar = (String, Int)
myLookup :: String -> Foo -> Maybe Bar
myLookup s ((s1, s2), i) | s == s1 = Just (s2, i)
| s == s2 = Just (s1, i)
| otherwise = Nothing
neighbors :: [Foo] -> String -> [Bar]
neighbors xs inputWord = mapMaybe (myLookup inputWord) xs
Upvotes: 1
Reputation: 477607
The only pattern you specify is:
neighbours pairs@((x1,x2):xs) inputWord
This thus means that this only works for non-empty lists. Indeed, the pattern ((x1, x2):xs)
"fires" given it matches with a "cons" with (x1, x2)
the first element of the list, and xs
the rest of the elements.
The check pair == []
will never succeed, since the pattern already means that this can never be an empty list.
We can add a clause for the empty list pattern, for example as first pattern to match. Furthermore we can improve the readability of the code by using the (((x11, x12), x2):xs)
pattern for a non-empty list. We then do not need to use fst
and snd
, but can use x11
and x12
directly:
neighbours :: Eq a => [((a, a), b)] -> a -> [(a, b)]
neighbours [] _ = []
neighbours pairs@(((x11, x12),x2):xs) inputWord
| inputWord == x11 = (x11, x2) : tl
| inputWord == x12 = (x12, x2) : tl
| otherwise = tl
where tl = neighbours xs inputWord
For example:
Prelude> neighbours [(('a', 'b'), 1), (('b', 'a'), 2), (('b', 'b'), 3), (('a', 'a'), 4)] 'a'
[('a',1),('a',2),('a',4)]
This function is however a bit "asymetrical" since for the two elements in the tuple, it "prefers" the first element over the second element. If the two items x11
and x12
are both equal to inputWord
, it might make more sense to yield them both.
Upvotes: 2