计算swift中最大值的最有效方法

时间:2017-05-23 21:22:53

标签: loops math swift3

我编写了以下程序,用于计算函数counter返回的最大值(collatzSequence(value: Int))。但是,每当我通过函数countLongestCollatzSequence()运行它时,Xcode总是会崩溃。我该怎么做才能使这段代码更好(或运行)?

谢谢。

func collatzSequence(value: Int) -> Int {
    var currentNumber: Int = value
    var counter: Int = 0
    while (currentNumber != 1) {
        if currentNumber % 2 == 0 {
            currentNumber = currentNumber / 2
            counter = counter + 1
        }
        else {
            currentNumber = (( currentNumber * 3 ) + 1 ) / 2
            counter = counter + 2
        }
    }
    return counter
}


func countLongestCollatzSequence() {
    var currentValue: Int = 0
    var largestValue: Int = 0
    for x in (50000...1000000).reversed() {
        currentValue = collatzSequence(value: x)
        if largestValue < currentValue {
            largestValue = currentValue
        }
    }
}

countLongestCollatzSequence()

PS:为清楚起见,第一个函数生成一个数字序列,第二个函数找出哪个序列最长。

2 个答案:

答案 0 :(得分:1)

你是在一个操场上做这件事的,这个开头有很多开销(例如,随着时间的推移跟踪价值),并且由于不明显的原因,会给游乐场带来严重的问题。

如果您将此代码放在一个独立的应用程序中,它运行得相当快。如果你将它作为发布版本运行(即打开优化),它运行得更快(我的MacBook Pro上0.3秒)。

最重要的是,游乐场不是最适合提高效率的游戏,所以如果你想最有效地运行它,我只需将代码放在一个独立的应用程序中,我认为你会对效率感到满意你现有的算法。

Re Alexander的记忆方法,将在WWDC 2014视频Advanced Swift中讨论。

答案 1 :(得分:1)

正如Rob所说,您希望将其编译为独立程序,并启用编译器优化。你不应该在操场上做基准测试。

我冒昧地写了一个这个算法的记忆版本,它真的很快。

首先,能够懒惰地生成序列非常重要,而不是总是被困在必须计算整个序列。考虑n = 8的情况。它产生序列[8, 4, 2, 1],其长度为4.假设我想立即计算n = 5的序列。我会得到[5, 16, 8, 4, 2, 1],其长度为6.请注意,最后8个元素是相同的。事实证明,如果我一块一块地生成这个元素,我可以得到[5, 16,,到达8,然后我知道我可以停止扩展序列。相反,我只能返回到目前为止我所拥有的元素数量(2),以及n = 84)的序列大小。这允许我短路 浩瀚 大部分计算。

以下是我CollatzSequence的实现:

struct CollatzSequence: Sequence {
    let n: Int

    init(_ n: Int) { self.n = n }

    public func makeIterator() -> CollatzIterator {
        return CollatzIterator(n)
    }
}

struct CollatzIterator: IteratorProtocol {
    var n: Int
    var done = false

    init(_ n: Int) { self.n = n }

    mutating func next() -> Int? {
        guard n > 1 else {
            guard !done else { return nil }
            done = true;
            return 1
        }

        defer {
            n = (n % 2 == 0)
                ? n/2 // even case
                : 3*n + 1 // odd case
        }

        return n
    }
}

接下来,我制作了一个计算器来计算任何给定CollatzSequence的长度。重要的是,它在我称为memoizedCounts的字典中保留了输入及其大小的历史记录。此字典使用基本情况初始化,n = 1生成序列[1],其长度为1。

struct CollatzSequenceLengthCalculator {
    var memoizedCounts = [1: 1]

    var previousElements = [Int]() // shared instance to save cost of reallocation

    mutating func lengthOfSequence(startingAt n: Int) -> Int {
//      print("====== n: \(n)")
        if let existingCount = memoizedCounts[n] {
//          print("Cache hit: counts[\(n)] is \(existingCount)")
            return existingCount
        }

        previousElements.removeAll(keepingCapacity: true)

        for i in CollatzSequence(n) {
            guard let existingCount = memoizedCounts[i] else {
//              print("No existing count for \(i)")
                previousElements.append(i)
                continue
            }

//          print("Cache hit: counts[\(i)] is \(existingCount).")
//          print("Going back to fill in previous these cache misses: \(previousElements)")

            for (offset, element) in previousElements.reversed().enumerated() {
                let newCount = offset + existingCount + 1
//              print("\tSetting counts[\(element)] to \(newCount)")
                memoizedCounts[element] = newCount
            }

            return existingCount + previousElements.count
        }

        fatalError("This should never happen")
    }
}

我还做了一个基准来比较这个优化算法中计算出的序列元素数量与天真方法的比较:

extension CollatzSequenceLengthCalculator {
    mutating func debug_lengthOfSequence(startingAt n: Int)
        -> (result: Int, numIterations: Int) {
//      print("====== n: \(n)")
        if let existingCount = memoizedCounts[n] { // redunant here to speed up common base case
//          print("Cache hit: counts[\(n)] is \(existingCount)")
            return (result: existingCount, numIterations: 0)
        }

        previousElements.removeAll(keepingCapacity: true)

        for i in CollatzSequence(n) {
            guard let existingCount = memoizedCounts[i] else {
//              print("No existing count for \(i)")
                previousElements.append(i)
                continue
            }

//          print("Cache hit: counts[\(i)] is \(existingCount).")
//          print("Going back to fill in previous these cache misses: \(previousElements)")

            for (offset, element) in previousElements.reversed().enumerated() {
                let newCount = offset + existingCount + 1
//              print("\tSetting counts[\(element)] to \(newCount)")
                memoizedCounts[element] = newCount
            }

            return (result: existingCount + previousElements.count, numIterations: previousElements.count)
        }

        fatalError("This should never happen")
    }
}

let input = 1...10000
var debugCalc = CollatzSequenceLengthCalculator()
let debug: [(input: Int, result: Int, numIterations: Int)] = input.map {
    let (result, numIterations) = debugCalc.debug_lengthOfSequence(startingAt: $0)
    return (input: $0, result: result, numIterations: numIterations)
}

//debug.forEach{ print($0) }

print("Total unoptimized iterations: \( debug.map{ $0.result }.reduce(0, +))")
print("Total optimized iterations: \( debug.map{ $0.numIterations }.reduce(0, +))")

我发现在n中计算1...100_000_000的所有序列时,朴素算法必须计算18_023_493_583个序列元素,而我的优化算法只需要217_289_746。那是 98.8%的减少!