将驼峰式字符串分隔成空格分隔的单词

问题描述 投票:0回答:14

我想将驼峰式字符串分隔成新字符串中以空格分隔的单词。这是我到目前为止所拥有的:

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"

我倾向于怀疑,如果我在紧密循环中调用它或数千次,这可能不是转换为空格分隔单词的最有效方法。在 Swift 中是否有更有效的方法来做到这一点?

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

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

[编辑 3:] 更新了 Swift 4 语法,并添加了大写字母的缓存,这提高了非常长的字符串和紧密循环的性能。

swift string camelcasing
14个回答
29
投票
extension String {
    func camelCaseToWords() -> String {
        return unicodeScalars.dropFirst().reduce(String(prefix(1))) {
            return CharacterSet.uppercaseLetters.contains($1)
                ? $0 + " " + String($1)
                : $0 + String($1)
        }
    }
}
print("ÄnotherCamelCaps".camelCaseToWords()) // Änother Camel Caps

可能对某人有帮助:)


21
投票

单线解决方案

我同意@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)
。你们都应得的信用!


13
投票

我可能会迟到,但我想分享对 Augustine P A 答案或 Leo Dabus 评论的一些改进。
基本上,如果我们使用

upper camel case
表示法(如“DuckDuckGo”),该代码将无法正常工作,因为它会在字符串的开头添加一个空格。
为了解决这个问题,这是代码的稍微修改版本,使用 Swift 3.x,并且它兼容大小写:

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)
        }
    }
}

12
投票

更好的完整快捷解决方案...基于 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
    }
}

10
投票

据我在旧 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 应用程序执行的单个试验。)


9
投票
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"

出去

"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.)

在此输入链接描述


5
投票

我可以用更少的代码行(并且没有字符集)来完成此扩展,但是,是的,如果您想在大写字母前面插入空格,您基本上必须枚举每个字符串。

extension String {
    var differentCamelCaps: String {
        var newString: String = ""
        for eachCharacter in self {
            if "A"..."Z" ~= eachCharacter {
                newString.append(" ")
            }
            newString.append(eachCharacter)
        }
        return newString
    }
}

print("ÄnotherCamelCaps".differentCamelCaps) // Änother Camel Caps

3
投票

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)
        }
    }
}

3
投票

这是我使用 Unicode 字符类想到的:(Swift 5)

extension String {
    var titleCased: String {
        self
            .replacingOccurrences(of: "(\\p{UppercaseLetter}\\p{LowercaseLetter}|\\p{UppercaseLetter}+(?=\\p{UppercaseLetter}))",
                                  with: " $1",
                                  options: .regularExpression,
                                  range: range(of: self)
            )
            .capitalized
    }
}

输出:

填充路径 ➝ 填充路径
ThisStringHasNoSpaces ➝ 该字符串没有空格
IAmNotAGoat ➝ 我不是山羊
哈哈,太搞笑了! ➝ 哈哈,太搞笑了!
这是 ASMSMessage ➝ 这是 Asms 消息

3
投票

斯威夫特5+

对之前答案的小风格改进

import Foundation

extension String {
    func camelCaseToWords() -> String {
        unicodeScalars.reduce("") {
            guard CharacterSet.uppercaseLetters.contains($1),
                  $0.count > 0
            else { return $0 + String($1) }
            return ($0 + " " + String($1))
        }
    }
}

通常建议使用

guard let
语句,因为它们为不匹配的情况提供“提前退出”并减少代码的整体嵌套级别(这通常会大大提高可读性......并且记住,可读性很重要!)


2
投票

如果你想提高效率,可以使用

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)")

1
投票

快捷方式:

extension String {
    var titlecased: String {
        map { ($0.isUppercase ? " " : "") + String($0) }
            .joined(separator: "")
            .trimmingCharacters(in: .whitespaces)
    }
}

1
投票

这是在 iOS 16 和 macOS 13 中使用新的正则表达式 API 的简洁解决方案:

extension String {
    var camelToTitleCase: String {
        replacing(#/[[:upper:]]/#) { " " + $0.output }.capitalized
    }
}
"camelToTitleCase".camelToTitleCase -> "Camel To Title Case"

如果第一个字符也可能是大写的,我们可以添加

.trimmingCharacters(in: .whitespaces)

更通用(但稍微不太简洁)的解决方案:

extension StringProtocol {
    var string: String {
        String(self)
    }

    func prepending(_ other: Self) -> String {
        other.appending(self)
    }
}

extension String {
    var camelToTitleCase: String {
        replacing(#/(\b[[:lower:]])|(\B[[:upper:]])/#) {
            ($0.output.1?.string ?? $0.output.2?.string.prepending(" ") ?? "")
                .uppercased()
        }
    }
}

这里正则表达式匹配单词边界(包括字符串开头)的小写字符或大写字符,并且结果不需要大写或修剪。

"ÄnotherCamelCaps".camelToTitleCase -> "Änother Camel Caps"
"änotherCamelCaps".camelToTitleCase -> "Änother Camel Caps"

0
投票

使用正则表达式解决方案

let camelCase = "SomeATMInTheShop"
let regexPattern = "[A-Z-_&](?=[a-z0-9]+)|[A-Z-_&]+(?![a-z0-9])"
let newValue = camelCase.replacingOccurrences(of: regexPattern, with: " $0", options: .regularExpression, range: nil)

输出==>

Some ATM In The Shop

© www.soinside.com 2019 - 2024. All rights reserved.