简体   繁体   中英

Is there a way to get a 'split' function in Haskell to accept two different types of input?

I am trying to create a function split that can take either [Int] and Int or [Char] Char to split either a list of integers on an integer given or split a string on a character given. Ie

Main> split [1,2,3,0,4,5,0,0,7,8,9] 0
[[1,2,3],[4,5],[7,8,9]]
Main> split "Mary had a little lamb" ' '
["Mary","had","a","little","lamb"]

I've tried using Either and (Eq a) but it still doesn't seem to work. Below is what I've tried doing using class instances but I know very little about this and get the error Haskell 98 does not support multiple parameter classes . The best way I think I'd understand it would be to use pattern matching or list comprehensions. Any help much appreciated.

class Split a where
  split :: (Eq a) => [a] -> a -> [a]

instance Split [Char] Char where
  split [] c     = [""]
  split (x:xs) c
    | x == c    = "" : (split xs c)
    | otherwise = (x : head (split xs c)) : tail (split xs c)

instance Split [Int] Int where
  split [] n = []
  split (x:xs) n
    | x == n = [] : (split xs n)
    | otherwise = (x : head (split xs n)) : tail (split xs n)

I can get the split function to work with strings and characters but not lists of integers.

You need a polymorphic function split

split :: (Eq a) => [a]->a->[[a]]

Implementation is simple

split [] _    = [[]]
split (x:xs) c
  | x == c    = [] : (split xs c)
  | otherwise = (x : head subSplit) : tail subSplit
        where
           subSplit = split xs c

EDIT I suggest different implementation.

split :: Eq a => [a] -> a -> [[a]]
split x c = map reverse $ split' x c []
   where
      split' :: Eq a => [a] -> a -> [a] -> [[a]]
      split' [] _ a = [a]
      split' (x:xs) c a 
         | x == c    = a : split' xs c []
         | otherwise = split' xs c (x:a)

Just to contribute with an other approach. This solution uses foldr . I think it is quite neat but less undestable than @talex's

split :: (Eq a) => [a] -> a -> [[a]]
split l c = foldr f acc l
   where acc = [[]]
         f a t@(i@(x:_):xs) = if a == c then []:t else (a:i):xs -- Case when the current accumulator is not empty
--                                           |          |- cons a to current accumulator
--                                           |- start a new accumulator
         f a t@([]:xs)      = if a == c then t    else [a]:xs -- Case when the current accumulator is empty. Usefull when two separators are together
--                                           |          |- cons a to current accumulator
--                                           |- Don't start a new accumulator, just continue with the current

Just correct solution.

split :: Eq a => [a] -> a -> [[a]]                                          
split xs delim = go $ dropWhile (== delim) xs                               
  where                                                                     
    go [] = []                                                              
    go xs = let (tok, rest) = break (== delim) xs                           
             in tok : go (dropWhile (== delim) rest)

Data.List.Split.splitOn (available from the split package) is close:

> splitOn [0] [1,2,3,0,4,5,0,0,7,8,9]
[[1,2,3],[4,5],[],[7,8,9]]
> splitOn " " "Mary had a little lamb"
["Mary","had","a","little","lamb"]

Your split :: Eq a => [a] -> a -> [[a]] would be

split lst d = filter (not.null) $ splitOn [d] lst

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM