I wanted to split a string on newlines and I was surprised that I could not find the inverse function to intercalate "\n"
. That is, a function that splits a string into pieces on new lines (or according to some other predicate).
Note that lines
and words
do something different. For example
intercalate "\n" (lines "a\n") == "a"
There is a similar function function splitOn
in the split library. I could also write such a function myself directly:
splitOn :: (a -> Bool) -> [a] -> [[a]]
splitOn p = map reverse . g []
where
g rs [] = [rs]
g rs (x:xs) | p x = rs : g [] xs
| otherwise = g (x : rs) xs
but I wonder if it could be constructed more easily using only functions from base.
As Nikita Volkov points out, the restriction "only Prelude functions" doesn't make it very easy, but here's one option:
splitWhen p [] = [[]]
splitWhen p l = uncurry (:) . fmap (splitWhen p . drop 1) . break p $ l
This uses the Functor
instance for (,) a
as an alternative to Control.Arrow.second
(to avoid a messier lambda expression), which works without importing anything (ghci says "defined in 'GHC.Base'"), but I'm not sure if that actually belongs in the Prelude
, since I can't find it in the Haskell Report.
Edit: Being allowed to use other functions from base
doesn't even help me that much. In any case I would use second
instead of fmap
because I think it adds a little clarity. With unfoldr
, using a Maybe
for the seed to distinguish the end of the string from an empty part (or empty line in the example):
import Control.Applicative ((<$>))
import Control.Arrow (second)
import Data.List (unfoldr)
splitWhen p = unfoldr (second check . break p <$>) . Just
where
check [] = Nothing
check (_:rest) = Just rest
-- or cramming it into a single line with 'Data.Maybe.listToMaybe'
splitWhen' p =
unfoldr (second (\rest -> tail rest <$ listToMaybe rest) . break p <$>) . Just
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With