Replace in Haskell

Haskell seems to be missing a String replace function. Text.Regex.subRegex seemed like overkill. So I wrote one. It actually works on any list, not just Strings.

replace :: Eq a => [a] -> [a] -> [a] -> [a]
replace [] _ _ = []
replace s find repl =
    if take (length find) s == find
        then repl ++ (replace (drop (length find) s) find repl)
        else [head s] ++ (replace (tail s) find repl)

Some examples:

*Main> replace "hello" "h" ""
*Main> replace "hello" "l" ""
*Main> replace "hello" "x" ""
*Main> replace "100,000,000" "," "hello"
*Main> replace "100,000,000" "," ""
*Main> replace [1,2,3] [1] [9]
*Main> replace [4,5,6,1,2,3,7,8,9,2,3,6,5,4,1,2,3] [1,2,3] [10]

If this function is already in the standard libraries somewhere or if this can be improved in some way please leave a comment to let me know. Thanks!

11 Replies to “Replace in Haskell”

  1. “head” and “tail” should be used sparingly, because “head” is a partially defined function (see “head []”).

    subst _    _  [       ] = []
    subst from to xs@(a:as) = 
        if isPrefixOf from xs 
            then to ++ drop (length from) xs
            else a : subst from to as
        where isPrefixOf as bs = and $ zipWith (==) as bs
  2. sorry, too smart (sic!) for my own good. my “isPrefixOf” is broken, instead use “Data.List.isPrefixOf”

  3. Thanks jethr0. I see you’ve moved the list to act on to the last of the arguments. Someone else on #haskell suggested that too, to improve composability.

  4. Yeah, the general rule is to always order parameters to functions in order of increasing likelihood of variation. In this case, for instance, it seems more likely that the same substitutions will be used on many different lists, rather than many different substitutions on the same initial list.

  5. The first function here is missing a check that can cause infinite recursion if an empty `find` list is given

     replace "abc" "" "A"

    it needs something like…

    replace _ [] list = list

    The second function given only replaces the first instance of the string, and if the `find` list is empty then it is equivalent to

    > to:xs

    the fourth line could be changed to…

    then to ++ subst from to (drop (length from) xs)

    but then it has the same problem as the first function, and needs another check

    substr [] _ list = list -- or something like that

    I chose to write my replace as follows

    replace::(Eq a) => [a] -> [a] -> [a] -> [a]
    replace [] newSub list = join newSub list
    replace oldSub newSub list = _replace list where
    	_replace list@(h:ts) = if isPrefixOf oldSub list
    		then newSub ++ _replace (drop len list)
    		else h : _replace ts
    	_replace [] = []
    	len = length oldSub

    I decided that an empty match list would mean inserting the new element between all of the elements in the list, but maybe the new element should be inserted on the beginning of the list as well.

    join::[a] -> [a] -> [a]
    join glue [h] = [h]
    join glue (h:ts) = h : glue ++ join glue ts
    join _ [] = []

    another function that works nicely with replace

    strip::(Eq a) => [a] -> [a] -> [a]
    strip old = replace old []
  6. replace “abc” “a” “b” –> “bb”. I don’t think that’s what you want, is it?

    There’s a solution over here:

    replace :: (Eq a) => [a] -> [a] -> [a] -> [a]
    replace _ _ [] = []
    replace old new xs@(y:ys) =
    case stripPrefix old xs of
    Nothing -> y : replace old new ys
    Just ys’ -> new ++ replace old new ys’

    Notice that the haystack argument is the last argument, instead of the first. This is so that you can do function composition nicely, for example:

    quote = replace “” “>” . replace “&” “&”

    If the haystack argument were first instead of last, the definition of quote would get a lot messier.

  7. Oh the irony… your comment code doesn’t escape my comment correctly! Trying this again:

    quote = replace “<” “&lt;” . replace “>” “&gt;” . replace “&” “&amp;”

  8. @Doug: Only for delusional criminals, who think one could „own“ and „sell“ information. Reality doesn’t care about them, and we should neither.

  9. `isPrefixOf find s` should perform better than `take (length find) s == find` as it only needs to traverse `find` once.

    (`isPrefixOf` is found in `Data.List`)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.