I've been given the following question in my coursework;
Define a function
flatten :: [(Char,Int)] -> String
that flattens a list of pairs of characters and digits to a string. For example:
flatten [('a',5),('b',4),('c',2)]
"a5b4c2"
flatten [('d',9),('d',3)]
"d9d3"
My problem is that whenever I attempt to define this function i get a type error relating the the [(Char, Int)] input. For example;
Couldn't match type '(Char, Int)' with '[Char]'
Expected type: [[Char]]
Actual type: [(Char, Int)]
I've tried more ways of writing this definition in more ways than I can count, so I don't have any particular code to show, or any particular error ( I kept getting different ones...so many). All i have so far is;
flatten :: [(Char, Int)] -> String
flatten [] = []
i figure my next line should go something like;
flatten ???? = concat (????)
but I have no idea what to put in place of these question marks and Google search/class notes give no examples to follow.
any ideas?
Well it is clear that in the case the list is not empty, it is of the form ((ca,cb):cs) with ca the Char, cb the Int and cs the remainder of the list [(Char,Int)].
In that case we can simply construct a string for that sequence ca:(show cb) with show :: Show a => a -> String we convert an integer to its String counterpart. Next we concatenate the flattening of remainder of the list to that string, so:
flatten ((ca,cb):cs) = ca:(show cb ++ flatten cs)
Or in full:
flatten :: [(Char, Int)] -> String
flatten [] = []
flatten ((ca,cb):cs) = ca:(show cb ++ flatten cs)
First of all, we try to create a String from a (Char, Int). If we can do that we've almost done, since we can do that for all (Char, Int). So let's transform a single (Char, Int):
flattenSingle :: (Char, Int) -> String
flattenSingle (c, i) = c : show i
Now, we need to do that for all entries:
flattenAll :: [(Char, Int)] -> [String]
flattenAll xs = map flattenSingle xs
And last, but not least, we need to concat the [String] (which is a [[Char]]) to a String (which is a [Char]):
flatten :: [(Char, Int)] -> String
flatten xs = concat (flattenAll xs)
And we're done. How did we do that? Well, we've started with a much easier problem, namely how to get a single String from a (Char, Int), and used that to get our result.
Here's everything in a single function:
flatten = concat . map flattenSingle
where
flattenSingle (c, i) = c : show i
Since concat . map f is often used, there's a function for that, called concatMap:
flatten :: [(Char, Int)] -> String
flatten = concatMap flattenSingle
where
flattenSingle (c, i) = c : show i
Let’s think about what goes into flatten and what comes out of it.
flatten, takes a list of pairs of type: (Char, Int) and produces a [Char]; it produces a list from an existing list. Does this ring a bell?
flatten xs = [ c | (char, int) <- xs, c <-[char] ++ show int]
We can sequentially deconstruct each pair in the given list; for each pair, we turn each component into a string so we can concatenate them. Now we have a string for each pair, we just need to take each character out to produce the final string.
flatten = mconcat. map (\(c,i) -> [c] ++ show i)
You might also use foldl to make your intention very clear:
a=[('a',5),('b',4),('c',2)]
f b (x,y)=b++[x]++(show y)
result=foldl f "" a
Or you can make it a one-liner:
Solution 1:
foldl (\b (x,y)->b++[x]++(show y)) "" a
Solution 2:
concat $ map (\(x,y)->[x]++show y) a
Solution 3: (Being more efficient compared to solution 1)
foldr (\(x,y) b->b++[x]++(show y)) "" a
Related
I'm trying to parse a string "A1B2C3D4" to [('A',1),('B',2),('C',3)] in Haskell.
I'm trying to use a map like this map (\[a, b] -> (a :: Char, b :: Int)) x where x is the string.
This is the function signature I need to follow :: String -> [(Char, Int)].
Unfortunately i'm getting type mismatches, can anyone give any hint how to solve this?
I'm in the right direction?
Well, map is really meant for applying a single function to every element of something, one-by-one. Splitting the string how you want requires context (knowing the next letter), so map isn't the best choice here.
However, you said your solution is required to be in terms of map. It can be done, but it's a bit roundabout. I couldn't think of any way to make map split the actual string, but it can certainly be used to transform it to the correct type:
isDigit :: Char -> Bool
isDigit c = elem c ['0'..'9']
split :: String -> [(Char, Int)]
split str = let chars = filter (not . isDigit) str
nums = filter isDigit str
zipped = zip chars nums in
map (\(a, b) -> (a, read [b])) zipped
There's a few problems.
The pattern [a, b] in map (\[a, b] -> ...) x only matches lists of two elements, so the compiler infers that the function \[a, b] -> ... has type [r] -> s for some r and s.
The compiler knows that map has the type (u -> v) -> [u] -> [v], so it unifies u with [r] and v with s to infer the type [[r]] -> [s] for map (\[a, b] -> ...).
This means x must have type [[r]], that is, it must be a list of lists. But you want x to be a String which is a synonym for [Char]. The compiler can't unify [[r]] and [Char], so it objects.
You're attempting to "cast" a to a Char and b to an Int like you would in C, but you can't do that in Haskell. If you want to convert a Char like '1' into the Int 1, you need a different approach, like read, which you can use to convert from a String to an Int.
Here's some advice. Don't use map. Try writing a recursive solution instead.
Start by considering a few cases:
what does myParser "" return?
what does myParser "a1" return?
what does myParser [a,b] return?
what does myParser (a:b:cs) return?
I came up with this but it's really not safe as it doesn't handle incorrect string like "AA11B2C3"!
splitingN :: Int -> [a] -> [[a]]
splitingN _ [] = []
splitingN n l
| n > 0 = take n l : splitingN n (drop n l)
| otherwise = error "uhhhhhh"
tuplify :: String -> (Char, Int)
tuplify a = (head a, read $ tail a)
stringy :: String -> [(Char, Int)]
stringy s = tuplify <$> splitingN 2 s
> stringy "A1B2C3D4" == [('A',1),('B',2),('C',3),('D',4)]
A much nicer way but still not fully safe would be:
stringy :: [a] -> [(a, a)]
stringy [] = []
stringy (a : b : rest) = (a, b) : splitting rest
stringy [a] = error "uhhhhh"
Should really check if a and b from (a : b : rest) are indeed Char & Int. Also this uses recursion and you mentioned using map so might not suffice and it's pretty polymorphic in it's types.
As others have pointed out, you need to understand that map applies the given function over each member of the list. Once you understand that, you will realize that there is no way you can get the conversion you want by applying a function on the existing list.
This leads to realization that once you have a list of "A1", "B2",... then you can take these and convert it using a map function.
I have given the code for function below. The split' function is not safe as it can blow up in lot of cases (expected a string which can be perfectly split into 2 chars). I am also using the function digitToInt, for which you need to import Data.Char. You did say you want no import, in that case you can write your own digitToInt function, look into the library code, it is fairly straightforward.
import Data.Char
split' :: String -> [String]
split' [] = []
split' (x:y:xs) = (x:[y]) : split' xs
convert :: String -> [(Char, Int)]
convert input = map (\s -> (s!!0 , digitToInt(s!!1) )) $ split' input
I'm trying to take a list of tuples, with type [(Char, Int)] and convert it to a string.
For example:
tupToStr [('j',9),('x',1),('f',3)]
"j9x1f3"
My attempt is below. My problem is the cons operator requires all values to have the same type. So I can't do 'j' : 9 : [] for example. Therefore you'll see a placeholder function, intToChar, which would ideally convert an Int to a Char. Is there a simple way of doing this conversion, that fits concisely into my existing function? If not, how would you write a function to do the conversion in question?
tupToStr :: [(Char, Int)] -> String
tupToStr [] = []
tupToStr (x:xs) = (fst x) : intToChar(snd x) : tupToStr xs
tupToStr = concatMap (\(c,i) -> c:show i)
We can use the intToDigit :: Int -> Char function from Data.Char for that. Furthermore it is more elegant to use pattern matching in the tuple instead of using fst and snd. So we can rewrite it to:
import Data.Char(intToDigit)
tupToStr :: [(Char, Int)] -> String
tupToStr [] = []
tupToStr ((c, n):xs) = c : intToDigit n : tupToStr xs
Note that this will only work given n is always in the range 0..15 (intToDigit will use 'a'..'f' for values greater than nine). In case you want to process values outside the range, you can use show, but this will of course return a String. With the above code, we get:
Prelude Data.Char> tupToStr [('j',9),('x',1),('f',3)]
"j9x1f3"
Change the bottom line to:
tupToStr ((a,b):xs) = a : show b ++ tupToStr xs
I am having trouble with a couple list problems. The first is supposed to insert a string between strings, as long as the strings are the same length, ie inserts "da" [("so","ed"),("c",""),("",""),("mo","le")] would return ["sodaed" "da" "modale"]
so far I have
inserts :: String -> [(String, String)] -> [String]
inserts str pairs = [[x,str,z] | (x,z) <- pairs, length (x) == length (z)]
inserts' :: String -> [(String, String)] -> [String]
inserts' [] [] = []
inserts' str [(x:xs),(y:ys)]
| (length y) == (length x) = (x, str, y) : inserts' str [xs,ys]
| otherwise = inserts' str [x,ys]
I am getting a type error though matching [char] to string
You're really close! I'm pretty sure the error message you're getting is something different than not being able to match [Char] and String though, because these are the same!
Let's see what happens when we remove the type signature on inserts (I'm doing this in ghci, but you can of course try it via a file as well):
Prelude> let inserts str pairs = [[x,str,z] | (x,z) <- pairs, length x == length z]
Prelude> :t inserts
inserts :: [a] -> [([a], [a])] -> [[[a]]]
OK, that's a pretty general type. As you might know, String is the same as [Char]. So if we substitute Char for a in the type of inserts, and replace [Char] by String, we can see that the inserts can specialize to String -> [(String,String)] -> [[String]].
So the arguments match, but the result has one level of lists too many. That's pretty logical, since x,str and z are strings, so [x,str,z] is a list of strings. All that's needed is to concatenate these three strings into one.
Either you can append the lists 'by hand', using x ++ str ++ z as the expression on the left side of the list comprehension, or you could use concat [x,str,z] to do it; concat flattens a list of lists (of Characters in this case) into a list.
For your second try, you can use something similar instead of the three-tuple of strings (x, str, y), do you see what you need to do?
For the type to be correct, I think for the first function should be:
inserts :: String -> [(String, String)] -> [String]
inserts str pairs = [x ++ str ++z | (x,z) <- pairs, length (x) == length (z)]
or
inserts :: String -> [(String, String)] -> [[String]]
inserts str pairs = [[x,str,z] | (x,z) <- pairs, length (x) == length (z)]
, depending on your needs.
if i say i have two strings or character lists,
list1 = ["c","a","t"]
list2 = ["d","o","g"]
and if i read a string using Input Output "ct" and pass it to the function,the function should return "dg".
Please give me any idea about such a function.
I would consider taking those two lists, zipping them together, use Data.Map.fromList to create a lookup Map, then map over the input String and use the Map to work out what to replace them with.
I'll first assume list1 and list2 have type [Char] (i.e. String), since that's what your text seems to indicate (your code has them as [String]s -- if you really want this, see the generalized version in the addendum).
If you zip the two lists, you end up with a list of pairs indicating how to translate characters. In your example, zip list1 list2 = [('c','d'), ('a','o'), ('t','g')]. We'll call this our lookup list. Now consider the function lookup:
lookup :: Eq a => a -> [(a, b)] -> Maybe b
In our case, we can specialize this to
lookup :: Char -> [(Char, Char)] -> Maybe Char
so we have something that takes a character and a lookup list and returns a substituted character if the input character is in the lookup list (otherwise a Nothing). Now we just need to glue the things we've found together: We essentially need to map \c -> lookup c lookupList (more elegantly written as flip lookup) over the input string while throwing out any characters not found in the lookup list. Well, enter mapMaybe:
mapMaybe :: (a -> Maybe b) -> [a] -> [b]
It does exactly what we want. Now your function can be written as
replace :: String -> String -> String -> String
replace list1 list2 = mapMaybe ((flip lookup) (zip list1 list2))
You'll need to import Data.Maybe.
Addendum, for when you understand the above: Observe how what we did above had nothing to do with the fact that we were working with lists of characters. We could do everything above with lists of any type for which equality makes sense, i.e. for (lists of) any type which is an instance of the Eq typeclass (cf the signature of lookup above). Moreover, we don't have to translate from that type to itself -- for example, each character above could be sent to say, an integer! So really, we can write
replace :: (Eq a) => [a] -> [b] -> [a] -> [b]
replace list1 list2 = mapMaybe ((flip lookup) (zip list1 list2))
and now our function works as long as list1 is a list of something for which equality makes sense. Replacement of characters just becomes a special case.
A quick example:
> replace "cat" "dog" "ct"
"dg"
> replace "cat" [1,2,3] "ct"
[1,3]
For two string you may do as follows:
patt :: String -> String -> String -> String
patt (x : xs) (y : ys) p'#(p : ps)
| p == x = y : patt xs ys ps
| otherwise = patt xs ys p'
patt _ _ [] = []
main :: IO ()
main = do
putStrLn $ patt "cat" "dog" "ct"
EDIT FOR CLARITY: I know what the 'head of empty list' error is and why it is thrown. What I don't know is why there is no error when I use 'elem' but there is if I use 'mElem'. That's the only change I make to cause the error.
If I use the 'Prelude.elem' function the program runs but with one small error. I wrote my version of 'elem' (mElem) to counter this error. I looked at the source code for 'elem' and wrote my function in a similar style. However, the program crashes due to a 'head of empty list error' resulting from the function 'getExisting'
genTupleCount :: [F.Record] -> [(String, Int)] -> [(String, Int)]
genTupleCount [] tuples = tuples
genTupleCount (x:xs) tuples | mElem (F.club x) (map fst tuples) = genTupleCount xs $ getNewTuples tuples existing
| otherwise = genTupleCount xs $ (F.club x, 1):tuples
where
existing = getExisting x tuples
getExisting :: F.Record -> [(String, Int)] -> (String, Int)
getExisting x tuples = head $ filter ((==F.club x).fst) tuples
getNewTuples :: [(String, Int)] -> (String, Int) -> [(String, Int)]
getNewTuples old e = (fst e, 1 + (snd e)):(delete e old)
mElem :: String -> [String] -> Bool
mElem _ [] = False
mElem str (x:xs) = (map toLower str) == (map toLower x) || mElem str xs
As Matt Fenwick said, you're assuming there is an element that satisfies your condition. I suggest using find instead:
getExisting :: F.Record -> [(String, Int)] -> Maybe (String, Int)
getExisting x = find ((==F.club x).fst)
This handles the case of there not being any such element by returning Nothing, and let's you skip mElem entirely; just check the result of getExisting to find out whether there is any such element, and if there is, what its value is.
As to why using mElem instead of elem causes your program to crash, it is because it does not verify that there is an element that satisfies the condition getExisting searches for. getExisting doesn't normalise the capitalisation as mElem does, so if nElem returns True only because of its case-folding, then the call to getExisting will occur, and it will perform head on the empty list, because filter will find no elements matching getExisting's stricter condition.
The find solution avoids this potential for error, since it only has the condition in one place.
Your function getExisting assumes that the result of filter ((==F.club x).fst) tuples has at least one element.
Example:
Prelude> head [3,4]
3
Prelude> head []
*** Exception: Prelude.head: empty list
It looks like switching to mElem causes an empty list to eventually be passed to head. Apparently, this doesn't happen using elem.