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.