计算 Swift 字符串中的行数

Count the number of lines in a Swift String

从网络服务读取了一个中等大小的文件(大约 500kByte)后,我得到了一个最初编码为 .isolatin1 的常规 Swift 字符串 (lines)。在实际拆分之前,我想(快速)计算行数以便能够初始化进度条。

最好的 Swift 成语是什么?

我想到了以下内容:

let linesCount = lines.reduce(into: 0) { (count, letter) in
   if letter == "\r\n" {
      count += 1
   }
}

这看起来还不错,但我在问自己是否有 shorter/faster 方法来做到这一点。 characters 属性 提供对一系列 Unicode 字符的访问,这些字符将 \r\n 视为唯一的实体。用所有 CharacterSet.newlines 检查这个是行不通的,因为 CharacterSet 不是一组 Character 而是一组 Unicode.Scalar (在我的书中有点违反直觉)这是一组 代码点(其中 \r\n 算作两个代码点),而不是 字形。正在尝试

var lines = "Hello, playground\r\nhere too\r\nGalahad\r\n"
lines.unicodeScalars.reduce(into: 0) { (cnt, letter) in
if CharacterSet.newlines.contains(letter) {
    cnt += 1
}

}

将计数为 6 而不是 3。所以这比上面的方法更通用,但是对于 CRLF 行结尾它不能正确工作。

有没有办法允许更多的行结束约定(如 CharacterSet.newlines 中),同时仍能为 CRLF 获得正确的结果?可以用更少的代码计算行数(同时仍然保持可读性)吗?

如果您可以在 NSString 上使用 Foundation 方法,我建议使用

enumerateLines(_ block: @escaping (String, UnsafeMutablePointer<ObjCBool>) -> Void)

这是一个例子:

import Foundation

let base = "Hello, playground\r\nhere too\r\nGalahad\r\n"
let ns = base as NSString

ns.enumerateLines { (str, _) in
    print(str)
}

考虑到所有换行类型,例如“\r\n”、“\n”等,它正确地分隔了行:

Hello, playground
here too
Galahad

在我的示例中,我打印了这些行,但是根据您的需要对它们进行计数很简单 - 我的版本仅用于演示。

因为我没有找到计算换行符的通用方法,所以我最终只是通过使用

遍历所有字符来解决我的问题
let linesCount = text.reduce(into: 0) { (count, letter) in
     if letter == "\r\n" {      // This treats CRLF as one "letter", contrary to UnicodeScalars
        count += 1
     }
}

我确信这比仅仅为了计数而枚举行要快得多,但我决定最终进行测量。今天我终于明白了,发现……我大错特错了。

一个 10000 行字符串在大约 1.0 秒内按上述方式计算行数,但使用

通过枚举进行计数
var enumCount = 0
text.enumerateLines { (str, _) in
    enumCount += 1
}

只用了大约 0.8 秒,而且始终快了 20% 多一点。我不知道 Swift 工程师在他们的袖子里藏了什么把戏,但他们确实很快 enumerateLines 了。这只是为了记录。

Swift 5 分机

extension String {
    
    func numberOfLines() -> Int {
        return self.numberOfOccurrencesOf(string: "\n") + 1
    }

    func numberOfOccurrencesOf(string: String) -> Int {
        return self.components(separatedBy:string).count - 1
    }
}

示例:

let testString = "First line\nSecond line\nThird line"
let numberOfLines = testString.numberOfLines() // returns 3

您可以使用以下扩展程序

extension String {

    var numberOfLines: Int {
        return self.components(separatedBy: "\n").count
    }

}

我使用这个,Apple 提供的 CharacterSet,专为这个任务制作:

let newLines = text.components(separatedBy: .newlines).count - 1