【问题标题】:Optimizing adding dashes to a long Swift String优化将破折号添加到长 Swift 字符串
【发布时间】:2017-05-18 22:55:13
【问题描述】:

我正在尝试使用十六进制字符串并在每个其他字符之间插入破折号(例如“b201a968”到“b2-01-a9-68”)。我找到了几种方法来做到这一点,但问题是我的字符串相当大(8066 个字符),而且我可以让它工作的最快速度仍然需要几秒钟。这些是我尝试过的方法以及它们需要多长时间。谁能帮我优化一下这个功能?

//42.68 seconds
    func reformatDebugString(string: String) -> String
    {
        var myString = string
        var index = 2
        while(true){
            myString.insert("-", at: myString.index(myString.startIndex, offsetBy: index))
            index += 3
            if(index >= myString.characters.count){
                break
            }
        }

        return myString
    }

//21.65 seconds
    func reformatDebugString3(string: String) -> String
    {
        var myString = ""
        let length = string.characters.count
        var first = true
        for i in 0...length-1{
            let index = string.index(myString.startIndex, offsetBy: i)
            let c = string[index]

            myString += "\(c)"
            if(!first){
                myString += "-"
            }
            first = !first
        }

        return myString
    }

//11.37 seconds
    func reformatDebugString(string: String) -> String
    {
        var myString = string
        var index = myString.characters.count - 2
        while(true){
            myString.insert("-", at: myString.index(myString.startIndex, offsetBy: index))
            index -= 2
            if(index == 0){
                break
            }
        }

        return myString
    }

【问题讨论】:

  • 这似乎是并行化的主要用例——也许可以试试 dispatch_apply。

标签: swift string optimization


【解决方案1】:

所有三种方法的问题是使用index(_:offsetBy:) 来获取循环中当前字符的索引。这是一个 O(n) 运算,其中 n 是要偏移的距离 - 因此使您的所有三个函数都以二次时间运行。

此外,对于解决方案 #1 和 #3,插入结果字符串是一个 O(n) 操作,因为插入点之后的所有字符都必须向上移动以容纳添加的字符。在这种情况下,从头开始构建字符串通常更便宜,因为我们可以在字符串的末尾添加一个给定的字符,如果字符串有足够的容量,则为 O(1),否则为 O(n)。

对于解决方案 #1,说 myString.characters.count 是一个 O(n) 操作,所以不是您希望在循环的每次迭代中都做的事情。

因此,我们希望从头开始构建字符串,并避免在循环内索引和计算字符数。这是这样做的一种方法:

extension String {

    func addingDashes() -> String {

        var result = ""

        for (offset, character) in characters.enumerated() {

            // don't insert a '-' before the first character,
            // otherwise insert one before every other character.
            if offset != 0 && offset % 2 == 0 {
                result.append("-")
            }

            result.append(character)
        }
        return result
    }
}

// ...

print("b201a968".addingDashes()) // b2-01-a9-68

您在发布版本中的最佳解决方案(#3)在我的计算机上花费了 37.79 秒,上面的方法花费了 0.023 秒。

【讨论】:

  • 简单、优雅、快速。 EZ PZ。
  • @Hamish,我在下面的链接中分享了我的 Xcode 游乐场。在我的系统上,我的解决方案大约需要 12.6 秒,您的解决方案大约需要 20.5 秒,而 OOper 大约需要 15.3 秒。你可以在你的系统上试试吗? drive.google.com/open?id=0Bz423-2RSnjHMzlGbWNpQy1LSG8
  • @RyanTensmeyer 不要使用游乐场——它们真的很麻烦而且不可靠。如果您将代码放入实际项目(macOS 命令行工具模板是一个不错的选择),您应该会看到正确的结果(此外,最好在发布版本中进行基准测试以考虑编译器优化 - 而还要确保编译器不会完全优化掉该方法)。 Here's my benchmarking code.
  • @Hamish 感谢您的帮助。很好的解决方案。我将 OOPer 的解决方案标记为答案,因为它稍微快一些。
  • @RyanTensmeyer 乐于助人:)
【解决方案2】:

正如 Hamish 的回答中已经指出的,您应该避免这两件事:

  • string.index(string.startIndex, offsetBy: ...)计算每个索引
  • insert(_:at:)修改一个大字符串

所以,这可以是另一种方式:

func reformatDebugString4(string: String) -> String {
    var result = ""

    var currentIndex = string.startIndex
    while currentIndex < string.endIndex {
        let nextIndex = string.index(currentIndex, offsetBy: 2, limitedBy: string.endIndex) ?? string.endIndex
        if currentIndex != string.startIndex {
            result += "-"
        }
        result += string[currentIndex..<nextIndex]
        currentIndex = nextIndex
    }

    return result
}

【讨论】:

  • 分割中间字符的好主意——这比我的要快(在快速基准测试中是 2.08 秒 vs 3.06 秒)。
  • @Hamish,感谢您的评价。在我的旧 MacBook 上,我找不到与您的显着差异。但enumerated() 的开销在某些情况下可能更大,因此值得尝试避免它。
  • 在这种情况下,enumerated() 的使用实际上似乎对性能没有任何明显影响——这是我所期望的,因为编译器应该能够将其优化到仅循环外的简单递增值。您的实现获得的速度提升似乎主要来自中间字符的切片(执行两个单独的附加会再次减慢速度)。
  • @Hamish,谢谢,我明白了。事实上,我偶尔会发现 两个单独的附加(你可以猜到我有 5、6、...)会出现一些减速,然后你解释一下。
  • @RyanTensmeyer,再次感谢。似乎我的实际上更快,尽管正如您在 Hamish 的 cmets 中看到的那样只是运气。无论如何,避免重复的 O(n) 操作更为重要,在优化代码时应该牢记这一点。愿您的应用程序出色而快速。祝你好运。
猜你喜欢
  • 2015-07-26
  • 1970-01-01
  • 2017-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-01
  • 2014-03-09
  • 1970-01-01
相关资源
最近更新 更多