【问题标题】:Array Contains Too Slow Swift数组包含太慢的 Swift
【发布时间】:2017-05-20 23:50:51
【问题描述】:

我一直在将我在 Java (Android) 中使用的算法移植到 Swift (iOS),并且在 Swift 版本上遇到了一些速度问题。

基本思想是存在具有深度的对象(评论树),我可以通过匹配隐藏对象列表来隐藏和显示来自数据集的回复。下面是一个可视化

Top
- Reply 1
- - Reply 2
- - Reply 3
- Reply 4

从数据集中隐藏后

Top
- Reply 1
- Reply 4

我从Java转换的相关方法如下

 //Gets the "real" position of the index provided in the "position" variable. The comments array contains all the used data, and the hidden array is an array of strings that represent items in the dataset that should be skipped over.

    func getRealPosition(position: Int)-> Int{

        let hElements = getHiddenCountUpTo(location: position)
        var diff = 0
        var i = 0
        while i < hElements {
            diff += 1
            if(comments.count > position + diff && hidden.contains(comments[(position + diff)].getId())){
                i -= 1
            }
            i += 1
        }
        return position + diff
    }

    func getHiddenCountUpTo(location: Int) -> Int{
        var count = 0
        var i = 0
        repeat {
            if (comments.count > i && hidden.contains(comments[i].getId())) {
                count += 1
            }
            i += 1
        } while(i <= location && i < comments.count)
        return count
    }

这与 UITableViewController 一起使用以将 cmets 显示为树。

在 Java 中,使用 array.contains 足够快,不会造成任何延迟,但 Swift 版本在调用 heightForRowAt 和填充单元格时会多次调用 getRealPosition 函数,导致添加更多评论 ID 时会增加延迟到“隐藏”数组。

有什么方法可以提高数组“包含”查找的速度(可能使用不同类型的集合)?我对应用程序进行了分析,“包含”是占用时间最多的方法。

谢谢

【问题讨论】:

  • 尝试使用NSOrderedSet?我意识到这不是一种特别 Swift-y 的方法,但一个有序的集合是你想要的最大速度成员测试。或者,您可以尝试 this open-source implementation 的 Swift OrderedSet
  • 问题不是contains。我认为,问题在于您的数据模型对于您想要显示的数据以及您想要用它做什么是错误的。如果您不能立即掌握数据,那么您的数据模型就是错误的。

标签: arrays swift performance contains


【解决方案1】:

Java 和 Swift 都必须遍历数组中包含的所有元素。随着数组变大,这会变得越来越慢。

Java 没有先验的理由表现更好,因为它们都使用完全相同的算法。然而,字符串在每种语言中的实现方式非常不同,因此可能会使 Swift 中的字符串比较更加昂贵。

无论如何,如果字符串比较会拖慢你的速度,那么你必须避免它。

轻松解决:使用 Set

如果你想要一个简单的性能提升,你可以用一组字符串替换一个字符串数组。 Swift 中的集合是用哈希表实现的,这意味着您期望恒定时间查询。实际上,这意味着对于大型集合,您会看到更好的性能。

    var hiddenset Set<String> = {}
    for item in hidden {
      strset.insert(item)
    }

为了获得最佳性能:使用 BitSet

但是你应该能够做得比一个系列做得更好。让我们看看你的代码

    hidden.contains(comments[i].getId()))

如果您总是以这种方式访问​​hidden,那么这意味着您拥有的是从整数 (i) 到布尔值(真或假)的映射。

那么你应该做以下...

    import Bitset;

    let hidden = Bitset ();
    // replace hidden.append(comments[i].getId())) by this:
    hidden.add(i)
    // replace hidden.contains(comments[i].getId())) by this:
    hidden.contains(i)

那么你的代码真的会飞起来!

要在 Swift 中使用快速的 BitSet 实现,请在 Package.swift 中包含以下内容(它是免费软件):

    import PackageDescription

    let package = Package(
        name: "fun",
        dependencies: [
       .Package(url: "https://github.com/lemire/SwiftBitset.git",  majorVersion: 0)
        ]
    )

【讨论】:

    【解决方案2】:

    我认为您需要 realPosition 从点击 tableview 中的一行链接到源数组?

    1) 为 tableViewDataSource 创建第二个数组

    将所有可见元素复制到这个新数组。创建一个特殊的 ViewModel 作为类或更好的结构,它只有必要的数据才能显示在 tableview 中。将 realdataposition 也保存在这个新的 ViewModel 中作为值。现在你有一个指向源数组的反向链接

    2) 然后仅从新数据源填充此 TableView

    3) 快速查看functional programming - 例如,您可以更好地查看数组:

    var array1 = ["a", "b", "c", "d", "e"]
    let array2 = ["a", "c", "d"]
    array1 = array1.filter { !array2.contains($0) }
    

    或者在你的情况下:

    let newArray = comments.filter{ !hidden.contains($0.getId()) }
    

    或枚举创建视图模型

    struct CommentViewModel {
      var id: Int
      var text: String
      var realPosition: Int
    }
    
    let visibleComments: [CommentViewModel] = comments
       .enumerated()
       .map { (index, element) in
          return CommentViewModel(id: element.getId(), text: element.getText(), realPosition: index)
       }
       .filter{ !hidden.contains($0.id) }    
    

    【讨论】:

    • 非常感谢您的建议!每当我从数据集中取出项目时,我最终都会过滤一个新数组,并使用该新数组来填充数据,而无需使用 getHiddenCountUpTo。这让整个事情变得更快了,我肯定会考虑用更少的开销制作一个更好的模型
    猜你喜欢
    • 2012-04-13
    • 1970-01-01
    • 1970-01-01
    • 2016-05-28
    • 2021-01-26
    • 2020-12-13
    • 1970-01-01
    • 2016-11-05
    • 2016-03-07
    相关资源
    最近更新 更多