2

I want make a function which when given a string eg "ab" and "cdabd" when it will be used on these two strings it will output "cd"

I have this up to now

takeUntil :: String -> String -> String
takeUntil [] [] = []
takeUntil xs [] = []
takeUntil [] ys = []
takeUntil xs ys = if contains xs ys then -- ???? I get stuck here. 

the contains function is one in which I had defined previously(this whole function should be case insensitive) contains function:

contains :: String -> String -> Bool
contains _ [] = True
contains [] _ = False
contains xs ys = isPrefixOf (map toLower ys) (map toLower xs) || contains (tail(map toLower xs)      (map toLower ys)
Nicholas
  • 125
  • 2
  • 12
  • 1
    Your definition is a bit wrong, did you mean `takeUntil` on the last 4 lines? – bheklilr Oct 23 '14 at 14:02
  • yes I did. it should be takeUntil – Nicholas Oct 23 '14 at 14:04
  • Also, it'd be really helpful if you could post the code for `contains`, or at least the type signature and a more specific description of what it does. – bheklilr Oct 23 '14 at 14:07
  • Im guessing that I may be needing a helper function to define what should happen if xs is in ys. the helper function should cut off everything before the two strings are matched i.e. "ab" "cdab" = "cd" – Nicholas Oct 23 '14 at 14:15
  • There are a lot of ways to solve this problem, but I think using `contains` will make things difficult. For one thing, what if I passed in `takeUntil "ab" "abcdab"`? Using `contains`, this would return `""` instead of `"cdab"`. Using `isPrefixOf` directly would be a better choice as @JosEdu has done. – bheklilr Oct 23 '14 at 14:21

2 Answers2

3

There is a lot of ways to do this, but continuing with your path, try the following:

import Data.List

takeUntil :: String -> String -> String
takeUntil [] [] = []                           --don't need this
takeUntil xs [] = [] 
takeUntil [] ys = [] 
takeUntil xs (y:ys) = if   isPrefixOf xs (y:ys)
                      then []
                      else y:(takeUntil xs (tail (y:ys)))

Some outputs:

takeUntil "ab" "cdabd"
"cd"

takeUntil "b" "cdabd"
"cda"

takeUntil "d" "cdabd"
"c"

takeUntil "c" "cdabd"
""

takeUntil "xxx" "cdabd"
"cdabd"

EDIT:

The OP wants the function to be case-insensitive.

Well, again you can do that in lot of ways. For example you can write a lowerCase function like (i think you already have it in Data.Text):

import qualified Data.Char as Char

lowerCase :: String -> String
lowerCase [] = []
lowerCase (x:xs) = (Char.toLower x):(lowerCase xs)

And then use it like (maybe ugly and not very practical):

takeUntil (lowerCase "cd") (lowerCase "abcDe")
"ab"

That is the result you expect.

Also, you can use that lowerCase function inside takeUntil:

-- ...
takeUntil xs (y:ys) = if  isPrefixOf (lowerCase xs) (lowerCase (y:ys))
-- ...

So, you can just do:

takeUntil "cd" "abcDe"
"ab"

Anyway, i think the best option is that one @bheklilr suggested. Make your own isPrefixOfCaseless function.

I hope this helps.

JosEduSol
  • 5,268
  • 3
  • 23
  • 31
  • Thankyou but the thing is I need to make it case insensitive so that if "cd" "abcDe" it will stil give me "ab" – Nicholas Oct 23 '14 at 14:21
  • try using `toLower` from `Data.Char` on both the `xs` and `y` – Arnon Oct 23 '14 at 14:23
  • The first case of `takeUntil` is subsumed by the second, hence it can be removed. Also, `tail (y:ys)` can be simplified to just `ys`. – chi Oct 23 '14 at 14:26
  • 2
    @Nicholas If you want to perform this check without case sensitivity, I'd suggest making a `isPrefixOfCaseless` (shorter name than `isPrefixOfCaseInsensitive`) function that locally maps `toLower` over its arguments. You'll end up duplicating some operations, but in this case it's better to get it working rather than getting it to run as fast as possible. – bheklilr Oct 23 '14 at 14:30
  • 1
    Right, `caselessPrefixOf pre list = isPrefixOf (map toLower pre) (map toLower list)` is going to be valuable. You can use other functions from Data.List too; `takeUntil xs ys = maybe ys fst $ find (isPrefixOf xs . snd) $ zip (inits ys) (tails ys)` brings it down to a one-liner with convoluted logic and you can just swap in `caselessPrefixOf` to that. – CR Drost Oct 23 '14 at 14:43
  • I like @bheklilr suggestion. Anyway i edited my answer. – JosEduSol Oct 23 '14 at 15:08
0

Within the numerous approaches to define takeUntil, consider using Data.Text functions as follows,

takeUntil :: String -> String -> String
takeUntil sep txt =  unpack $ fst $ breakOn (pack sep) (toCaseFold $ pack txt)

Note that pack converts a String to a Text, whereas uncpak does the inverse; toCaseFold for case insensitive operations; breakOn delivers a pair where the first element includes text until the first (possible) match.

Update

This approach covers tests already suggested, yet it does not preseve original String for instance here,

takeUntil "e" "abcDe"
"abcd"

A work around this involves for instance the splitting by index at the break point.

elm
  • 20,117
  • 14
  • 67
  • 113