Madi
Madi

Reputation: 97

Split string/list with a char or int

I'm trying to make a function in Haskell to split a string at a certain char and a list at a certain number.

For doing this the splitAt function is exactly what I need for numbers, but I can't give a char with this function.

E.g.

splitAt 5 [1,2,3,4,5,6,7,8,9,10]

gives

([1,2,3,4,5],[6,7,8,9,10])

that is exactly what I needed with the 5 in the left side of the tuple. But now I want to do this with a char and a string. But splitAt only takes and int for the second argument. I want

splitAt 'c' "abcde"

resulting in

("abc", "de")

I looking for something in the direction of

splitAt (findIndex 'c' "abcde") "abcde"

but the function findIndex returns something of the type Maybe Int and splitAt needs an Int. Then I tried the following

splitAt (head (findIndices (== 'c') "abcde")) "abcde"

This is a possible solution but it returns the following

("ab","cde")

with the c on the wrong side of the tupple. You can add succ to c but what will the result be if the char is a Z.

Is there an easy way to modify to make

splitAt (findIndex 'c' "abcde") "abcde"

work?

Upvotes: 3

Views: 1259

Answers (4)

dave4420
dave4420

Reputation: 47042

Here's a different way, that doesn't involve messing around with list indices.

break is nearly what you want. Let's reuse it. You want the matching element to be included at the end of the first output list, instead of at the start of the second.

import Control.Arrow ((***))

breakAfter :: (a -> Bool) -> [a] -> ([a], [a])
breakAfter p xs = map fst *** map fst $ break snd (zip xs $ False : map p xs)

How this works:

  1. Transform our input list into a list of pairs (zip). The first element of each pair is taken from the original list. The second element of the pair is a Bool stating whether the previous element of the list is the one we're looking for. This is why we say False : map p xs --- if we just said map p xs, we would reproduce exactly the behaviour of break. Sticking the extra False in at the start is the important bit.
  2. Reuse break. Our condition is encoded in the second element of each pair.
  3. Throw away all those Bools. We don't need them any more.

Upvotes: 0

dreamcrash
dreamcrash

Reputation: 51393

You can use the fromMaybe function to take the result from Maybe, for example:

splitlist = splitAt (fromMaybe 0 (findIndex 'c' "abcde") "abcde")

fromMaybe :: a -> Maybe a -> a

The fromMaybe function takes a default value and Maybe value. If the Maybe is Nothing, it returns the default values; otherwise, it returns the value contained in the Maybe. (source).

With the default value set to 0, if your findIndex return Nothing the result of splitAt will be ("",list), for the same case but with default value set to length list the finally result it will be (list,"").

Upvotes: 2

josejuan
josejuan

Reputation: 9566

Given c :: Char and s :: String, you can write some as

splitAt ((1+) $ fromJust $ findIndex (==c) s) s

but

  1. you get a exception if c is not into s
  2. you traverse s two times

A Maybe alternative is

maybe Nothing (\x -> splitAt (1+x) s) (findIndex (==c) s)

you can set "else value" (Nothing in my example).

You can write your own function as

splitAt' :: Char -> String -> (String, String)
splitAt' _ [] = ("", "")
splitAt' c (x:xs) | c == x = ([c], xs)
                  | True   = (x:cs, ys) where (cs, ys) = splitAt' c xs

Then, you get (s, "") if not c in s.

Upvotes: 1

AndrewC
AndrewC

Reputation: 32455

You can use findIndex, just unwrap the Maybe and add one:

import Data.List

splitAfter :: (a-> Bool) -> [a] -> ([a],[a])
splitAfter this xs = case findIndex this xs of
    Nothing -> (xs,[])
    Just n -> splitAt (n+1) xs

giving, for example

*Main> splitAfter (=='c') "abcde"
("abc","de")

Maybe is a handy datatype for encoding failure in a way that's easy to recover. There's even a function maybe :: b -> (a -> b) -> Maybe a -> b to use a default value and a function to handle the two cases separately:

splitAfter' :: (a-> Bool) -> [a] -> ([a],[a])
splitAfter' this xs = maybe (xs,[]) 
                  (\n -> splitAt (n+1) xs) 
                  (findIndex this xs)

which also works. For example

*Main> splitAfter' (==5) [1..10]
([1,2,3,4,5],[6,7,8,9,10])

Upvotes: 2

Related Questions