2

I would like to delete some elements from a list. I have so far a delete function:

deleteElem :: Int -> [a] -> [a]
deleteElem _ [] = []
deleteElem x zs | x > 0 = take (x-1) zs ++ drop x zs
                | otherwise = zs

And I would like to delete for example two positions, which are stored in a list from a list of elements. So this works fine:

map (deleteElem 2) [["hello", "whatever", "foo", "bar"], ["hello", "whatever", "foo", "bar"], ["hello", "whatever", "foo", "bar"], [hello", "whatever", "foo", "bar"]]

I'll get the result:

[["hello", "whatever", "bar"], ["hello", "whatever", "bar"], ["hello", "whatever", "bar"], [hello", "whatever", "bar"]]

But now I would like to apply deleteElem [2,3]

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Paktwis Homayun
  • 300
  • 3
  • 17

2 Answers2

3

If I am interpreting your question correctly, you are saying that you would like a way to apply your function to a list of indices instead of a single index at a time.

The simplest way I can thing of to do this would be to create another function called deleteElems instead of deleteElem (notice the trailing s.)

deleteElems would be of type [Int] -> [a] -> [a] and it would call on every index.

NOTE: See UPDATE at the bottom for the correct solution (I am leaving this part here so others may learn from my original attempt to solve the problem and why it is incorrect.)

Here is one way to do it:

deleteElems xs zs = foldr (\x z -> deleteElem x z) zs xs

which can be shortened to:

deleteElems xs zs = foldr deleteElem zs xs

Prelude> deleteElems [2,3] [1..10]
[1,4,5,6,7,8,9,10]

or from your example:

Prelude> map (deleteElems [2,3]) [["hello", "whatever", "foo", "bar"], ["hello", "whatever", "foo", "bar"], ["hello", "whatever", "foo", "bar"], ["hello", "whatever", "foo", "bar"]]
[["hello","bar"],["hello","bar"],["hello","bar"],["hello","bar"]]

deleteElems uses foldr to repeatedly call deleteElem to remove the indices in xs from zs. For a more in depth explanation of foldr, see How does foldr work?.

UPDATE:

as per comments the above implementation of deleteElems is actually incorrect because when given a list of indices, say [2,4,6], it will first remove index 2, return a new list, then remove index 4 on the new list and return a newer list, then remove index 6 on the newer list. This process is not commutative, meaning changing the order of the indices, or giving deleteElems the indices [6,4,2] will not do the same thing.

I a way to get the anticipated behaviour (removing the given indices from the original list) using the intersect function from Data.List:

deleteElems xs zs = foldr1 intersect $ map ($ zs) $ map deleteElem xs

This new version of deleteElems applies deleteElem using each index in xs, creating a list of length xs number of lists of deleteElem functions curried to each specific index. Then the map ($ zs) applies each of the curried deleteElem functions to zs, yielding a list of lists, where each inner list is just deleteElem applied to one of the indices and zs. Finally, we use intersect from Data.List to find the list with all correct elements removed.

Also, I still definitely recommend checking out How does foldr work?.

Community
  • 1
  • 1
DJG
  • 6,413
  • 4
  • 30
  • 51
  • 1
    Try out `deleteElems [2, 4, 6] [1..10] == deleteElems [6, 4, 2] [1..10]`. Is it the same? Should it be the same? – bheklilr Sep 25 '13 at 13:19
  • 2
    You could sort the Indices-List descending, then the problem would also be solved. – kaan Sep 25 '13 at 18:56
0

Let's define deleting a element at position i as splitting the list at position i, then recombining the list without the head element of the second part of the list. This is what you have implemented anyway.

Now, deleting multiple elements is like deleting elements from the second part of the list using the same procedure - if we can specify the rest of the indices relative to the head of the second part.

This calls for a simple definition of deleteElems:

deleteElems is = del [i-p | (p:i:_) <- tails $ sort $ 0:is] where
  del [] xs = xs
  del is xs = (r++) $ concatMap tail ss where
     (r:rs,ts) = unzip $ zipWith splitAt is $ xs:ts
     ss = filter (not . null) $ rs ++ [last ts]

Here the list comprehension builds a list of indices relative to the previous index. Then del uses zipWith splitAt to split the list at the specified positions. Note xs:ts represents the list of "second parts" on all iterations, and r:rs is the list of the "first parts" on all iterations. Clearly, r is the first "first part", and is included intact. The rest is trimmed with tail. Filtering empty lists is needed in case the same index is specified more than once in the list of indices.

Sassa NF
  • 5,306
  • 15
  • 22