RemoveObjectsAtIndexes для массивов Swift

Что такое массив Swift, эквивалентный NSMutableArray -removeObjectsAtIndexes:? Удаление каждого индекса по одному не работает, так как оставшиеся индексы будут сдвигаться после удаления одного индекса. Какой эффективный способ реализовать эту функциональность?

Ответ 1

Вот решение, которое я сейчас использую:

extension Array {
    mutating func removeObjectAtIndexes(indexes: [Int]) {
        var indexSet = NSMutableIndexSet()

        for index in indexes {
            indexSet.addIndex(index)
        }

        indexSet.enumerateIndexesWithOptions(.Reverse) {
            self.removeAtIndex($0.0)
            return
        }
    }

    mutating func removeObjectAtIndexes(indexes: Int...) {
        removeObjectAtIndexes(indexes)
    }
}

Ответ 2

Мне нравится чистое решение Swift, т.е. без обращения к NSIndexSet:

extension Array {
    mutating func removeAtIndexes (ixs:[Int]) -> () {
        for i in ixs.sorted(>) {
            self.removeAtIndex(i)
        }
    }
}

РЕДАКТИРОВАТЬ В Swift 4 это будет:

extension Array {
    mutating func remove (at ixs:[Int]) -> () {
        for i in ixs.sorted(by: >) {
            self.remove(at:i)
        }
    }
}

Но спустя годы после того, как я написал этот ответ, видео об использовании алгоритмов WWDC 2018 года указывает на недостаток: это O (n 2), потому что сама команда remove(at:) должна проходить через массив.

Согласно этому видео, Swift 4.2 removeAll(where:) эффективен, поскольку использует полустабильное разбиение. Таким образом, мы могли бы написать что-то вроде этого:

extension Array {
    mutating func remove(at set:IndexSet) {
        var arr = Swift.Array(self.enumerated())
        arr.removeAll{set.contains($0.offset)}
        self = arr.map{$0.element}
    }
}

Мои тесты показывают, что, несмотря на неоднократное contains, что в 100 раз быстрее. Тем не менее, @vadian подход в 10 раз быстрее, чем тот, потому что он разворачивает contains, гениально прогуливаясь по индексу, установленному в то же время, когда он обходит массив (используя полустабильное разбиение).

Ответ 3

В соответствии с сессией 223 WWDC 2018 "Охват алгоритмов" эффективным решением является алгоритм полустабильного разбиения:

extension RangeReplaceableCollection where Self: MutableCollection, Index == Int {

    mutating func remove(at indexes : IndexSet) {
        guard var i = indexes.first, i < count else { return }
        var j = index(after: i)
        var k = indexes.integerGreaterThan(i) ?? endIndex
        while j != endIndex {
            if k != j { swapAt(i, j); formIndex(after: &i) }
            else { k = indexes.integerGreaterThan(k) ?? endIndex }
            formIndex(after: &j)
        }
        removeSubrange(i...)
    }
}

Он перемещает все элементы, которых нет в индексе, установленном в конец массива, просто меняя элементы. Полустабильный означает, что алгоритм сохраняет порядок левой части, но не заботится о порядке правой стороны, так как элементы будут удалены в любом случае. После цикла переменная i содержит первый индекс удаляемых элементов. Операция пакетного удаления стоит недорого, потому что никакие индексы не будут сдвинуты/перестроены.


Например, если у вас есть массив

[0, 1, 2, 3, 4, 5, 6, 7]

и вы хотите, чтобы удалить элементы с индексом 2 и 4 алгоритм выполняет следующие действия в while циклы (начальное значение индекса j является индексом после того, как первый индекс должен быть удален):

  • Индекс 3: Поменяйте местами элементы с номерами 2 и 3[0, 1, 3, 2, 4, 5, 6, 7]
  • Индекс 4: Без изменений
  • Индекс 5: Поменяйте местами элементы с номерами 3 и 5[0, 1, 3, 5, 4, 2, 6, 7]
  • Индекс 6: Поменяйте местами элементы с номерами 4 и 6[0, 1, 3, 5, 6, 2, 4, 7]
  • Индекс 7: поменяйте местами элементы с индексами 5 и 7[0, 1, 3, 5, 6, 7, 4, 2]

  • Наконец удалить элементы в поддиапазоне 6...


