Split string to a list of strings in Clean

北城以北 提交于 2019-12-11 02:57:49

问题


Because of the limited amount of resources, I need to propose a question here. I have been struggling with functional programming, the endless Haskell tutorials don't really help me. So what I want to achieve, in Clean language, is to split a string like " car cow cat " to a list of strings ["car","cow","cat"]. Can you provide me a detailed answer (does not have to be complete code), on how to iterate through this string, and especially the part when the newly constructed strings are added to the list?


回答1:


I'm going to offer a simple solution. There are infinitely better ways of doing this in Haskell, but it's the simplest I can think for someone new in functional programming, without using any specifically Haskell function like takeWhile, or even any folds and maps...

You basically want to simulate iterating over a list, so here is what I suggest:

  1. Define a function that will take a string and a split-by character. This function will return a list of strings - spliton :: String -> Char -> [String]

  2. To move over the list, we'll want to gobble up characters until we hit one of our splitting characters. We'll also want to save the word we've saved up until now, and the entire list of words. For that, we'll define a subfunction that will save the states

    spliton' :: String -> Char -> String -> [String] -> [String]

    spliton' [] _ sofar res = res ++ [sofar]

    I've also included the simplest clause - an empty string. When our string is empty, we'll just want to return what we have saved so far.

  3. Now lets move on to our actual recursive function: If we hit our split character, we'll add the string we have saved so far to the list and restart with an empty current-state string If we don't hit the split character, we'll add the character to the current-state string

    spliton' (currchar:rest) splitby sofar res
         | currchar==splitby = spliton' rest splitby "" (res++[sofar])
         | otherwise = spliton' rest splitby (sofar++[currchar]) res
    

So, to summarize our code:

spliton :: String -> Char -> [String]
spliton source splitchar = spliton' source splitchar [] []

spliton' :: String -> Char -> String -> [String] -> [String]
spliton' [] _ sofar res = res ++ [sofar]
spliton' (currchar:rest) splitby sofar res
         | currchar==splitby = spliton' rest splitby "" (res++[sofar])
         | otherwise = spliton' rest splitby (sofar++[currchar]) res

Note: This will not however get rid of the empty string - meaning if you have many superfluous spaces - you'll get them added to the list. I'll leave you to think how to handle that case - hope this can help you get started.




回答2:


Let's split this up in several sub-problems:

  1. Make a list of characters from the string so that we can easily apply pattern matching.
  2. Scrape the initial part of the list (as long as possible with only spaces or only not-spaces), and only keep it when it is not whitespace.
  3. Repeat the second step while the list is non-empty.

The first thing can be done using fromString. For the second and third step, we define a helper function:

scrape :: [Char] -> [String]
scrape [] = []
scrape cs=:[c:_]
| isSpace c = scrape (dropWhile isSpace cs)
| otherwise = [toString word:scrape rest]
where
    (word,rest) = span (not o isSpace) cs

The first alternative is the base case to match the empty list. The second alternative matches the whole list cs with a first element c. If the first character is a space, we recursively (step 3) call the same function on the same list without the initial part of spaces. If the first character is not a space, we use span :: (a -> Bool) [a] -> ([a], [a]) to split the list in the initial part that is a word, and the rest. We store the word using toString as a string, and recursively call scrape for the rest of the list.

Now, we only need a wrapper to make this a function with the type String -> [String]:

split :: String -> [String]
split s = scrape (fromString s)
where
    scrape :: [Char] -> [String]
    scrape [] = []
    scrape cs=:[c:_]
    | isSpace c = scrape (dropWhile isSpace cs)
    | otherwise = [toString word:scrape rest]
    where
        (word,rest) = span (not o isSpace) cs

Note that you can easily abstract from the delimiter, by passing a character d and replacing isSpace c with c == d and (not o isSpace) by ((<>) d). Alternatively, you can choose to not pass a character d but a function isDelim :: Char -> Bool. You then get isDelim c and (not o isDelim), respectively.



来源:https://stackoverflow.com/questions/26357128/split-string-to-a-list-of-strings-in-clean

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!