16

I'm trying to be more efficient with my code, but I'm having a brain fart. This code I wrote works great, and does exactly what I need it to do: It checks an array and removes an Object at an unknown Index. But I feel there is a better, more efficient way to write it. I went to Array.remove(at:) but that requires a known index. I'm getting into big O notation and don't know how to make this easier to process. Any ideas?

 // create a new object array
 var sort : [MyCustomObject] = []

//iterate through my object array  

        for i in objectArray{
            if i === objectToRemove{
            }
            else{
                sort.append(i)
            }
        }

     // set old array to sort, which no longer has the unwanted object 
        self.objectArray = sort
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Brandon Bravos
  • 390
  • 1
  • 2
  • 10

5 Answers5

27

Use firstIndex(where:) (previously called index(where:) in Swift 4.1 and earlier) to search the array for your object using the predicate { $0 === objectToRemove }, then call remove(at:) on the array to remove it:

if let idx = objectArray.firstIndex(where: { $0 === objectToRemove }) {
    objectArray.remove(at: idx)
}

This allows you to search for your object whether it is Equatable or not.

vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 2
    Note that firstIndex(where:) still in beta (formerly known as `index(where:)`) – Leo Dabus Aug 28 '18 at 20:55
  • 1
    @LeoDabus, thanks for the note. I'm having trouble finding a link for `index(where:)` Apple documentation. – vacawama Aug 28 '18 at 21:01
  • I couldn't find it either. – Leo Dabus Aug 28 '18 at 22:53
  • In the end, your answer was the cleanest and easiest code to implement without making my CustomObject Equatable. Thank you! I have also checked the apple documentation for index(where:) and have not been able to find anything. But it works! – Brandon Bravos Aug 30 '18 at 02:45
  • Though Swift 4.1, `index(where:)` was the call. Apple just updated the documentation before Swift 4.2 was released (really soon now). – vacawama Aug 30 '18 at 02:51
11

If you are coding with Xcode 10.0+ beta (Swift 4.2 or later) you can use the new method removeAll(where:)

mutating func removeAll(where predicate: (Element) throws -> Bool) rethrows

Discussion: Use this method to remove every element in a collection that meets particular criteria. Complexity: O(n), where n is the length of the collection.

This example removes all the odd values from an array of numbers:

Swift 5.2 or later

extension BinaryInteger {
    var isEven: Bool { isMultiple(of: 2) }
    var isOdd: Bool { !isMultiple(of: 2) }
}

var numbers = [5, 6, 7, 8, 9, 10, 11]
numbers.removeAll(where: \.isOdd) // numbers == [6, 8, 10]
numbers

In your case make sure MyCustomObject conforms to Equatable

objectArray.removeAll(where: { $0 == objectToRemove })

or use one of its properties that does conform to it as the predicate (i.e id: Int):

objectArray.removeAll(where: { $0.id == idToRemove })

Note: If you are not using Xcode 10.0+ beta (Swift 4.2) you can implement your own removeAll(where:) method as you can see in this answer.


Implementing a removeFirst(where:) and removeLast(where:) to avoid iterating the whole Collection as mentioned in comments by @vacawama

Swift 4.1

extension RangeReplaceableCollection  {
    @discardableResult
    mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element?  {
        guard let index = try index(where: predicate) else { return nil }
        return remove(at: index)
    }
}

extension RangeReplaceableCollection where Self: BidirectionalCollection {
    @discardableResult
    mutating func removeLast(where predicate: (Element) throws -> Bool) rethrows -> Element? {
        guard let index = try indices.reversed().first(where: {
            try predicate(self[$0])
        }) else { return nil }
        return remove(at: index)
    }
}

Swift 4.2 or later (as suggested by @Hamish)

extension RangeReplaceableCollection {
    @discardableResult
    mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? {
        guard let index = try firstIndex(where: predicate) else { return nil }
        return remove(at: index)
    }
}

extension RangeReplaceableCollection where Self: BidirectionalCollection {
    @discardableResult
    mutating func removeLast(where predicate: (Element) throws -> Bool) rethrows -> Element? {
        guard let index = try lastIndex(where: predicate) else { return nil }
        return remove(at: index)
    }
}

You can also check this post for a remove(while:), removeLast(while:) and dropLast(while:) method implementations.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

you can use this generic to remove any kind of object From array

extension Array where Element: Equatable {
mutating func removeObject(object: Element)  {
    if let index = firstIndex(of: object) {
        remove(at: index)
    }
}}
Rishabh Shukla
  • 461
  • 6
  • 19
0

You can try

if let ind = objectArray.index(of:objectToRemove) {
  objectArray.remove(at:ind)
}
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
0

Swift 4

Use enumerated() and you will have the index:

var array = ["a","b","c"]
let objectToRemove = "b"
for (index, value) in array.enumerated() {
    if value == objectToRemove {
        array.remove(at: index)
    }
}
print(array) // Prints: ["a","c"]

Edit:

Or maybe better yet, use filter:

var array = ["a","b","c"]
let objectToRemove = "b"
array = array.filter { $0 != objectToRemove } // Keeps only what is
                                              // different from the 
                                              // objectToRemove
print(newArray)  // Prints: ["a","c"]
Michel Koga
  • 39
  • 1
  • 5