tags:

views:

1010

answers:

7

Trying to learn Haskell. I am trying to write a simple function to remove a number from a list without using built-in function (delete...I think). For the sake of simplicity, let's assume that the input parameter is an Integer and the list is an Integer list. Here is the code I have, Please tell me what's wrong with the following code

areTheySame :: Int -> Int-> [Int]

areTheySame x y | x == y = []
                | otherwise = [y]

removeItem :: Int -> [Int] -> [Int]

removeItem x (y:ys) = areTheySame x y : removeItem x ys
A: 
Justin Smith
Where is an empty list added and why would that be an error? Moreover, according to my Haskell interpreter, your code contains syntax errors.
ahans
you are right about the syntax being all wrong. What I meant about the adding [] to the list was that "1 :[] : [ 0, 1, 2]" will not work because you cannot add the empty list to an integer list. And his code did the equivalent of that.
Justin Smith
You can add an empty list to an integer list, you just cannot do this using the `:` operator as correctly noted by Chris Lutz, as this operator adds one element to the beginning of a list.
ahans
+7  A: 

The : operator doesn't do what you think it does:

(:) :: a -> [a] -> [a]

It takes an item of type a and adds it to the beginning of a list of type a. You're using it to join two lists of type a. For that, you need to use ++:

(++) :: [a] -> [a] -> [a]

Also, if you make a recursive function, it needs an ending condition. So try this:

removeItem _ [] = []
removeItem x (y:ys) = areTheySame x y ++ removeItem x ys

That way, when you get to the end of the list, the function will stop recursing.

Chris Lutz
If you actually apply your explanation to your code and change the last `:` for an `++` your code would be correct, currently it's producing lists of lists (`areTheySame` always returns a list and you add this to the beginning of a list using `:`).
ahans
If you keep the original type definition, you get a type error.
ahans
You're right - I copied his line and forgot to change the part I had just talked about changing. Either way, it's probably not the best way to do things, but it certainly works.
Chris Lutz
+2  A: 

This is the minimal fix to make your example work:

removeItem :: Int -> [Int] -> [Int]
removeItem _ []     = []
removeItem x (y:ys) = areTheySame x y ++ removeItem x ys

First, you need to use ++ to concatenate lists, as the : operator used by you adds just one element to the beginning of a list (it can neither be used to add lists with one element nor to add empty lists). You first compare the head of the list (y) to the item you want to remove and correctly return the item or an empty list using areTheySame. Then you want to recursively continue using removeItem on the rest of the list (ys). The resulting list needs to be concatenated using ++.

Second, as Chris Lutz noted, you need an ending condition when you reach the end of the list. By adding this line, Haskell knows what to do with an empty list (that is, nothing, just return an empty list).

As Chuck said, you can simplify the code for this task by having removeItem not delegate the task of the comparison, but compare itself and throw away the element if it should be removed, otherwise keep it at the list head (using :). In any case, continue recursively with the rest of the list.

-- nothing can be removed from an empty list
-- ==> return empty list and stop recursion
removeItem _ []     = []

-- if the list is not empty, cut off the head in y and keep the rest in ys
--                    if x==y, remove y and continue
removeItem x (y:ys) | x == y    = removeItem x ys    
--                    otherwise, add y back and continue 
                    | otherwise = y : removeItem x ys
ahans
+11  A: 

The others are right that the problem is the : operator. I would say that your areTheySame function that returns a list is the wrong approach anyway, though. Rather than switch to the ++ operator, a better implementation of that function would be:

removeItem _ []                 = []
removeItem x (y:ys) | x == y    = removeItem x ys
                    | otherwise = y : removeItem x ys

As you can see, this is a pretty simple implementation. Also, consing like this is much less taxing for your program than appending a bunch of lists together. It has other benefits as well, such as working lazily.

Chuck
+2  A: 

For reference, you may be interested in seeing how it's done in delete from Data.List.

You could leave areTheySame as is, but you'd then need to use concatMap in removeItem to collapse the empty lists:

removeItem :: Int -> [Int] -> [Int]
removeItem x xs = concatMap (areTheySame x) xs

or equivalently

removeItem :: Int -> [Int] -> [Int]
removeItem x = concatMap (areTheySame x)

Note that the types of your functions could be more general:

areTheySame :: (Eq a) => a -> a -> [a]
removeItem  :: (Eq a) => a -> [a] -> [a]

This allows removal of items from lists of any type for which == is defined, not just Int.

Greg Bacon
+4  A: 

You can also do this as a list-comprehension

delete :: Eq a => a -> [a] -> [a]
delete deleted xs = [ x | x <- xs, x /= deleted ]
Dagititis
A: 

I believe all the solutions given so far work differently than Data.List.delete, which only deletes the first member.

deleteFromList x xs =
  case break (==x) xs of
    (_,[]) -> xs
    (notsat,sat) -> notsat ++ tail sat

was my attempt to delete only the first member (haven't peaked at D.L yet).

It's unclear which behavior the top poster wants.

tphyahoo