22

Is it possible to remove more than one item from an array, at the same time, using index locations as per .remove(at: i) kind of like:

Pseudo code:

myArray.remove(at: 3, 5, 8, 12)

And if so, what's the syntax for doing this?


UPDATE:

I was trying this, it worked, but the extension in the answer below is much more readable, and sensible, and achieves the goal of one that's exactly as the pseudo code.

an array of "positions" is created: [3, 5, 8, 12]

let sorted = positions.sorted(by: { $1 < $0 })
for index in sorted
{
    myArray.remove(at: index)
}
Krunal
  • 77,632
  • 48
  • 245
  • 261
Confused
  • 6,048
  • 6
  • 34
  • 75

7 Answers7

37

It's possible if the indexes are continuous using removeSubrange method. For example, if you would like to remove items at index 3 to 5:

myArray.removeSubrange(ClosedRange(uncheckedBounds: (lower: 3, upper: 5)))

For non-continuous indexes, I would recommend remove items with larger index to smaller one. There is no benefit I could think of of removing items "at the same time" in one-liner except the code could be shorter. You can do so with an extension method:

extension Array {
  mutating func remove(at indexes: [Int]) {
    for index in indexes.sorted(by: >) {
      remove(at: index)
    }
  }
}

Then:

myArray.remove(at: [3, 5, 8, 12])

UPDATE: using the solution above, you would need to ensure the indexes array does not contain duplicated indexes. Or you can avoid the duplicates as below:

extension Array {
    mutating func remove(at indexes: [Int]) {
        var lastIndex: Int? = nil
        for index in indexes.sorted(by: >) {
            guard lastIndex != index else {
                continue
            }
            remove(at: index)
            lastIndex = index
        }
    }
}


var myArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
myArray.remove(at: [5, 3, 5, 12]) // duplicated index 5
// result: [0, 1, 2, 4, 6, 7, 8, 9, 10, 11, 13] only 3 elements are removed
Thanh Pham
  • 2,021
  • 21
  • 30
  • The reason I'm giving the example of indices as (3, 5, 8, 12) is because they'll not be contiguous. – Confused Oct 11 '16 at 10:34
  • The `filter` solution calls `index(of:)`, which not only iterates over the array unnecessarily a second time, but is also not guaranteed to find the correct index (e.g., if there are duplicate elements). – Arkku Oct 11 '16 at 10:41
  • I have updated the answer @Confused although it could still not be as ideal as you would expect :D – Thanh Pham Oct 11 '16 at 10:52
  • thanks Thanh! I updated my question with the approach I was using. But I like yours much better, and am now using that. Is it also possible to do the same sort of extension that creates and array from these positions, based on objects at these positions in another array? I probably need to ask another question about this... – Confused Oct 11 '16 at 17:38
  • what I found interesting, when I tried to rename your some of the parts of your technique, the final function in the extension required an array rather than an Int. I tried to rename remove(at: _) to removes(from: _) to cover the plural nature of it. Why does this simple change change the required type of "index" to [index]? – Confused Oct 11 '16 at 17:41
  • Perhaps make sure that your extension method is declared as `mutating`. @Confused – Thanh Pham Oct 12 '16 at 00:38
  • @Confused regarding the method to create an array from indexes: `[3, 5, 8, 12].map { myArray[$0] }` – Thanh Pham Oct 12 '16 at 01:15
  • In the extension, I'm using exactly your code, and only changing the name of remove(at: _) to removes(from: _), the mutating part of your code still there. I simply wanted to make it more readable for the plurality of what its doing. – Confused Oct 12 '16 at 02:19
  • That other thing, with the .map... PURE AWESOME! – Confused Oct 12 '16 at 02:19
  • This is obviously a two part process. I'm randomly picking indices from an array, and moving them to another array, and wanting to delete them from the original. When I get it all working, I'll update the question and point out this two stage thing, and maybe it won't be a complete duplicate. I don't think it is, because of my desire to get it into one line, and the extension trick you're doing, so will modify to that, too. Extensions blow my mind. – Confused Oct 12 '16 at 02:21
  • I haven't understood the extension, can anyone please make me understand how that works. Btw the approach is magical, thanks for the answer. @ThanhPham – iPeter Mar 16 '18 at 13:31
  • @iPeter what do you know about extensions? – Confused Mar 16 '18 at 13:37
  • And I agree, I'm still in awe of the elegance and magicalness of this approach. Thanks again, @ThanhPham – Confused Mar 16 '18 at 13:37
  • I know about extensions, I want to know how those lines of code does the work. – iPeter Mar 16 '18 at 14:27
