Getting started with strings in Haskell
I need to write a function that takes a string of gggggggeeeeetttttt and can count how many times the letter repeats and o开发者_Python百科utput as g7e5t6.
I've only just started Haskell so not really sure at all where to start.
The function group
will group together identical elements in a list. Since a string is a list of characters, we have:
group "ggggeeetttt" = ["gggg","eee","tttt"]
To get the letter, we can use map head
, since head
takes the first element of a string (or any list):
map head ["gggg","eee","tttt"] = ['g','e','t']
Now, we want to know how many elements there are in each substring. This can be done using map length
:
map length ["gggg","eee","tttt"] = [4,3,4]
Let's turn these numbers back into strings:
map show [4,3,4] = ["4","3","4"]
Now we need to combine the original list and length list somehow. We can do this as follows:
zipWith (:) ['g','e','t'] ["4","3","4"]
zipWith
will apply the specified function to paired elements in the two lists. Here I've used (:)
, which adds an element to the start of the list.
This gives you all the building blocks you need to do what you want. For example, this should work, although I haven't tested it:
f s = concat $ zipWith (:) letters lengths
where
groups = group s
letters = map head groups
lengths = map (show . length) groups
Variant with comprehensions, imo a bit prettier (considering previous explanations just code):
ghci> let s = "gggggggeeeeetttttt"
ghci> putStrLn $ concat [head g: show (length g) | g <- group s]
g7e5t6
The best way to start, is to figure out your problem. I would suggest the following method:
- Split the string into substrings of equal characters. So, you need a function which takes a
String
and returns, a list of strings ([String]
).
Example:splitStr "gggggggeeeeetttttt"
should return["ggggggg","eeeee","tttttt"]
Happily, the moduleData.List
already provides such a function, it's namedgroup
. - Calculate the number of repetitions and the letter which is repeated. The number of repetition is simply the length of the sublist, use
length
for that. - Get the repeated character and stick it in front of the number of repetitions. For the repeated character, we may use any one of the list, we take the first one. To calculate the first element of a list (aka string), you can use
head
. Eg.head "abc"
yields'a'
.
To stick this in front of the length, you can use a small helper function andshow
, which turns most stuff into a string. Our helper looks like this:makeRepetitions string = head string : show (length string)
.:
aka cons adds an element to the front of a list. - Apply the helper of step (3) to all elements of the list of string in (1). We use
map
for that.map
applies a function to a list of values and returns the list of results - concat the sublists together to your result. We can use
concat
for this. Actually, we can combine (4) and (5) usingconcatMap
. This function maps a function to a list of values and concats the results - just as we want it.
Now, your code looks like this:
import Data.List
runlength :: String -> String
runlength string = concatMap makeRepetitions (group string)) where
makeRepetitions string = head string : show (length string)
Because that much brackets are annoying, Haskellers often use .
.The dot combines two functions to create a new one. You can think of f = functionA . functionB
is equal to f x = functionA (functionB x)
. Using the dot, we can reformat the program a bit:
import Data.List
runlength :: String -> String
runlength = concatMap makeRepetitions . group where
makeRepetitions string = head string : show (length string)
IMO, This representation is more readable. You can see runlength
as a pipeline. First apply group
, then we use concatMap
to map makeRepetitions
onto the input and concat the results.
Abuse of arrows and pointfree :)
f = group >>> concatMap (head &&& (show . length) >>> uncurry (:))
And here's with applicative:
f = concatMap ((:) <$> head <*> (show . length)) . group
精彩评论