Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement the inverse of "intercalate" (splitting a string into pieces on a character) using functions from "base"?

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.

like image 307
Petr Avatar asked Dec 26 '22 15:12

Petr


1 Answers

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
like image 129
raymonad Avatar answered Feb 02 '23 01:02

raymonad