Andreas F
Andreas F

Reputation: 1186

Haskell: How to replace a string's character at a given index?

I am looking for a function that replaces a character at a given index.

For example:

replace 4 '!' "Hello World!"
-- Output: "Hell! World!"

Upvotes: 0

Views: 1060

Answers (3)

chepner
chepner

Reputation: 530940

There are 4 cases to consider:

  1. The list is empty or the index is negative.
  2. The index is too big (greater than or equal to the length of the list).
  3. The list is nonempty and the index is 0.
  4. The list is nonempty and the index is not zero.

In case 1, we return the input unchanged:

replace k _ xs | null xs || k < 0 = xs

(This could be split into two subcases

replace _ _ [] = []
replace k _ xs | k < 0 = xs

)

Case 2 is implicitly handled by recursing until we reach Case 1.

Case 3 is handled by simply returning the replacement character prepended to the tail of the input.

replace 0 c (x:xs) = c : xs

Case 4 is the interesting case: replacing the kth element of a list is the same as prepending the head of the list to the result of replacing the k-1th element of the tail.

replace k c (x:xs) = x : replace (k-1) c xs

When k is too big, we eventually reach Case 1, since xs will become null before k becomes 0, bypassing Case 3.

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476557

You can improve it slighly by using replacement : strAfter, and using a safe tail function to prevent an error if the index is greater than the length of the string:

safeTail :: [a] -> [a]
safeTail [] = []
safeTail (_:xs) = xs

replaceCharAtIndex :: Int -> Char -> String -> String
replaceCharAtIndex index replacement str = strHead ++ replacement : safeTail strAfter
    where (strHead, strAfter) = splitAt index str

One however should be careful: for negative indices, it will replace the first character of the string. For indices that are greater than the length of the string, it will append to the string. Both cases are not per se the desired behavior.

We can alter the behavior by checking for negative indexes, and matching on the empty tail:

replaceCharAtIndex :: Int -> Char -> String -> String
replaceCharAtIndex index replacement str
    | index < 0 = … -- index located before the string
    | (_:xs) <- strAfter = strHead ++ replacement : xs
    | otherwise = …  -- index located after the string.
    where (strHead, strAfter) = splitAt index str

Here we thus have two s to fill in a result for these edge-cases.

Upvotes: 1

Andreas F
Andreas F

Reputation: 1186

In case somebody is looking for such a function or a similar one, I came up with this:

replaceCharAtIndex :: Int -> Char -> String -> String
replaceCharAtIndex index replacement str = strHead ++ [replacement] ++ drop 1 strAfter
  where (strHead, strAfter) = splitAt index str

Usage: replaceCharAtIndex 4 '!' "Hello World!"

Upvotes: 1

Related Questions