4

I am attempting to create an extension method for the Array type to allow for removing an item from an array

extension Array {
    func remove(item: AnyObject) {
        for var i = self.count - 1; i >= 0; i-- {
            if self[i] == item {
                self.removeAtIndex(i)
            }
        }
    }
}

On the test condition if self[i] == item, I get the following error: 'T' is not convertible to 'MirrorDisposition'

I've tried many different things, which include:

  • Using generics: remove<T>(item: T)
  • Using the === operator, which just gives the error 'T' does not conform to protocol 'AnyObject'

I'm new to Swift, so this is where my knowledge runs out. Any suggestions would be greatly appreciated.

Albert Bori
  • 9,832
  • 10
  • 51
  • 78

5 Answers5

3

You are getting an error because the compiler can't guarantee that the element stored in your array can be compared with ==. You have to ensure that it the contained type is Equatable. However, there is no way to add a method to a generic class that is more restrictive than the class itself. It is better to implement it as a function:

func removeItem<T: Equatable>(item: T, var fromArray array: [T]) -> [T] {
    for i in reverse(0 ..< array.count) {
        if array[i] == item {
            array.removeAtIndex(i)
        }
    }
    return array
}

Or you could add it as a more generic extension:

extension Array {
    mutating func removeObjectsPassingTest(test: (object: T) -> Bool) {
        for var i : Int = self.count - 1; i >= 0; --i {
            if test(object: self[i]) {
                self.removeAtIndex(i)
            }
        }
    }
}

Then you can do this:

var list: [Int] = [1,2,3,2,1]
list.removeObjectsPassingTest({$0 == 2})
drewag
  • 93,393
  • 28
  • 139
  • 128
  • + 1 for the generic extension, it works more in-line with the other functions available on Array: `filter`, `map`, etc. – Albert Bori Aug 09 '14 at 00:41
  • You cannot call a `mutating` method (`removeAtIndex`) on a constant (`array`) – newacct Aug 09 '14 at 01:58
  • @newacct yes, that is true, how is that relevant? – drewag Aug 09 '14 at 01:59
  • @drewag: That's what you did in your first code sample before you edited it. You now changed it to `var`, which is probably not what you want because then only the local copy of the array in the function is changed. – newacct Aug 09 '14 at 02:03
  • @newacct, ya I changed it to return the modified array. Thanks for pointing out the constant array issue. – drewag Aug 09 '14 at 02:04
  • @drewag: oh right you return it. Though you could also make it `inout` and not have to return it. – newacct Aug 09 '14 at 02:07
  • @newacct ya I considered that, but i prefer to make the function more "functional" (pun unavoidable). Plus `inout` feels dirty to me – drewag Aug 09 '14 at 02:10
2

The way I would write this function is like this:

mutating func remove<U where U : Equatable>(item: U) {
    for var i = self.count - 1; i >= 0; i-- {
        if self[i] as U == item {
            self.removeAtIndex(i)
        }
    }
}

Be sure to decorate your function with mutating.

I would use a different type parameter U since you can't really change Array's type parameter to be Equatable. Then I would try to cast the items to U to do the comparison.

Of course, this will fail if you try to call this function with an Array that is instantiated with a non-equatable type.

tng
  • 4,286
  • 5
  • 21
  • 30
  • 1
    Important note, with this implementation, the compiler will not complain if you pass a type that doesn't match the type contained in array, but it will crash at runtime because the `as` conversion will fail. Better to at least make that an optional casting. – drewag Aug 09 '14 at 00:14
  • 1
    @MariánČerný: you cannot form a range with end < start – newacct Aug 09 '14 at 01:56
  • @newacct Makes sense. I will delete my note not to confuse people. – Marián Černý Aug 09 '14 at 05:56
1

This is not a solution but if you are trying to remove an item from an array, this is how I do it:

var numbers = [1, 2, 3, 4, 5] if let possibleIndex = find(numbers, 1) { numbers.removeAtIndex(possibleIndex) }

Shan
  • 3,057
  • 4
  • 21
  • 33
0

I don't know what is MirrorDispsition, but I think the problem is that you can't always equate two objects in Array, because they are not guaranteed to be equatable.

Edit: Look at tng's solution. It will only work with equatable items, though.

MirekE
  • 11,515
  • 5
  • 35
  • 28
  • If I understand the '===' operator correctly, it would compare whether the two items reference the same object, not whether the objects value's are equal. So, I'm not sure why '===' is not working. – Albert Bori Aug 08 '14 at 22:51
  • 1
    “AnyObject can represent an instance of any class type.” ..and the object from the array does not have to be AnyObject (instance of a class type). It can be a closure, struct, etc. – MirekE Aug 08 '14 at 22:57
0

The error message is confusing. The problem why it does not work is because Swift compiler can not find == operator for Array's element type T. For this to work T would need to conform to Equatable protocol.

Marián Černý
  • 15,096
  • 4
  • 70
  • 83