25

Remove elements using indexes of an array elements:

  1. Array of Strings and indexes

    let animals = ["cats", "dogs", "chimps", "moose", "squarrel", "cow"]
    let indexAnimals = [0, 3, 4]
    let arrayRemainingAnimals = animals
        .enumerated()
        .filter { !indexAnimals.contains($0.offset) }
        .map { $0.element }
    
    print(arrayRemainingAnimals)
    
    //result - ["dogs", "chimps", "cow"]
    
  2. Array of Integers and indexes

    var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    let indexesToRemove = [3, 5, 8, 12]
    
    numbers = numbers
        .enumerated()
        .filter { !indexesToRemove.contains($0.offset) }
        .map { $0.element }
    
    print(numbers)
    
    //result - [0, 1, 2, 4, 6, 7, 9, 10, 11]
    



Remove elements using element value of another array

  1. Arrays of integers

    var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    let elementsTobeRemoved = [3, 5, 8, 12]
    let arrayResult = numbers.filter { element in
        return !elementsTobeRemoved.contains(element)
    }
    print(arrayResult)
    
    //result - [0, 1, 2, 4, 6, 7, 9, 10, 11]
    
  2. Arrays of strings

    let arrayLetters = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
    let arrayRemoveLetters = ["a", "e", "g", "h"]
    let arrayRemainingLetters = arrayLetters.filter {
        !arrayRemoveLetters.contains($0)
    }
    
    print(arrayRemainingLetters)
    
    //result - ["b", "c", "d", "f", "i"]
    
Krunal
  • 77,632
  • 48
  • 245
  • 261
  • I used answer 1. Everything else I tried was recursing the "new" array which was re-indexed so using a let constant instead of var is a must. I'm trying to understand better exactly how it works. – AMC08 Jan 12 '23 at 21:27
9

Simple and clear solution, just Array extension:

extension Array {

    mutating func remove(at indices: [Int]) {
        Set(indices)
            .sorted(by: >)
            .forEach { rmIndex in
                self.remove(at: rmIndex)
            }
    }
}
  • Set(indices) - ensures uniqueness
  • .sorted(by: >) - function removes elements from last to first, so during removal we are sure that indexes are proper
Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58
7

Swift 4

extension Array {

    mutating func remove(at indexs: [Int]) {
        guard !isEmpty else { return }
        let newIndexs = Set(indexs).sorted(by: >)
        newIndexs.forEach {
            guard $0 < count, $0 >= 0 else { return }
            remove(at: $0)  
        }
    }

}

var arr = ["a", "b", "c", "d", "e", "f"]

arr.remove(at: [2, 3, 1, 4])

result: ["a", "f"]
3

You can make a set of indexes you want to remove.

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let indexSet = [3, 5, 8, 12]
indexSet.reversed().forEach{ array.remove(at: $0) }
print(array)

Output: [0, 1, 2, 4, 6, 7, 9, 10, 11]

In case indexes are continuous then use removeSubrange

array.removeSubrange(1...3) /// Will remove the elements from 1, 2 and 3 positions.
TheTiger
  • 13,264
  • 3
  • 57
  • 82
2

According to the NSMutableArray API I recommend to implement the indexes as IndexSet.

You just need to inverse the order.

extension Array {

    mutating func remove(at indexes: IndexSet) {
        indexes.reversed().forEach{ self.remove(at: $0) }
    }
}

Please see also this answer providing a more efficient algorithm.

vadian
  • 274,689
  • 30
  • 353
  • 361
0

Swift 5.6

I had a situation where I needed to remove values in one list using values from another list, so I used this:

var mainList = [1,2,3,4,5,6,7,8]
let otherList = [3,4,6,8]

mainList.remove(where: { otherList.contains($0) })

print(mainList) // [1, 2, 5, 7]
SouthernYankee65
  • 1,129
  • 10
  • 22