将CamelCase字符串分隔为Swift中空格分隔的单词(Separating CamelCase string into space-separated words in Swift)

我想将一个CamelCase字符串分隔成一个新字符串中的空格分隔的单词。 这是我到目前为止:

var camelCaps: String { guard self.count > 0 else { return self } var newString: String = "" let uppercase = CharacterSet.uppercaseLetters let first = self.unicodeScalars.first! newString.append(Character(first)) for scalar in self.unicodeScalars.dropFirst() { if uppercase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps // Produce: "a Camel Caps" let anotherCamelCaps = "ÄnotherCamelCaps" let anotherCamelCapped = anotherCamelCaps.camelCaps // "Änother Camel Caps"

我倾向于怀疑,如果我将它称为紧密循环或1000年的时间,这可能不是转换为空间分隔词的最有效方式。 在Swift中有更有效的方法吗?

[编辑1:]我需要的解决方案对Unicode标量应该保持通用,而不是特定于罗马ASCII“A..Z”。

[编辑2:]解决方案也应该跳过第一个字母,即不要在第一个字母前加一个空格。

[编辑3:]更新了Swift 4语法,并添加了uppercaseLetters的缓存,这可以提高超长字符串和紧密循环中的性能。

I would like to separate a CamelCase string into space-separated words in a new string. Here is what I have so far:

var camelCaps: String { guard self.count > 0 else { return self } var newString: String = "" let uppercase = CharacterSet.uppercaseLetters let first = self.unicodeScalars.first! newString.append(Character(first)) for scalar in self.unicodeScalars.dropFirst() { if uppercase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps // Produce: "a Camel Caps" let anotherCamelCaps = "ÄnotherCamelCaps" let anotherCamelCapped = anotherCamelCaps.camelCaps // "Änother Camel Caps"

I'm inclined to suspect that this may not be the most efficient way to convert to space-separated words, if I call it in a tight loop, or 1000's of times. Are there more efficient ways to do this in Swift?

[Edit 1:] The solution I require should remain general for Unicode scalars, not specific to Roman ASCII "A..Z".

[Edit 2:] The solution should also skip the first letter, i.e. not prepend a space before the first letter.

[Edit 3:] Updated for Swift 4 syntax, and added caching of uppercaseLetters, which improves performance in very long strings and tight loops.

最满意答案

据我测试我的旧MacBook,你的代码似乎对短字符串足够有效:

import Foundation extension String { var camelCaps: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters for scalar in self.unicodeScalars { if upperCase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } var camelCaps2: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters var range = self.startIndex..<self.endIndex while let foundRange = self.rangeOfCharacter(from: upperCase,range: range) { newString += self.substring(with: range.lowerBound..<foundRange.lowerBound) newString += " " newString += self.substring(with: foundRange) range = foundRange.upperBound..<self.endIndex } newString += self.substring(with: range) return newString } var camelCaps3: String { struct My { static let regex = try! NSRegularExpression(pattern: "[A-Z]") } return My.regex.stringByReplacingMatches(in: self, range: NSRange(0..<self.utf16.count), withTemplate: " $0") } } let aCamelCaps = "aCamelCaps" assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps2) assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps3) let t0 = Date().timeIntervalSinceReferenceDate for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps } let t1 = Date().timeIntervalSinceReferenceDate print(t1-t0) //->4.78703999519348 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps2 } let t2 = Date().timeIntervalSinceReferenceDate print(t2-t1) //->10.5831440091133 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps3 } let t3 = Date().timeIntervalSinceReferenceDate print(t3-t2) //->14.2085000276566

(不要试图在Playground中测试上面的代码,这些数字是从作为CommandLine应用程序执行的单个试验中获取的。)

As far as I tested on my old MacBook, your code seems to be efficient enough for short strings:

import Foundation extension String { var camelCaps: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters for scalar in self.unicodeScalars { if upperCase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } var camelCaps2: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters var range = self.startIndex..<self.endIndex while let foundRange = self.rangeOfCharacter(from: upperCase,range: range) { newString += self.substring(with: range.lowerBound..<foundRange.lowerBound) newString += " " newString += self.substring(with: foundRange) range = foundRange.upperBound..<self.endIndex } newString += self.substring(with: range) return newString } var camelCaps3: String { struct My { static let regex = try! NSRegularExpression(pattern: "[A-Z]") } return My.regex.stringByReplacingMatches(in: self, range: NSRange(0..<self.utf16.count), withTemplate: " $0") } } let aCamelCaps = "aCamelCaps" assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps2) assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps3) let t0 = Date().timeIntervalSinceReferenceDate for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps } let t1 = Date().timeIntervalSinceReferenceDate print(t1-t0) //->4.78703999519348 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps2 } let t2 = Date().timeIntervalSinceReferenceDate print(t2-t1) //->10.5831440091133 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps3 } let t3 = Date().timeIntervalSinceReferenceDate print(t3-t2) //->14.2085000276566

(Do not try to test the code above in the Playground. The numbers are taken from a single trial executed as a CommandLine app.)

将CamelCase字符串分隔为Swift中空格分隔的单词(Separating CamelCase string into space-separated words in Swift)

我想将一个CamelCase字符串分隔成一个新字符串中的空格分隔的单词。 这是我到目前为止:

