142
extension Array {
    func removeObject<T where T : Equatable>(object: T) {
        var index = find(self, object)
        self.removeAtIndex(index)
    }
}

However, I get an error on var index = find(self, object)

'T' is not convertible to 'T'

I also tried with this method signature: func removeObject(object: AnyObject), however, I get the same error:

'AnyObject' is not convertible to 'T'

What is the proper way to do this?

KTPatel
  • 1,212
  • 4
  • 19
  • 24
Snowman
  • 31,411
  • 46
  • 180
  • 303
  • Try removing the `T where` from your your method declaration. So just `func removeObject`. This question is related: http://stackoverflow.com/questions/24091046/unable-to-use-contains-within-a-swift-array-extension – ahruss Jul 24 '14 at 16:24

15 Answers15

168

As of Swift 2, this can be achieved with a protocol extension method. removeObject() is defined as a method on all types conforming to RangeReplaceableCollectionType (in particular on Array) if the elements of the collection are Equatable:

extension RangeReplaceableCollectionType where Generator.Element : Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func removeObject(object : Generator.Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }
}

Example:

var ar = [1, 2, 3, 2]
ar.removeObject(2)
print(ar) // [1, 3, 2]

Update for Swift 2 / Xcode 7 beta 2: As Airspeed Velocity noticed in the comments, it is now actually possible to write a method on a generic type that is more restrictive on the template, so the method could now actually be defined as an extension of Array:

extension Array where Element : Equatable {
    
    // ... same method as above ...
}

The protocol extension still has the advantage of being applicable to a larger set of types.

Update for Swift 3:

extension Array where Element: Equatable {
    
    // Remove first collection element that is equal to the given `object`:
    mutating func remove(object: Element) {
        if let index = index(of: object) {
            remove(at: index)
        }
    }
}

Update for Swift 5:

extension Array where Element: Equatable {
    
