Что такое массив 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+.