var camelCaps: String { guard self.count > 0 else { return self } var newString: String = "" let uppercase = CharacterSet.uppercaseLetters let first = self.unicodeScalars.first! newString.append(Character(first)) for scalar in self.unicodeScalars.dropFirst() { if uppercase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps // Produce: "a Camel Caps" let anotherCamelCaps = "ÄnotherCamelCaps" let anotherCamelCapped = anotherCamelCaps.camelCaps // "Änother Camel Caps"

我倾向于怀疑,如果我将它称为紧密循环或1000年的时间,这可能不是转换为空间分隔词的最有效方式。 在Swift中有更有效的方法吗?

[编辑1:]我需要的解决方案对Unicode标量应该保持通用,而不是特定于罗马ASCII“A..Z”。

[编辑2:]解决方案也应该跳过第一个字母,即不要在第一个字母前加一个空格。

[编辑3:]更新了Swift 4语法,并添加了uppercaseLetters的缓存,这可以提高超长字符串和紧密循环中的性能。

I would like to separate a CamelCase string into space-separated words in a new string. Here is what I have so far:

var camelCaps: String { guard self.count > 0 else { return self } var newString: String = "" let uppercase = CharacterSet.uppercaseLetters let first = self.unicodeScalars.first! newString.append(Character(first)) for scalar in self.unicodeScalars.dropFirst() { if uppercase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps // Produce: "a Camel Caps" let anotherCamelCaps = "ÄnotherCamelCaps" let anotherCamelCapped = anotherCamelCaps.camelCaps // "Änother Camel Caps"

I'm inclined to suspect that this may not be the most efficient way to convert to space-separated words, if I call it in a tight loop, or 1000's of times. Are there more efficient ways to do this in Swift?

[Edit 1:] The solution I require should remain general for Unicode scalars, not specific to Roman ASCII "A..Z".

[Edit 2:] The solution should also skip the first letter, i.e. not prepend a space before the first letter.

[Edit 3:] Updated for Swift 4 syntax, and added caching of uppercaseLetters, which improves performance in very long strings and tight loops.

最满意答案

据我测试我的旧MacBook,你的代码似乎对短字符串足够有效:

import Foundation extension String { var camelCaps: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters for scalar in self.unicodeScalars { if upperCase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } var camelCaps2: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters var range = self.startIndex..<self.endIndex while let foundRange = self.rangeOfCharacter(from: upperCase,range: range) { newString += self.substring(with: range.lowerBound..<foundRange.lowerBound) newString += " " newString += self.substring(with: foundRange) range = foundRange.upperBound..<self.endIndex } newString += self.substring(with: range) return newString } var camelCaps3: String { struct My { static let regex = try! NSRegularExpression(pattern: "[A-Z]") } return My.regex.stringByReplacingMatches(in: self, range: NSRange(0..<self.utf16.count), withTemplate: " $0") } } let aCamelCaps = "aCamelCaps" assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps2) assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps3) let t0 = Date().timeIntervalSinceReferenceDate for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps } let t1 = Date().timeIntervalSinceReferenceDate print(t1-t0) //->4.78703999519348 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps2 } let t2 = Date().timeIntervalSinceReferenceDate print(t2-t1) //->10.5831440091133 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps3 } let t3 = Date().timeIntervalSinceReferenceDate print(t3-t2) //->14.2085000276566

(不要试图在Playground中测试上面的代码,这些数字是从作为CommandLine应用程序执行的单个试验中获取的。)

As far as I tested on my old MacBook, your code seems to be efficient enough for short strings:

import Foundation extension String { var camelCaps: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters for scalar in self.unicodeScalars { if upperCase.contains(scalar) { newString.append(" ") } let character = Character(scalar) newString.append(character) } return newString } var camelCaps2: String { var newString: String = "" let upperCase = CharacterSet.uppercaseLetters var range = self.startIndex..<self.endIndex while let foundRange = self.rangeOfCharacter(from: upperCase,range: range) { newString += self.substring(with: range.lowerBound..<foundRange.lowerBound) newString += " " newString += self.substring(with: foundRange) range = foundRange.upperBound..<self.endIndex } newString += self.substring(with: range) return newString } var camelCaps3: String { struct My { static let regex = try! NSRegularExpression(pattern: "[A-Z]") } return My.regex.stringByReplacingMatches(in: self, range: NSRange(0..<self.utf16.count), withTemplate: " $0") } } let aCamelCaps = "aCamelCaps" assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps2) assert(aCamelCaps.camelCaps == aCamelCaps.camelCaps3) let t0 = Date().timeIntervalSinceReferenceDate for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps } let t1 = Date().timeIntervalSinceReferenceDate print(t1-t0) //->4.78703999519348 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps2 } let t2 = Date().timeIntervalSinceReferenceDate print(t2-t1) //->10.5831440091133 for _ in 0..<1_000_000 { let aCamelCaps = "aCamelCaps" let camelCapped = aCamelCaps.camelCaps3 } let t3 = Date().timeIntervalSinceReferenceDate print(t3-t2) //->14.2085000276566

(Do not try to test the code above in the Playground. The numbers are taken from a single trial executed as a CommandLine app.)