    /// Remove first collection element that is equal to the given `object` or `element`:
    mutating func remove(element: Element) {
        if let index = firstIndex(of: element) {
            remove(at: index)
        }
    }
}
multitudes
  • 2,898
  • 2
  • 22
  • 29
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    Perfect, you gotta love Swift (2). I really like how with time more things get possible and stuff gets simplified – Kametrixom Jun 28 '15 at 21:25
  • 1
    Good point, in many ways the fact that the answer is still technically correct, just no longer idiomatic, is even worse – people will come, read the answer, think a free function is the right way to solve it since it’s a highly rated answer. Pretty ugly scenario. Will post to meta. – Airspeed Velocity Jul 02 '15 at 11:21
  • Actually, rereading the answer – it is now factually incorrect in 2.0. You _can_ now write a method on a generic type that is more restrictive on the template. So editing to point this out is probably appropriate. – Airspeed Velocity Jul 02 '15 at 11:24
  • @AirspeedVelocity: But you still cannot write an extension method for `Array` which (for example) applies only for arrays of `Equatable` items – or can you? – Martin R Jul 02 '15 at 11:37
  • Yes, as of 2.0b2 you can write `extension Array where T: Equatable`. Though _not_ `extension Array where T == Int`. But still it’s better to do it as a protocol extension if you can, as you did. – Airspeed Velocity Jul 02 '15 at 11:42
  • 1
    @AirspeedVelocity: Wow, I missed that. Is it covered in the release notes? – Martin R Jul 02 '15 at 11:44
  • Yes but it was easy to miss as it was in the bug fixes section ("Extensions to generic types can now be further constrained by placing additional protocol requirements on the type parameters of the generic type. (21142043 )”) – Airspeed Velocity Jul 02 '15 at 11:47
  • @AirspeedVelocity: Thanks. Seems that I did not read the release notes thoroughly. Actually this helped me to update http://stackoverflow.com/a/27218964/1187415 for Swift 2, as I could not figure out how to achieve that with a protocol extension. (Perhaps you know a better answer?) – Martin R Jul 02 '15 at 11:55
  • This is by far the more concise answer. Don't forget the add the operators :-) `func -= (inout lhs: Array, rhs: T) { lhs.remove(rhs) }` – Leslie Godwin Nov 01 '15 at 05:38
  • 1
    If you want the same functionality as ObjC (i.e. removes all matching objects instead of only the 1st one), you can change "if" to "while" – powertoold Nov 12 '15 at 00:24
  • @pxpgraphics Swift 2 runs on iOS 7 and later: http://stackoverflow.com/questions/30825638/swift-2-0-minimum-system-version-requirement-deployment-target. – Martin R Mar 20 '16 at 21:28
  • True, however `removeAtIndex()` is iOS 9 only while `indexOf()` is supported in iOS 7 – pxpgraphics Mar 20 '16 at 21:33
  • @pxpgraphics: `removeAtIndex()` is part of Swift 2 and available on iOS 7 and later. If the online help states something different then the help is wrong. Just try it in the iOS 7 Simulator. – Martin R Mar 21 '16 at 07:45
  • @MartinR I believe the Apple Documentation is wrong, which is very unfortunate as this is the canonical source of info for iOS developers. **Function:** `@warn_unused_result func indexOf(element: Self.Generator.Element) -> Self.Index?` **Availability:** iOS (9.0 and later) – pxpgraphics Mar 21 '16 at 16:00
  • @MartinR Again, for `removeAtIndex(:)`— **Function:** `mutating func removeAtIndex(index: Int) -> Element` **Availability:** iOS (8.0 and later) – pxpgraphics Mar 21 '16 at 16:03
  • 1
    @pxpgraphics: Yes, the availability annotations are often wrong. I can only recommend to file a bug report at Apple. – Martin R Mar 21 '16 at 16:11
  • This didn't work for my list of CLLocation objects, any ideas please? (Swift 3) – Apqu Jun 30 '16 at 14:48
  • @MartinR Is it applicable for an array of enum? If it is so, How it behave? Is it legitimate to use "enum random: Equatable" I couldn't conceptualize the equable concepts – venky Sep 21 '16 at 14:10
  • 2
    The Swift 3 version is great, but I would rename its declaration slightly to `remove(object: Element)` in order to comply with the [Swift API design guidelines](https://swift.org/documentation/api-design-guidelines/) and avoid verbosity. I have submitted an edit reflecting this. – swiftcode Oct 12 '16 at 16:07
  • Be careful with this removing just the first match.. ["cat", "cat", "dog"], remove("cat") -> ["cat", "dog"] – Alexandre G Mar 18 '18 at 23:44
67

You cannot write a method on a generic type that is more restrictive on the template.

NOTE: as of Swift 2.0, you can now write methods that are more restrictive on the template. If you have upgraded your code to 2.0, see other answers further down for new options to implement this using extensions.

The reason you get the error 'T' is not convertible to 'T' is that you are actually defining a new T in your method that is not related at all to the original T. If you wanted to use T in your method, you can do so without specifying it on your method.

The reason that you get the second error 'AnyObject' is not convertible to 'T' is that all possible values for T are not all classes. For an instance to be converted to AnyObject, it must be a class (it cannot be a struct, enum, etc.).

Your best bet is to make it a function that accepts the array as an argument:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) {
}

Or instead of modifying the original array, you can make your method more thread safe and reusable by returning a copy:

func arrayRemovingObject<T : Equatable>(object: T, fromArray array: [T]) -> [T] {
}

As an alternative that I don't recommend, you can have your method fail silently if the type stored in the array cannot be converted to the the methods template (that is equatable). (For clarity, I am using U instead of T for the method's template):

extension Array {
    mutating func removeObject<U: Equatable>(object: U) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            if let to = objectToCompare as? U {
                if object == to {
                    index = idx
                }
            }
        }

        if(index != nil) {
            self.removeAtIndex(index!)
        }
    }
}

