ClaireBookworm
ClaireBookworm

Reputation: 101

How would I split a string after the spaces in haskell?

I know Haskell doesn't have loops, so I can't do that. I also know that recursion is "helpful" here, but that's about all I'm aware of. So far, I've gotten a basic type signature, which is

toSplit :: String -> [String]

Basically, it changes from a string into a list of words...

Thanks!

P.S. I want to use the takeWhile and dropWhile functions... not a library...

Upvotes: 2

Views: 4495

Answers (2)

Stefan
Stefan

Reputation: 675

If you want to implement that yourself, first thing you need to do is find the first word. You can do that with:

takeWhile (/=' ') s

If the string starts with delimiter characters you need to trim those first. We can do that with dropWhile.

takeWhile (/=' ') $ dropWhile (==' ') s

Now we have the first word, but we also need the rest of the string starting after the first word, on which we will recurse. We can use splitAt:

(_, rest) = splitAt (length word) s

And then we recurse with the rest of the string and cons the first word onto the result of that recursion giving us a list of all words.

And we need to define the base case, the result for the empty string which will terminate the recursion once there are no more characters.

toSplit :: String -> [String]
toSplit "" = []
toSplit s =
  let word = takeWhile (/=' ') $ dropWhile (==' ') s
      (_, rest) = splitAt (length word) s
  in word : toSplit (dropWhile (==' ') rest)

Edit: There is a bug in the code above and it's not handling an edge case properly.

The bug is that it's calling splitAt on the original s but this gives a wrong result if s has leading spaces:

*Main> toSplit " foo"
["foo","o"]

This should fix the bug:

let trimmed = dropWhile (==' ') s
    word = takeWhile (/=' ') trimmed
    (_, rest) = splitAt (length word) trimmed

That leaves one edge case:

*Main> toSplit " "
[""]

One possible solution is to use a helper function:

toSplit :: String -> [String]
toSplit = splitWords . dropWhile (==' ') 
    where
        splitWords "" = []
        splitWords s = 
          let word = takeWhile (/=' ') s
              (_, rest) = splitAt (length word) s
          in word : splitWords (dropWhile (==' ') rest)

Upvotes: 5

assembly.jc
assembly.jc

Reputation: 2066

I know Haskell doesn't have loops, so I can't do that. I also know that recursion is "helpful" here...

Yes, Haskell use recursion rather then for, while loop structure that in imperative language.

In stead of write recursive function by myself, it is more common use map, foldr (foldl), unfoldr or etc recursively traverse the list. The advantage of these functions is that you can concentrate what want to do in step function. Like your question, is suitable use unfoldr to accomplish it.

Here is an example with takeWhile and dropWhile:

import Data.Char (isSpace)
import Data.List (unfoldr)

toSplit::String->[String]
toSplit = unfoldr step 
    where step [] = Nothing
          step xs = Just (takeWhile (not . isSpace) xs, 
                          (dropWhile isSpace . dropWhile (not . isSpace)) xs) 

But a little bit verbose, use break function may be more readable like:

toSplit' = unfoldr step
    where step [] = Nothing
          step xs = let (word, rest) = break isSpace xs
                    in  Just (word, dropWhile isSpace rest)

Upvotes: 1

Related Questions