3

I'm having trouble writing this function that takes a character and a list of characters, then eliminates the last occurrence of that input character in the list. I was able to take out the first occurrence of the input character with my function below:

fun :: Char -> String -> String
fun c (s:ss)
 | s == c   = ss
 | otherwise = s : fun c ss
fun _ [] = []

What I need help on is how I should modify this function to take out the last occurrence of the input character, instead of the first. The result should be something like fun 'c' "abcdccytrc" returning "abcdccytr".

Numeri
  • 1,027
  • 4
  • 14
  • 32
T-Bird
  • 187
  • 1
  • 4
  • 20
  • 5
    How's reversing the string, then using your function? That might not be what you're looking for, but it would work. – Numeri Nov 12 '15 at 23:09
  • I was thinking about this and I did try it out, I was trying to keep it all in one function though. – T-Bird Nov 12 '15 at 23:38
  • 1
    Yeah, that's why I tried to answer in one function. But as Will Ness and chi commented on my answer, there are better solutions. I would prefer it if you switched the accepted answer to @SimonShine 's answer, but thank you! – Numeri Nov 13 '15 at 01:22

2 Answers2

2

Okay, here's what I came up with:

fun :: Char -> String -> String
fun c (s:ss)
 | ((fun c ss) == ss) && (s == c) = ss
 | otherwise                      = s : fun c ss
fun _ [] = []

Essentially, if s == c, and the rest of the string (ss) is unchanged by running this function on it (i.e., it contains no character c), then return the remaining characters.

If this requirement isn't met (i.e., the rest of the string has the character c at least one time), retain the current character and apply the function to the rest of the string.

Aside from this, I think reversing the string and then calling your original function, then reversing it again, like I suggested in a comment, might be more understandable, but that's just opinion.

Numeri
  • 1,027
  • 4
  • 14
  • 32
  • 2
    comparing the result to the original string will make your function run in quadratic time overall. but you don't need to compare to find out the result - it was you who built it in the first place. you just need to signal it to yourself, whether the character was removed there or not. You can return `(String,Bool)`, or an `Either String String` value, for that. – Will Ness Nov 12 '15 at 23:29
  • 3
    It looks very inefficient. Two recursive calls each invocation seem to indicate an exponential complexity. Factoring the recursive calls out should make it quadratic, which is still far from the linear complexity you get by reversing. – chi Nov 12 '15 at 23:53
  • @chi Yes, I know it is very inefficient, but I actually just barely started learning Haskell: I thought of trying to return a boolean to improve this, but I didn't know how to do that in Haskell. That's actually why I suggested reversing the string as well. – Numeri Nov 13 '15 at 01:18
  • @WillNess ... In fact, I actually think the OP should accept Simon's answer below because of those reasons (inefficiency and better coding). – Numeri Nov 13 '15 at 01:20
2

As Numeri suggests, removing the last occurrence by removing the first occurrence in the reversed list is one way:

removeFirst :: Char -> String -> String
removeFirst _ [] = []
removeFirst c1 (c2:cs) = if c1 == c2 then cs else c2:removeFirst c1 cs

removeLast :: Char -> String -> String
removeLast c1 = reverse . removeFirst c1 . reverse

As Will Ness suggests, returning the string in which the last occurrence is removed, and a boolean to indicate whether the current occurrence should be removed or not, is another:

removeLast :: Char -> String -> String
removeLast c1 = snd . remLast
  where
    remLast :: String -> (Bool, String)
    remLast [] = (False, [])
    remLast (c2:cs) =
      case remLast cs of
        (True, cs') -> (True, c2:cs')
        (False, cs') -> if c1 == c2 then (True, cs') else (False, c2:cs')
sshine
  • 15,635
  • 1
  • 41
  • 66