var list = [1,2,3]
list.removeObject(2) // Successfully removes 2 because types matched
list.removeObject("3") // fails silently to remove anything because the types don't match
list // [1, 3]

Edit To overcome the silent failure you can return the success as a bool:

extension Array {
  mutating func removeObject<U: Equatable>(object: U) -> Bool {
    for (idx, objectToCompare) in self.enumerate() {  //in old swift use enumerate(self) 
      if let to = objectToCompare as? U {
        if object == to {
          self.removeAtIndex(idx)
          return true
        }
      }
    }
    return false
  }
}
var list = [1,2,3,2]
list.removeObject(2)
list
list.removeObject(2)
list
Rodrigo
  • 11,909
  • 23
  • 68
  • 101
drewag
  • 93,393
  • 28
  • 139
  • 128
  • Check out my answer here: http://stackoverflow.com/a/24939242/458960 Why am I able to do it this way and not using the `find` method? – Snowman Jul 24 '14 at 16:29
  • Your method is susceptible to runtime crashes. With my function, the compiler will prevent that from happening at all. – drewag Jul 24 '14 at 16:35
  • @moby and you are not able to do it with find because the type of `object` does not match the type stored in the Array – drewag Jul 24 '14 at 16:37
  • @moby, I added an alternative that lets you define it as an extension and fails silently if the types don't match, but I really recommend against using it. – drewag Jul 24 '14 at 16:51
  • Why can't I do something like ``? – Snowman Jul 24 '14 at 16:53
  • And why would you strongly recommend against the solution above? We've been programming like this with Objective-C for years, using `id`s, and it was never a problem then. – Snowman Jul 24 '14 at 16:57
  • @drewag Does this method only work for `Int`s? I have an array of `UIView`s and I need to remove objects like this. Can I do that with this function? – Isuru Oct 26 '14 at 05:05
  • 1
    @Isuru This method works with any object that implements the `Equatable` protocol. UIView does so yes it will work with UIViews – drewag Oct 26 '14 at 05:12
  • Found an edge case where it failed when the only object information is that is implements a particular protocol, "Generic parameter 'U' cannot be bound to non-@objc protocol type 'X'", https://gist.github.com/danieljfarrell/4dbe7c322c90318d78dc – Daniel Farrell Mar 23 '15 at 20:44
  • 4
    Wow, writing a for loop to remove an element, back to the 90s it is! – Zorayr Mar 28 '15 at 03:58
  • This code has a logical flaw. You're casting each array element into the passed-in object's type (U) but it should be the other way around. Otherwise if you pass in a child class then this method will fail because it won't be able to upcast the array elements. – Oren Jul 16 '15 at 17:21
  • 5
    In the latest swift. `enumerate(self)` have to fix to `self.enumerate()` – TomSawyer Oct 12 '15 at 19:13
  • It is not necessary to use a for loop to remove an element in the array, even if you want to remove all the coincidences of an element in the array. The answer is not wrong but there is better solutions using `indexOf` – juanjo Nov 21 '15 at 21:24
  • In Swift 3, I had to replace `self.enumerate()` with `self.enumerated()`... – Nick Mar 03 '17 at 00:16
29

briefly and concisely:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}
János
  • 32,867
  • 38
  • 193
  • 353
  • 2
    This is cool. Of course it can be done without the `inout`, too. Even with the `inout` intact, one could use, `array = array.filter() { $0 != object }`, I think. – Dan Rosenstark Jan 12 '15 at 20:05
  • 11
    Be aware of using force unwrapped index, which may be nil. Change to "if let ind = index {array.removeAtIndex(ind)}" – HotJard Apr 26 '15 at 09:05
17

After reading all the above, to my mind the best answer is:

func arrayRemovingObject<U: Equatable>(object: U, # fromArray:[U]) -> [U] {
  return fromArray.filter { return $0 != object }
}

Sample:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = arrayRemovingObject("Cat", fromArray:myArray )

