对于Swift数组,removeObjectsAtIndexes

时间:2014-10-03 05:10:31

标签: swift

什么是等同于NSMutableArray的{​​{1}}的Swift数组?逐个删除每个索引不起作用,因为删除一个索引后剩余的索引将会移位。什么是实现此功能的有效方法?

14 个答案:

答案 0 :(得分:16)

我喜欢纯粹的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 Embracing Algorithms视频指出了这个缺陷:它是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}
    }
}

我的tests表明,尽管重复contains,但速度提高了100倍。但是,@ vadian的方法比 快10倍,因为他通过巧妙地走在索引集中同时展开contains(使用半稳定)分区)。

答案 1 :(得分:12)

根据WWDC 2018 Session 223 – Embracing Algorithms,一个有效的解决方案是半稳定分区算法:

extension Array {

    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]

并且您要删除索引24处的元素,算法会在while循环中执行以下步骤(索引j的初始值为第一个要删除的索引后的索引):

  • 索引3:转换索引23[0, 1, 3, 2, 4, 5, 6, 7]
  • 的元素
  • 索引4:无变化
  • 索引5:转换索引35[0, 1, 3, 5, 4, 2, 6, 7]
  • 的元素
  • 索引6:转换索引46[0, 1, 3, 5, 6, 2, 4, 7]
  • 的元素
  • 索引7:转换索引57[0, 1, 3, 5, 6, 7, 4, 2]

  • 的元素
  • 最后删除子范围6...

  • 中的元素

答案 2 :(得分:6)

针对Swift 2.0进行了更新:

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

如果它发出不使用结果的警告,请使用forEach代替map(我认为自Swift 2 beta 6以来)

编辑:超级通用懒人解决方案:

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

答案 3 :(得分:5)

我最终这样做了:

根据Apple's documentation on NSIndexSet,“索引将索引存储为已排序的范围”。所以我们可以向后枚举给定的NSIndexSet 并逐个从每个索引处删除数组中的元素,如下所示:

extension Array {

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

}

答案 4 :(得分:4)

Ethan's answer上的另一个扩展(从我自己的框架中删除):

extension Array {
    // Code taken from https://stackoverflow.com/a/26174259/1975001
    // 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)
        }
    }
}

此版本使用Swift基金会桥提供的NSIndexSet SequenceType扩展名。

编辑:此外,更新版本的Swift(不记得添加了哪一个)有一个名为struct的{​​{1}}类型,可以与IndexSet桥接。并且NSIndexSet可以reverse()

答案 5 :(得分:2)

要完成Ethan's answer,将在Swift 3.0中弃用C循环样式。这是一个Swift 3.0兼容的答案:

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

答案 6 :(得分:1)

我使用了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!)
        })
}

答案 7 :(得分:1)

我需要这个才能使用NSTableview。这就是我使用的。

.bashrc

答案 8 :(得分:1)

基于Kent的解决方案,但针对 Swift 3

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

答案 9 :(得分:1)

Swift 4尝试

with    products as
        (
        select  P_ID
        ,       C_ID
        from    CompanyProducts
        join    Orders
        on      P_Name like '%' + O_Name + '%'
        )
select  c.C_ID
,       (select count(distinct P_ID) from products) - count(distinct P_ID)
from    Companies c
left join    
        products p
on      c.C_ID = p.C_ID
group by
        c.C_ID

答案 10 :(得分:0)

一种方法是:

var arrayB = arrayA.removeAtIndex(5)

另一种方式是:

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

答案 11 :(得分:0)

我找到了一个系统api,但它在iOS 13及更高版本的SwiftUI中都可用。?

enter image description here

答案 12 :(得分:0)

我还没有测试过,但是我敢打赌,如果非变异版本复制连续的子范围,它会更快。像这样:

state

答案 13 :(得分:-3)

以下是我目前使用的解决方案:

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)
    }
}