9

I have an array and I want to remove a bunch of indices

var arr = [0,1,2,3,4,5,6]
var rmIndices = [1,4,5]

What is the best way to remove indices 1,4,5 from arr?

7 Answers 7

22

Note that PermutationGenerator is going away in Swift 3 and also doesn't keep the ordering the same, though perhaps it did at one time. Using the accepted answer results in [2, 6, 0, 3] which may be unexpected. A couple of alternative approaches that give the expected result of [0, 2, 3, 6] are:

let flatArr = arr.enumerate().flatMap { rmIndices.contains($0.0) ? nil : $0.1 }

or

let filterArr = arr.enumerate().filter({ !rmIndices.contains($0.0) }).map { $0.1 }
Sign up to request clarification or add additional context in comments.

6 Comments

Is there somewhere I can read up on what these hieroglyphics are doing?
@Confused It basically says: Go over the elements (enumerate) and get me back all the non-nil elements (flatMap) by testing whether the array with indices to be removed containing the index ($0.0) of the object being enumerated (that's $0); if that is true return nil (so not be included by flatMap) else return the associated value ($0.1) Here is a more verbose version of the first: let flatArr = arr.enumerated().flatMap { (index, value) in rmIndices.contains(index) ? nil : value }
I'm still confused, but this makes things a little clearer. I can't see how ($0.0) is anything, at all.
This looks like a nice solution - but do you have gist of it working- with a bit more explanation.
@Confused $0.0 - index, $0.1 - value, $0 - tuple (obtained from enumerated)
|
9

Rather than a list of indices to remove, it may be easier to have a list of indices to keep, which you can do using the Set type:

let rmIndices = [1,4,5]
let keepIndices = Set(arr.indices).subtract([1,4,5])

Then you can use PermutationGenerator to create a fresh array of just those indices:

arr = Array(PermutationGenerator(elements: arr, indices: keepIndices))

2 Comments

I was able to use something similar to your answer. (The other answer by Vatsal) will remove the wrong items because the indices of the two arrays are not kept in sync. I created a new array by populating those items whose indices were not in rmIndices array. I did not use the PermutationGenerator.
PermutationGenerator was removed in swift3
9
rmIndices.sort({ $1 < $0 })     

for index in rmIndices
{
    arr.removeAtIndex(index)
}

Note that I've sorted the indices in descending order. This is because everytime you remove an element E, the indices of the elements beyond E reduce by one.

5 Comments

The docs state that Array.removeAtIndex “Invalidates all indices with respect to self.” In practice, this is probably OK, but in theory they’re at liberty to change Swift in the future in a way that would make the results of doing this undefined.
@AirspeedVelocity: You're absolutely right, thanks for pointing that out.
@AirspeedVelocity, actually, arr.removeAtIndex(index) would invalidate the indices on arr, but not on rmIndices, so the for in loop is safe. Complexity would be O(m*n), being m the number of indices to remove and n the number of items in the original array. @Vatsal, sorting the rmIndices array here wouldn't have any effect.
@EnekoAlonso the values in rmIndices are absolutely effected by removing elements from arr, because they are referring to specific elements of arr. When you remove element 0 of arr, then index 1 no longer points to the same element, because every element shuffles down. Just because the numbers are not “invalidated” in the “can’t be used any more” sense doesn’t mean they work just as they did before the removal. Note it is this shuffling-down behaviour that means that, far from not having any effect, the sort is required for correct behaviour.
Note that the compiler asks me to use sortInPlace instead of sort at the time of writing.
1

For Swift 3

    var arr = [0,1,2,3,4,5,6]
    let rmIndices = [1,4,5]
    arr = arr.filter{ !rmIndices.contains($0) }
    print(arr)

if you want to produce output very fastly then you can use

    var arr = [0,1,2,3,4,5,6]
    let rmIndices = [1,4,5]
    arr = Array(Set(arr).subtracting(rmIndices))
    print(array)

But it will change order of your array

2 Comments

This doesn't seem to work, something is wrong about the contains
This solution isn't based on indices - it's based on values. You still need some way to know the indices of the values in arr, assuming that the contents of your array are something other than the indices in order ;)
1

Remove elements using indexes array:

  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

    let arrayResult = numbers.filter { element in
        return !indexesToRemove.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"]
    

1 Comment

Doesn't map produce a new array, therefore leaving the original intact? If so then the first example doesn't really remove the elements.
1

In Swift 4:

let newArr = arr.enumerated().compactMap {
    rmIndices.contains($0.0) ? nil : $0.1
}
  • enumerated() generates (index, value) pairs
  • compactMap concatenates non-nil values
  • In the closure, $0.0 is the index (first element of enumerated pair) as $0.1$ is the value
  • compactMap gathers values whose indices are not found in rmIndices

Comments

0

The problem with flatmap is that it gives incorrect results if your array contains optionals.

The following is much faster than the functional style solutions provided and works with optionals. You just have to make sure rmIndices is sorted and unique. It's also fairly language agnostic.

 var numRemoved: Int = 0
 for index in rmIndices {
     let indexToRemove = index - numRemoved
     arr.remove(at: indexToRemove)
     numRemoved += 1
 }

If you need to make sure rmIndices is sorted and unique:

 rmIndices = Set(rmIndices).sorted()

Using XCTest to remove 500 elements (including the operation to ensure uniqueness and sorted):

0.006 sec

vs.

arr.enumerated().filter({ !rmIndices.contains($0.0) }).map { $0.1 }:

0.206 sec

I use this as an extension on Array

extension Array {

    mutating func remove(at indices: [Int]) {
        let rmIndices = Set(indices).sorted()
        var numRemoved: Int = 0
        for index in rmIndices {
            let indexToRemove = index - numRemoved
            self.remove(at: indexToRemove)
            numRemoved += 1
        }
    }

}

1 Comment

beware that with remove(at:) all the elements following the specified position are moved to close the gap (aka removal is proportional to the length of the collection). If you use remove(at:) inside a for-loop, you're getting yourself into a O(n^2) operation. For a more comprehensive explanation watch the following video from minute 6:23 developer.apple.com/videos/play/wwdc2018/223

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.