Разделение строки CamelCase в словах, разделенных пробелами, в 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, что повышает производительность в очень длинных строках и узких циклах.
Ответы
Ответ 1
Насколько я тестировал на своем старом 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
(Не пытайтесь проверить код выше на игровой площадке. Числа берутся из одного прогона, выполненного в виде приложения CommandLine.)
Ответ 2
Вот еще один способ сделать то же самое для Swift 2.x
extension String {
func camelCaseToWords() -> String {
return unicodeScalars.reduce("") {
if NSCharacterSet.uppercaseLetterCharacterSet().characterIsMember(uint_least16_t($1.value)) {
return ($0 + " " + String($1))
}
else {
return ($0 + String($1))
}
}
}
}
Swift 3.x
extension String {
func camelCaseToWords() -> String {
return unicodeScalars.reduce("") {
if CharacterSet.uppercaseLetters.contains($1) {
return ($0 + " " + String($1))
}
else {
return $0 + String($1)
}
}
}
}
Может быть полезным для кого-то :)
Ответ 3
Возможно, я опаздываю, но хочу поделиться небольшим улучшением с ответом Августин PA или Лео Дабус комментарии.
В принципе, этот код не будет работать должным образом, если мы используем нотацию upper camel case
(например, "DuckDuckGo" ), потому что она добавит пробел в начале строки.
Чтобы устранить эту проблему, это немного измененная версия кода, используя Swift 3.x, и она совместима как с верхним, так и с нижним регистром:
extension String {
func camelCaseToWords() -> String {
return unicodeScalars.reduce("") {
if CharacterSet.uppercaseLetters.contains($1) {
if $0.characters.count > 0 {
return ($0 + " " + String($1))
}
}
return $0 + String($1)
}
}
}
Ответ 4
Решение One Line
Я согласен с @aircraft, регулярные выражения могут решить эту проблему в одном LOC!
// Swift 5 (and probably 4?)
extension String {
func titleCase() -> String {
return self
.replacingOccurrences(of: "([A-Z])",
with: " $1",
options: .regularExpression,
range: range(of: self))
.trimmingCharacters(in: .whitespacesAndNewlines)
.capitalized // If input is in llamaCase
}
}
Поддерживает этот ответ JS.
Постскриптум У меня есть суть для snake_case → CamelCase
здесь.
P.P.S. Я обновил это для New Swift (в настоящее время 5.1), затем увидел ответ @busta и поменял мой startIndex..<endIndex
на его range(of: self)
. Кредит, где он должен вас всех!
Ответ 5
лучшее полное решение... на основе ответа AmitaiB
extension String {
func titlecased() -> String {
return self.replacingOccurrences(of: "([A-Z])", with: " $1", options: .regularExpression, range: self.range(of: self))
.trimmingCharacters(in: .whitespacesAndNewlines)
.capitalized
}
}
Ответ 6
Я могу сделать это расширение в меньших строках кода (и без CharacterSet), но да, вы в основном должны перечислить каждую строку, если вы хотите вставить пробелы перед прописными буквами.
var differentCamelCaps: String {
var newString: String = ""
for eachCharacter in self.characters {
if (eachCharacter >= "A" && eachCharacter <= "Z") == true {
newString.append(" ")
}
newString.append(eachCharacter)
}
return newString
}
Ответ 7
Если вы хотите сделать его более эффективным, вы можете использовать Regular Expressions
.
extension String {
func replace(regex: NSRegularExpression, with replacer: (_ match:String)->String) -> String {
let str = self as NSString
let ret = str.mutableCopy() as! NSMutableString
let matches = regex.matches(in: str as String, options: [], range: NSMakeRange(0, str.length))
for match in matches.reversed() {
let original = str.substring(with: match.range)
let replacement = replacer(original)
ret.replaceCharacters(in: match.range, with: replacement)
}
return ret as String
}
}
let camelCaps = "aCamelCaps" // there are 3 Capital character
let pattern = "[A-Z]"
let regular = try!NSRegularExpression(pattern: pattern)
let camelCapped:String = camelCaps.replace(regex: regular) { " \($0)" }
print("Uppercase characters replaced: \(camelCapped)")
Ответ 8
extension String {
func titlecased() -> String {
return self
.replacingOccurrences(of: "([a-z])([A-Z](?=[A-Z])[a-z]*)", with: "$1 $2", options: .regularExpression)
.replacingOccurrences(of: "([A-Z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression)
.replacingOccurrences(of: "([a-z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression)
.replacingOccurrences(of: "([a-z])([A-Z][a-z])", with: "$1 $2", options: .regularExpression)
}
}
В
"ThisStringHasNoSpacesButItDoesHaveCapitals"
"IAmNotAGoat"
"LOLThatsHilarious!"
"ThisIsASMSMessage"
Out
"This String Has No Spaces But It Does Have Capitals"
"I Am Not A Goat"
"LOL Thats Hilarious!"
"This Is ASMS Message" // (Difficult tohandle single letter words when they are next to acronyms.)
введите описание ссылки здесь
Ответ 9
Решение Swift 5
extension String {
func camelCaseToWords() -> String {
return unicodeScalars.reduce("") {
if CharacterSet.uppercaseLetters.contains($1) {
if $0.count > 0 {
return ($0 + " " + String($1))
}
}
return $0 + String($1)
}
}
}