Что такое массив Swift, эквивалентный NSMutableArray -removeObjectsAtIndexes:? Удаление каждого индекса по одному не работает, так как оставшиеся индексы будут сдвигаться после удаления одного индекса. Какой эффективный способ реализовать эту функциональность?
RemoveObjectsAtIndexes для массивов Swift
Ответ 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+.
