2

I need to remove the last occurrence of a specific element in a [Bool] Array. For example in JavaScript this would be:

var k = [true, true, false];
k.splice(k.lastIndexOf(true), 1);
==> [true, false]

How would I achieve the same behavior in Swift?

RK.
  • 867
  • 2
  • 10
  • 19
  • Let me get this straight. You want to remove the last occurrence of a given object in an array. So if your array was var array = `["apple", "pear", "banana", "apple", "orange"]` you'd want your `array.removeLastOccurrenceOf("apple")` to change the array to `["apple", "pear", "banana", "orange"]`? – Duncan C Aug 12 '15 at 00:42
  • For those who want to **remove the last element in an array**, see [this answer](http://stackoverflow.com/a/38882934/3681880). – Suragch Aug 10 '16 at 20:32

3 Answers3

7

You can easily find the last occurrence of a value by enumerating in reverse. When you find the value you're looking for, just remove it and break from the loop. Use reverse() enumerate the range of indexes in reverse order:

for i in array.indices.reversed() where array[i] == searchValue {
    array.remove(at: i)
    break
}
Stuart
  • 36,683
  • 19
  • 101
  • 139
6

Xcode 8.2 • Swift 3.0.2

var k = [true, true, true, false, true, false]

if let index = k.reversed().index(of: true) {
    k.remove(at: index.base - 1)
}

print(k)   // "[true, true, true, false, false]"

If you would like to create an extension to add this functionality to Array you need to constrain it to equatable elements:

extension Array where Element: Equatable {
    ///  Returns the last index where the specified value appears in the collection.
    ///  After using lastIndex(of:) to find the last position of a particular element in a collection, you can use it to access the element by subscripting.
    /// - Parameter element: The element to find the last Index
    func lastIndex(of element: Element) -> Index? {
        if let index = reversed().index(of: element) {
            return  index.base - 1
        }
        return nil
    }
    /// Removes the last occurrence where the specified value appears in the collection.
    /// - Returns: True if the last occurrence element was found and removed or false if not.
    /// - Parameter element: The element to remove the last occurrence.
    @discardableResult
    mutating func removeLastOccurrence(of element: Element) -> Bool {
        if let index = lastIndex(of: element) {
            remove(at: index)
            return true
        }
        return false
    }
}

Playground testing

var k = [true, true, true, false, true, false]

k.removeLastOccurrence(of: true)
print(k)                        // "[true, true, true, false, false]"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
2

I don't think there's a builtin function like lastIndexOf, so it's a bit more work.

var removeIndex: Int?

for (index, item) in enumerate(arr) {
    if item == search {
        removeIndex = index 
    }
}

if let removeIndex = removeIndex {
    arr.removeAtIndex(removeIndex)
}

Where arr is the array you're searching (k in your example) and search is what you're searching for (true in your example).

ezig
  • 1,219
  • 1
  • 10
  • 15
  • 1
    That would work, but you don't really need an array. You could simply store the last matching index into an Int, and then remove the object at that index from the array. Less storage, simpler code. – Duncan C Aug 12 '15 at 00:40
  • Agreed, that's cleaner and more efficient – I changed my answer. – ezig Aug 12 '15 at 00:43
  • 1
    Now as a final cleanup why not use "optional binding" on the last if statement: `if let removeIndex = removeIndex {arr.removeAtIndex(removeIndex!)}` – Duncan C Aug 12 '15 at 00:44
  • Good tip. The forced unwrapping `removeIndex!` is unneeded with the binding, right? `arr.removeAtIndex(removeIndex)` should work. – ezig Aug 12 '15 at 00:47
  • Right. The optional binding converts the optional to a non-optional of the same type. You can use a new constant name for the non-optional version, or if you use the same name, it's a new non-optional constant in the scope of the braces after the "if let" statement, and the optional is not accessible from inside the braces. – Duncan C Aug 12 '15 at 00:52
  • 1
    Note, for Swift 2.0, you'd need to change to `arr.enumerate()`. – vacawama Aug 12 '15 at 01:00