Ответ 4

Обновлен для Swift 2.0:

extension Array {
    mutating func removeAtIndices(incs: [Int]) {
        incs.sort(>).map { removeAtIndex($0) }
    }
}

Используйте forEach вместо map, если он дает предупреждение о том, что результат не используется (поскольку Swift 2 beta 6 я думаю)

EDIT: супер общее ленивое решение:

extension RangeReplaceableCollectionType where Index : Comparable {
    mutating func removeAtIndices<S : SequenceType where S.Generator.Element == Index>(indices: S) {
        indices.sort().lazy.reverse().forEach{ removeAtIndex($0) }
    }
}

Ответ 5

В итоге я сделал это так:

Согласно документации Apple по NSIndexSet, "индекс устанавливает индексы магазина как отсортированные диапазоны". Таким образом, мы можем перечислить заданный NSIndexSet обратном направлении и удалить элемент из массива по каждому индексу один за другим, например, так:

extension Array {

  mutating func removeAtIndexes(indexes: NSIndexSet) {
    for var i = indexes.lastIndex; i != NSNotFound; i = indexes.indexLessThanIndex(i) {
      self.removeAtIndex(i)
    }
  }

}

Ответ 6

Еще одно дополнение к ответу Итана (урезанное из того, что у меня есть в моей собственной структуре):

extension Array {
    // Code taken from /questions/706944/removeobjectsatindexes-for-swift-arrays/2701048#2701048
    // Further adapted to work with Swift 2.2
    /// Removes objects at indexes that are in the specified 'NSIndexSet'.
    /// - parameter indexes: the index set containing the indexes of objects that will be removed
    public mutating func removeAtIndexes(indexes: NSIndexSet) {
        for i in indexes.reverse() {
            self.removeAtIndex(i)
        }
    }
}

Эта версия использует расширение NSIndexSet SequenceType предоставляемое мостом Swift Foundation.

edit: Кроме того, более новые версии Swift (не помню, какая из них добавила) имеют struct тип с именем IndexSet, который может быть соединен с NSIndexSet. И может быть reverse() как NSIndexSet.

Ответ 7

Чтобы завершить Ответ этана, стили C-loop будут устаревать в Swift 3.0. Вот тогда ответ Swift 3.0:

mutating func removeAtIndexes(indexes: NSIndexSet) {
    var i = indexes.lastIndex
    while i != NSNotFound {
        self.removeAtIndex(i)
        i = indexes.indexLessThanIndex(i)
    }
}

Ответ 8

Я использовал функцию Swift filter:

func removeMusicListAtIndexes(idxSet: NSIndexSet) {
    if idxSet.containsIndex(selectedMusic) {
        selectedMusic = -1;
    }
    musicList = musicList.filter({
        var idx = find(self.musicList, $0)
        // If a value isn't in the index, it isn't being removed
        return !idxSet.containsIndex(idx!)
        })
}

Ответ 9

Мне это нужно для работы с NSTableview. Это то, что я использую.

extension Array{
mutating func removeElementsAtIndexes(indexset:NSIndexSet){
    self = self.enumerate().filter({!indexset.containsIndex($0.index)}).map({$0.element})
    }
}

Ответ 10

Основано на решении Kent, но обновлено для Swift 3

extension Array {
    mutating func remove(indices: IndexSet) {
        self = self.enumerated().filter { !indices.contains($0.offset) }.map { $0.element }
    }
}

Ответ 11

Быстрая попытка 4

extension Array {

    mutating func removeAtIndexes(indexes: IndexSet) {
        var i:Index? = indexes.last
        while i != nil {
            self.remove(at: i!)
            i = indexes.integerLessThan(i!)
        }
    }
}

Ответ 12

один способ:

var arrayB = arrayA.removeAtIndex(5)

Другой способ:

var arr = ["I", "Love", "Life"]
let slice = arr[1..<2]
println(slice)
//[Love]

Ответ 13

Я нашел системный API, но он доступен в SwiftUI для iOS 13+.

enter image description here