Arrays 保存值或每次评估哪个方法更好?

Arrays 保存值或每次评估哪个方法更好?,arrays,swift,optimization,higher-order-functions,Arrays,Swift,Optimization,Higher Order Functions,我试图了解以下哪一项是更好的方法 我有一个structs的Array struct A { var selectionCount: Int } var ayes = [A]() 如果我想知道是否选择了任何元素,我是否应该每次都在项目上循环 func selectedCount() -> Int { return ayes.filter({ $0.selectionCount != 0 }).reduce(0, +) } // OR 存储var并在每次访问时访问它,如果

我试图了解以下哪一项是更好的方法

我有一个
struct
s的
Array

struct A {
    var selectionCount: Int
}

var ayes = [A]()
如果我想知道是否选择了任何元素,我是否应该每次都在项目上循环

func selectedCount() -> Int {
    return ayes.filter({ $0.selectionCount != 0 }).reduce(0, +)
}
// OR
存储
var
并在每次访问时访问它,如果我想知道是否进行了任何选择

var totalSelectedElements = 0

func select(at: Int) {
    ayes[at].selectionCount += 1
    totalSelectedElements += 1
}

func deselect(at: Int) {
    ayes[at].selectionCount -= 1
    totalSelectedElements -= 1
}

我倾向于投票反对存储可以从现有数据中派生的信息。然而,这种方法可能对性能至关重要。因此产生了两个问题:

  • 数组中的数量级是多少?我们说的只是几百件吗?如果是这样,您应该能够安全地忽略增加的开销
  • 需要多久访问一次有问题的值

  • 如果性能是“更好的方法”的意思,那么准备好一个值当然要比遍历成百上千个元素并获取它们的属性然后将它们相加快得多


    如果“更好的方法”意味着更好的API设计,那么前者更通用,因为从您的代码中调用任何对象
    select(at:)
    deselect(at:)
    ,因此
    selectionCount
    可能变为负值。。。您的代码将是有状态的,它将依赖于变量的状态。

    区分接口和实现很重要。首先设计所需的接口,然后始终可以更改内部实现以满足您的(性能与存储)需求

    我认为
    A
    的数组应该受到保护,您应该只允许通过
    select(at:)
    deselect(at:)
    方法进行访问。这允许您以任何一种方式执行内部实现:

    struct Ayes {
        private struct A {
            var selectionCount = 0
        }
    
        private var ayes = [A](repeating: A(), count: 100)
        private var totalSelectedElements = 0
    
        mutating func select(at: Int) {
            ayes[at].selectionCount += 1
            totalSelectedElements += 1
        }
    
        mutating func deselect(at: Int) {
            guard ayes[at].selectionCount > 0 else { return }
            ayes[at].selectionCount -= 1
            totalSelectedElements -= 1
        }
    
        func selectCount(at: Int) -> Int {
            return ayes[at].selectionCount
        }
    
        var totalElements: Int {
            return totalSelectedElements
        }
    }
    
    这实际上取决于您访问
    totalElements
    的频率,您是要存储它还是要计算它。通过隐藏实现细节,您可以自由地更改实现,而不会影响程序的其余部分

    我喜欢为快速访问维护计数的想法,通过保护对内部实现的访问,您可以保证计数是准确的


    示例:

    var ayes = Ayes()
    
    print(ayes.totalElements) // 0
    ayes.select(at: 3)
    ayes.select(at: 3)
    ayes.select(at: 4)
    print(ayes.totalElements) // 3
    print(ayes.selectCount(at: 3)) // 2
    ayes.deselect(at: 3)
    print(ayes.selectCount(at: 3)) // 1
    ayes.deselect(at: 3)
    print(ayes.selectCount(at: 3)) // 0
    ayes.deselect(at: 3)
    print(ayes.selectCount(at: 3)) // 0
    print(ayes.totalElements) // 1
    

    替代实施-相同接口

    此解决方案结合了使用字典和保持计数的思想:

    struct Ayes {
        private var ayes = [Int : Int]()
        private var totalSelectedElements = 0
    
        mutating func select(at: Int) {
            ayes[at, default: 0] += 1
            totalSelectedElements += 1
        }
    
        mutating func deselect(at: Int) {
            guard var count = ayes[at] else { return }
            count -= 1
            totalSelectedElements -= 1
            ayes[at] = count == 0 ? nil : count
        }
    
        func selectCount(at: Int) -> Int {
            return ayes[at, default: 0]
        }
    
        var totalElements: Int {
            return totalSelectedElements
        }
    }
    

    这避免了对预分配数组的需要,但仍然可以通过字典和内部计数进行快速访问。

    1。数组可以包含100到5000之间的元素。2.它可以变化,所以它取决于查询频率。多久一次?你可能可以将查询转移到后台队列中,并使用委托模式将任何重大更改通知你的应用程序,从而减轻查询的负担。就我个人而言,这是迄今为止最好的答案。您不想过早地在性能上花费太多时间。等你知道有问题的时候再关注它。这个答案考虑到未来很可能需要注意此代码,将其隔离,从而关闭使用它进行更改的客户端。