Swift 2 (xcode 7b4) array extension:

extension Array where Element: Equatable {  
  func arrayRemovingObject(object: Element) -> [Element] {  
    return filter { $0 != object }  
  }  
}  

Sample:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = myArray.arrayRemovingObject("Cat" )

Swift 3.1 update

Came back to this now that Swift 3.1 is out. Below is an extension which provides exhaustive, fast, mutating and creating variants.

extension Array where Element:Equatable {
    public mutating func remove(_ item:Element ) {
        var index = 0
        while index < self.count {
            if self[index] == item {
                self.remove(at: index)
            } else {
                index += 1
            }
        }
    }

    public func array( removing item:Element ) -> [Element] {
        var result = self
        result.remove( item )
        return result
    }
}

Samples:

// Mutation...
      var array1 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      array1.remove("Cat")
      print(array1) //  ["Dog", "Turtle", "Socks"]

// Creation...
      let array2 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      let array3 = array2.array(removing:"Cat")
      print(array3) // ["Dog", "Turtle", "Fish"]
December
  • 584
  • 5
  • 10
13

With protocol extensions you can do this,

extension Array where Element: Equatable {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 == object }) {
            removeAtIndex(index)
        }
    }
}

Same functionality for classes,

Swift 2

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 === object }) {
            removeAtIndex(index)
        }
    }
}

Swift 3

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = index(where: { $0 === object }) {
             remove(at: index)
        }
    }
}

But if a class implements Equatable it becomes ambiguous and the compiler gives an throws an error.

Community
  • 1
  • 1
  • 1
    i'm getting a `Binary operator '===' cannot be applied to two elements of type '_' and 'Element'` – shoe Jan 20 '17 at 01:20
6

With using protocol extensions in swift 2.0

extension _ArrayType where Generator.Element : Equatable{
    mutating func removeObject(object : Self.Generator.Element) {
        while let index = self.indexOf(object){
            self.removeAtIndex(index)
        }
    }
}
ogantopkaya
  • 474
  • 6
  • 8
4

what about to use filtering? the following works quite well even with [AnyObject].

import Foundation
extension Array {
    mutating func removeObject<T where T : Equatable>(obj: T) {
        self = self.filter({$0 as? T != obj})
    }

}
valvoline
  • 7,737
  • 3
  • 47
  • 52
3

Maybe I didn't understand the question.

Why wouldn't this work?

import Foundation
extension Array where Element: Equatable {
    mutating func removeObject(object: Element) {
        if let index = self.firstIndex(of: object) {
            self.remove(at: index)
        }
    }
}

var testArray = [1,2,3,4,5,6,7,8,9,0]
testArray.removeObject(object: 6)
let newArray = testArray

var testArray2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
testArray2.removeObject(object: "6")
let newArray2 = testArray2
Chris Marshall
  • 4,910
  • 8
  • 47
  • 72
3

No need to extend:

var ra = [7, 2, 5, 5, 4, 5, 3, 4, 2]

print(ra)                           // [7, 2, 5, 5, 4, 5, 3, 4, 2]

ra.removeAll(where: { $0 == 5 })

print(ra)                           // [7, 2, 4, 3, 4, 2]

if let i = ra.firstIndex(of: 4) {
    ra.remove(at: i)
}

print(ra)                           // [7, 2, 3, 4, 2]

if let j = ra.lastIndex(of: 2) {
    ra.remove(at: j)
}

print(ra)                           // [7, 2, 3, 4]
Roi Zakai
  • 252
  • 3
  • 3
2

There is another possibility of removing an item from an array without having possible unsafe usage, as the generic type of the object to remove cannot be the same as the type of the array. Using optionals is also not the perfect way to go as they are very slow. You could therefore use a closure like it is already used when sorting an array for example.

