【问题标题】:Separating CamelCase string into space-separated words in Swift在 Swift 中将 CamelCase 字符串分隔为空格分隔的单词
【发布时间】:2016-12-22 22:40:52
【问题描述】:

我想将 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 语法进行了更新,并添加了大写字母的缓存,从而提高了在非常长的字符串和紧凑循环中的性能。

【问题讨论】:

  • 单行return unicodeScalars.reduce("") { CharacterSet.uppercaseLetters.contains($1) ? $0 + " " + String($1) : $0 + String($1)}
  • 如何处理连续大写字符的字符串?例如:上面的代码“大写字母”返回为“大写字母 L E T T E R S”。而预期的输出是“大写字母”。
  • @Frankenxtein 只需检查正在创建的字符串,在我们的例子中是$0,看看最后一个字母是否也是大写的。如果是,您只需添加字符 $1,不带空格。

标签: ios swift string macos camelcasing


【解决方案1】:
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

可能对某人有帮助:)

【讨论】:

  • 非常感谢您的代码...我对其进行了一些修改以解决问题
  • @matteodv 很高兴听到代码对您有用。您能否提示您使用相同的逻辑解决了什么问题?可能对我和其他人也有帮助。
  • 当然!几天前我发布了一个答案;)stackoverflow.com/a/45981006/271742
  • 这不适用于“TestABC”之类的内容
【解决方案2】:

单线解决方案

我同意@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
    }
}

道具this JS answer

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

附言我为 New Swift(目前为 5.1)更新了这个,然后看到了 @busta 的答案,并将我的 startIndex..<endIndex 换成了他的 range(of: self)。归功于你们!

【讨论】:

  • 谢谢,我很欣赏这个答案,也许它对某些人有用。但是,我的目标不是实现一行代码,而是找到一个更高效、可维护的实现。正则表达式解决方案的运行速度比上面提供的其他解决方案慢,而最快的解决方案需要更多的代码行数。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    就我在旧 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 中测试上面的代码。这些数字取自作为命令行应用程序执行的单个试验。)

    【讨论】:

    • 最后一种方法 camelCaps3 似乎是最快的,因为它将正则表达式缓存在静态中。但是,它不处理非 ASCII 大写字母(例如 Ä)。
    • @BrianArnold,本文的主要意图是表明使用正则表达式并没有预期的那么快。如果它比您的代码更快,则可以对其进行改进以包含非 ASCII 大写字母。例如,您可以使用"\\p{Lu}" 代替"[A-Z]"。请自行尝试。
    • 谢谢,使用"\\p{Lu}"for 正则表达式更通用,当我将其编译为命令行工具以获得最佳性能时,正则表达式确实没有预期的那么快。奇怪的是,Leo Daubus 优化的单行建议在编译为命令行工具时也没有预期的那么快。所以,我只好保留我建议的代码,因为它足够快且可读性强。
    【解决方案5】:

    我可能会迟到,但我想与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)
            }
        }
    }
    

    【讨论】:

      【解决方案6】:
      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.)
      

      enter link description here

      【讨论】:

      • 这应该是公认的答案。这非常适合我正在寻找的内容,因为它可以处理应该保持在一起的大写字母,例如“LOL”。
      【解决方案7】:

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

      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
      

      【讨论】:

      • if "A"..."Z" ~= eachCharacter {
      【解决方案8】:

      如果你想让它更高效,你可以使用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)")
      

      【讨论】:

      • 是什么让你觉得这比OP的代码效率更高?
      【解决方案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)
              }
          }
      }
      

      【讨论】:

        【解决方案10】:

        这是我想出的使用 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
            }
        }
        

        输出:

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

        【讨论】:

          猜你喜欢
          • 2010-12-01
          • 1970-01-01
          • 2019-12-02
          • 1970-01-01
          • 2013-09-09
          • 2022-07-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多