将CamelCase字符串分隔为Swift中以空格分隔的单词

时间:2023-02-01 03:34:05

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

我想将一个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"

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?

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

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

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

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

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

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

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

7 个解决方案

#1


6  

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

据我在旧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

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

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

#2


5  

Here another method to do the same thing for Swift 2.x

这里是为Swift 2.x做同样事情的另一种方法

extension String {

    func camelCaseToWords() -> String {

        return unicodeScalars.reduce("") {

            if NSCharacterSet.uppercaseLetterCharacterSet().characterIsMember(uint_least16_t($1.value))  == true {
                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) == true {

                return ($0 + " " + String($1))
            }
            else {

                return $0 + String($1)
            }
        }
    }
}

May be helpful for someone :)

可能对某人有帮助:)

#3


3  

I might be late but I want to share a little improvement to Augustine P A answer or Leo Dabus comment.
Basically, that code won't work properly if we are using upper camel case notation (like "DuckDuckGo") because it will add a space at the beginning of the string.
To address this issue, this is a slightly modified version of the code, using Swift 3.x, and it's compatible with both upper and lower came case:

我可能会迟到但我希望与Augustine P分享一点改进答案或Leo Dabus评论。基本上,如果我们使用上部驼峰表示法(如“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


3  

One Line Solution

I concur with @aircraft, regular expressions can solve this problem in one LOC!

我同意@aircraft,正则表达式可以在一个LOC中解决这个问题!

extension String {
    func titleCase() -> String {
        return (self as NSString)
            .replacingOccurrences(of: "([A-Z])", with: " $1", options: 
.regularExpression, range: NSRange(location: 0, length: count))
            // optional
            .trimmingCharacters(in: .whitespacesAndNewlines) 
            .capitalized // If input is in llamaCase
    }
}

Props to this JS answer.

这个JS答案的道具。

P.S. I have a gist for snake_case → CamelCase here.

附:我对snake_case→CamelCase有一个要点。

#5


2  

I can do this extension in less lines of code (and without a CharacterSet), but yes, you basically have to enumerate each String if you want to insert spaces in front of capital letters.

我可以在较少的代码行中(并且没有CharacterSet)执行此扩展,但是,如果要在大写字母前面插入空格,则基本上必须枚举每个String。

var differentCamelCaps: String {

    var newString: String = ""

    for eachCharacter in self.characters {
        if (eachCharacter >= "A" && eachCharacter <= "Z") == true {
            newString.append(" ")
        }
        newString.append(eachCharacter)
    }

    return newString
}

#6


2  

If you want to make it more efficient, you can use 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)")

#7


1  

a better full swifty solution... based on AmitaiB answer

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

#1


6  

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

据我在旧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

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

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

#2


5  

Here another method to do the same thing for Swift 2.x

这里是为Swift 2.x做同样事情的另一种方法

extension String {

    func camelCaseToWords() -> String {

        return unicodeScalars.reduce("") {

            if NSCharacterSet.uppercaseLetterCharacterSet().characterIsMember(uint_least16_t($1.value))  == true {
                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) == true {

                return ($0 + " " + String($1))
            }
            else {

                return $0 + String($1)
            }
        }
    }
}

May be helpful for someone :)

可能对某人有帮助:)

#3


3  

I might be late but I want to share a little improvement to Augustine P A answer or Leo Dabus comment.
Basically, that code won't work properly if we are using upper camel case notation (like "DuckDuckGo") because it will add a space at the beginning of the string.
To address this issue, this is a slightly modified version of the code, using Swift 3.x, and it's compatible with both upper and lower came case:

我可能会迟到但我希望与Augustine P分享一点改进答案或Leo Dabus评论。基本上,如果我们使用上部驼峰表示法(如“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


3  

One Line Solution

I concur with @aircraft, regular expressions can solve this problem in one LOC!

我同意@aircraft,正则表达式可以在一个LOC中解决这个问题!

extension String {
    func titleCase() -> String {
        return (self as NSString)
            .replacingOccurrences(of: "([A-Z])", with: " $1", options: 
.regularExpression, range: NSRange(location: 0, length: count))
            // optional
            .trimmingCharacters(in: .whitespacesAndNewlines) 
            .capitalized // If input is in llamaCase
    }
}

Props to this JS answer.

这个JS答案的道具。

P.S. I have a gist for snake_case → CamelCase here.

附:我对snake_case→CamelCase有一个要点。

#5


2  

I can do this extension in less lines of code (and without a CharacterSet), but yes, you basically have to enumerate each String if you want to insert spaces in front of capital letters.

我可以在较少的代码行中(并且没有CharacterSet)执行此扩展,但是,如果要在大写字母前面插入空格,则基本上必须枚举每个String。

var differentCamelCaps: String {

    var newString: String = ""

    for eachCharacter in self.characters {
        if (eachCharacter >= "A" && eachCharacter <= "Z") == true {
            newString.append(" ")
        }
        newString.append(eachCharacter)
    }

    return newString
}

#6


2  

If you want to make it more efficient, you can use 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)")

#7


1  

a better full swifty solution... based on AmitaiB answer

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

相关文章