//removes the first item that is equal to the specified element
mutating func removeFirst(element: Element, equality: (Element, Element) -> Bool) -> Bool {
    for (index, item) in enumerate(self) {
        if equality(item, element) {
            self.removeAtIndex(index)
            return true
        }
    }
    return false
}

When you extend the Array class with this function you can remove elements by doing the following:

var array = ["Apple", "Banana", "Strawberry"]
array.removeFirst("Banana") { $0 == $1 } //Banana is now removed

However you could even remove an element only if it has the same memory address (only for classes conforming to AnyObject protocol, of course):

let date1 = NSDate()
let date2 = NSDate()
var array = [date1, date2]
array.removeFirst(NSDate()) { $0 === $1 } //won't do anything
array.removeFirst(date1) { $0 === $1 } //array now contains only 'date2'

The good thing is, that you can specify the parameter to compare. For example when you have an array of arrays, you can specify the equality closure as { $0.count == $1.count } and the first array having the same size as the one to remove is removed from the array.

You could even shorten the function call by having the function as mutating func removeFirst(equality: (Element) -> Bool) -> Bool, then replace the if-evaluation with equality(item) and call the function by array.removeFirst({ $0 == "Banana" }) for example.

borchero
  • 5,562
  • 8
  • 46
  • 72
  • Since `==` is a function, you can also call it like this for any type that implements `==` (like String, Int etc.): `array.removeFirst("Banana", equality:==)` – Aviel Gross Nov 10 '15 at 15:22
  • @AvielGross this is new in Swift 2 I think - feel free to edit the answer accordingly if you want to – borchero Nov 10 '15 at 15:24
1

Using indexOf instead of a for or enumerate:

extension Array where Element: Equatable {

   mutating func removeElement(element: Element) -> Element? {
      if let index = indexOf(element) {
         return removeAtIndex(index)
      }
      return nil
   }

   mutating func removeAllOccurrencesOfElement(element: Element) -> Int {
       var occurrences = 0
       while true {
          if let index = indexOf(element) {
             removeAtIndex(index)
             occurrences++
          } else {
             return occurrences
          }
       }
   }   
}
juanjo
  • 3,737
  • 3
  • 39
  • 44
0

I finally ended up with following code.

extension Array where Element: Equatable {

    mutating func remove<Element: Equatable>(item: Element) -> Array {
        self = self.filter { $0 as? Element != item }
        return self
    }

}
Kaz Yoshikawa
  • 1,577
  • 1
  • 18
  • 26
0

Your problem is T is not related to the type of your array in anyway for example you could have

var array = [1,2,3,4,5,6]

array.removeObject(object:"four")

"six" is Equatable, but its not a type that can be compared to Integer, if you change it to

var array = [1,2,3,4,5,6]

extension Array where Element : Equatable {
    mutating func removeObject(object: Element) {
        filter { $0 != object }
    }
}

array.removeObject(object:"four")

it now produces an error on calling removeObject for the obvious reason its not an array of strings, to remove 4 you can just

array.removeObject(object:4)

Other problem you have is its a self modifying struct so the method has to be labeled as so and your reference to it at the top has to be a var

Nathan Day
  • 5,981
  • 2
  • 24
  • 40
-1

Implementation in Swift 2:

extension Array {
  mutating func removeObject<T: Equatable>(object: T) -> Bool {
    var index: Int?
    for (idx, objectToCompare) in self.enumerate() {
      if let toCompare = objectToCompare as? T {
        if toCompare == object {
          index = idx
          break
        }
      }
    }
    if(index != nil) {
      self.removeAtIndex(index!)
      return true
    } else {
      return false
    }
  }
}
Mihriban Minaz
  • 3,043
  • 2
  • 32
  • 52
-4

I was able to get it working with:

extension Array {
    mutating func removeObject<T: Equatable>(object: T) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            let to = objectToCompare as T
            if object == to {
                index = idx
            }
        }

        if(index) {
            self.removeAtIndex(index!)
        }
    }
}
Snowman
  • 31,411
  • 46
  • 180
  • 303