【问题标题】:How to split a string into substrings of equal length如何将字符串拆分为等长的子字符串
【发布时间】:2015-08-25 19:16:23
【问题描述】:

所以

split("There are fourty-eight characters in this string", 20)

应该返回

["There are fourty-eig", "ht characters in thi","s string"]

如果我让 currentIndex = string.startIndex 然后尝试将其提前()它比 string.endIndex 更远,我会在检查我的 currentIndex

var string = "12345"
var currentIndex = string.startIndex
currentIndex = advance(currentIndex, 6)
if currentIndex > string.endIndex {currentIndex = string.endIndex}

【问题讨论】:

标签: string swift swift2


【解决方案1】:

我刚刚回答了关于 SO 的类似问题,并认为我可以提供更简洁的解决方案:

斯威夫特 2

func split(str: String, _ count: Int) -> [String] {
    return 0.stride(to: str.characters.count, by: count).map { i -> String in
        let startIndex = str.startIndex.advancedBy(i)
        let endIndex   = startIndex.advancedBy(count, limit: str.endIndex)
        return str[startIndex..<endIndex]
    }
}

斯威夫特 3

func split(_ str: String, _ count: Int) -> [String] {
    return stride(from: 0, to: str.characters.count, by: count).map { i -> String in
        let startIndex = str.index(str.startIndex, offsetBy: i)
        let endIndex   = str.index(startIndex, offsetBy: count, limitedBy: str.endIndex) ?? str.endIndex
        return str[startIndex..<endIndex]
    }
}

斯威夫特 4

为了提高效率,改成while循环,并根据大众的要求做成了字符串的扩展:

extension String {
    func split(by length: Int) -> [String] {
        var startIndex = self.startIndex
        var results = [Substring]()

        while startIndex < self.endIndex {
            let endIndex = self.index(startIndex, offsetBy: length, limitedBy: self.endIndex) ?? self.endIndex
            results.append(self[startIndex..<endIndex])
            startIndex = endIndex
        }

        return results.map { String($0) }
    }
}

【讨论】:

  • 谢谢!真正快速的解决方案,最好将其添加到扩展字符串
  • 真的,最好的快速解决方案,这是一个扩展:extension String { func split(_ count: Int) -> [String] { return stride(from: 0, to: self.characters.count , by: count).map { i -> String in let startIndex = self.index(self.startIndex, offsetBy: i) let endIndex = self.index(startIndex, offsetBy: count, limitedBy: self.endIndex) ?? self.endIndex 返回 self[startIndex..
【解决方案2】:

Swift 5,基于@Ondrej Stocek 解决方案

extension String {
    func components(withMaxLength length: Int) -> [String] {
        return stride(from: 0, to: self.count, by: length).map {
            let start = self.index(self.startIndex, offsetBy: $0)
            let end = self.index(start, offsetBy: length, limitedBy: self.endIndex) ?? self.endIndex
            return String(self[start..<end])
        }
    }
}

【讨论】:

  • 这优化了每个子字符串的上限的偏移量,但下限总是不必要地从起始索引一路偏移。您应该将最后一个保持在上限以从那里偏移
【解决方案3】:

只需遍历字符序列即可轻松解决此问题:

斯威夫特 2.2

extension String {
    func splitByLength(length: Int) -> [String] {
        var result = [String]()
        var collectedCharacters = [Character]()
        collectedCharacters.reserveCapacity(length)
        var count = 0
        
        for character in self.characters {
            collectedCharacters.append(character)
            count += 1
            if (count == length) {
                // Reached the desired length
                count = 0
                result.append(String(collectedCharacters))
                collectedCharacters.removeAll(keepCapacity: true)
            }
        }
        
        // Append the remainder
        if !collectedCharacters.isEmpty {
            result.append(String(collectedCharacters))
        }
        
        return result
    }
}

let foo = "There are fourty-eight characters in this string"
foo.splitByLength(20)

斯威夫特 3.0

extension String {
    func splitByLength(_ length: Int) -> [String] {
        var result = [String]()
        var collectedCharacters = [Character]()
        collectedCharacters.reserveCapacity(length)
        var count = 0
        
        for character in self.characters {
            collectedCharacters.append(character)
            count += 1
            if (count == length) {
                // Reached the desired length
                count = 0
                result.append(String(collectedCharacters))
                collectedCharacters.removeAll(keepingCapacity: true)
            }
        }
        
        // Append the remainder
        if !collectedCharacters.isEmpty {
            result.append(String(collectedCharacters))
        }
        
        return result
    }
}

let foo = "There are fourty-eight characters in this string"
foo.splitByLength(20)

由于字符串是一种相当复杂的类型,范围和索引可能具有不同的计算成本,具体取决于视图。这些细节仍在不断发展,因此上述一次性解决方案可能是更安全的选择。

希望对你有帮助

【讨论】:

    【解决方案4】:

    基于“代码不同”答案的字符串扩展:

    斯威夫特 3/4/5

    extension String {
        func components(withLength length: Int) -> [String] {
            return stride(from: 0, to: self.characters.count, by: length).map {
                let start = self.index(self.startIndex, offsetBy: $0)
                let end = self.index(start, offsetBy: length, limitedBy: self.endIndex) ?? self.endIndex
                return self[start..<end]
            }
        }
    }
    

    用法

    let str = "There are fourty-eight characters in this string"
    let components = str.components(withLength: 20)
    

    【讨论】:

      【解决方案5】:

      如果您想以一定长度分割字符串,可以使用以下字符串扩展名,但也要考虑单词:

      斯威夫特 4:

      func splitByLength(_ length: Int, seperator: String) -> [String] {
          var result = [String]()
          var collectedWords = [String]()
          collectedWords.reserveCapacity(length)
          var count = 0
          let words = self.components(separatedBy: " ")
      
          for word in words {
              count += word.count + 1 //add 1 to include space
              if (count > length) {
                  // Reached the desired length
      
                  result.append(collectedWords.map { String($0) }.joined(separator: seperator) )
                  collectedWords.removeAll(keepingCapacity: true)
      
                  count = word.count
                  collectedWords.append(word)
              } else {
                  collectedWords.append(word)
              }
          }
      
          // Append the remainder
          if !collectedWords.isEmpty {
              result.append(collectedWords.map { String($0) }.joined(separator: seperator))
          }
      
          return result
      }
      

      这是对 Matteo Piombo 上述回答的修改。

      用法

      let message = "Here is a string that I want to split."
      let message_lines = message.splitByLength(18, separator: " ")
      
      //output: [ "Here is a string", "that I want to", "split." ]
      

      【讨论】:

        【解决方案6】:

        我的字符数组解决方案:

        func split(text: String, count: Int) -> [String] {
            let chars = Array(text)
            return stride(from: 0, to: chars.count, by: count)
                .map { chars[$0 ..< min($0 + count, chars.count)] }
                .map { String($0) }
        }
        

        或者您可以对带有 Substring 的大字符串使用更优化的变体:

        func split(text: String, length: Int) -> [Substring] {
            return stride(from: 0, to: text.count, by: length)
                .map { text[text.index(text.startIndex, offsetBy: $0)..<text.index(text.startIndex, offsetBy: min($0 + length, text.count))] }
        }
        

        【讨论】:

        • 这不必要地从起始索引偏移了所有索引
        【解决方案7】:

        您不得使用超出字符串大小的范围。下面的方法将演示如何做到这一点:

        extension String {
            func split(len: Int) -> [String] {
                var currentIndex = 0
                var array = [String]()
                let length = self.characters.count
                while currentIndex < length {
                    let startIndex = self.startIndex.advancedBy(currentIndex)
                    let endIndex = startIndex.advancedBy(len, limit: self.endIndex)
                    let substr = self.substringWithRange(Range(start: startIndex, end: endIndex))
                    array.append(substr)
                    currentIndex += len
                }
                return array
            }
        }
        

        用法:

        "There are fourty-eight characters in this string".split(20)
        //output: ["There are fourty-eig", "ht characters in thi", "s string"]
        

        "???????⛵".split(3)
        //output: ["???", "???", "?⛵"]
        

        编辑: 更新了使用 Xcode 7 beta 6 的答案。advance 方法消失了,取而代之的是IndexadvancedBy 实例方法。 advancedBy:limit: 版本在这种情况下特别有用。

        【讨论】:

        • 似乎是一个可行的变体,谢谢。 p.s.我会将长度更改为 str.characters.count
        • 这个解决方案对于 Swift 2.0 来说已经过时了。当我们心爱的表情符号出现时,使用 UTF8 视图可能会导致奇怪的行为。
        • @yshilov 这很有道理,我已经更新了答案。
        • @MatteoPiombo 这个答案不是“相对于 Swift 2.0 过时”。事实上,它是使用 Xcode7 和 Swift2 编写的。 UTF8 问题与 Swift 版本无关。
        • @Adam 我应该更具体地了解 Swift 2.0。我指的是最新的 Xcode 7 Beta 6。在这个 beta 中,索引方面有很大的变化。 substringWithRange 已不存在。
        【解决方案8】:

        endIndex 不是有效索引;它比有效范围大一。

        【讨论】:

        • 是的,我什至不尝试使用此索引调用任何字符串方法,我只是将变量 currentIndex 任意移位并在验证此新索引之前出现错误
        • 您比较了currentIndex &gt; endIndex,但currentIndex 永远不会超过endIndex - 在到达之前会引发异常。
        【解决方案9】:

        这是一个版本,适用于以下情况:

        • 给定的行长度为 0 或更小
        • 输入为空
        • 一行的最后一个单词放不下:该单词换行了
        • 一行的最后一个单词长于行长:该单词被部分剪切和包裹
        • 一行的最后一个单词比多行长:该单词被多次剪切和换行。
        extension String {
        
            func ls_wrap(maxWidth: Int) -> [String] {
                guard maxWidth > 0 else {
                    Logger.logError("wrap: maxWidth too small")
                    return []
                }
                let addWord: (String, String) -> String = { (line: String, word: String) in
                    line.isEmpty
                        ? word
                        : "\(line) \(word)"
                }
                let handleWord: (([String], String), String) -> ([String], String) = { (arg1: ([String], String), word: String) in
                    let (acc, line): ([String], String) = arg1
                    let lineWithWord: String = addWord(line, word)
                    if lineWithWord.count <= maxWidth { // 'word' fits fine; append to 'line' and continue.
                        return (acc, lineWithWord)
                    } else if word.count > maxWidth { // 'word' doesn't fit in any way; split awkwardly.
                        let splitted: [String] = lineWithWord.ls_chunks(of: maxWidth)
                        let (intermediateLines, lastLine) = (splitted.ls_init, splitted.last!)
                        return (acc + intermediateLines, lastLine)
                    } else { // 'line' is full; start with 'word' and continue.
                        return (acc + [line], word)
                    }
                }
                let (accLines, lastLine) = ls_words().reduce(([],""), handleWord)
                return accLines + [lastLine]
            }
            
            // stolen from https://stackoverflow.com/questions/32212220/how-to-split-a-string-into-substrings-of-equal-length
            func ls_chunks(of length: Int) -> [String] {
                var startIndex = self.startIndex
                var results = [Substring]()
                while startIndex < self.endIndex {
                    let endIndex = self.index(startIndex, offsetBy: length, limitedBy: self.endIndex) ?? self.endIndex
                    results.append(self[startIndex..<endIndex])
                    startIndex = endIndex
                }
                return results.map { String($0) }
            }
            
            // could be improved to split on whiteSpace instead of only " " and "\n"
            func ls_words() -> [String] {
                return split(separator: " ")
                    .flatMap{ $0.split(separator: "\n") }
                    .map{ String($0) }
            }
        }
        
        extension Array {
            
            var ls_init: [Element] {
                return isEmpty
                    ? self
                    : Array(self[0..<count-1])
            }
        }
        

        【讨论】:

          【解决方案10】:

          使用 while 循环的解决方案实际上比使用 stride 的解决方案更灵活一些。这是亚当回答的一个小更新(Swift 5):

          extension String {
          
          func split(len: Int) -> [String] {
              
              var currentIndex = 0
              var array = [String]()
              let length = self.count
              
              while currentIndex < length {
                  let startIndex = index(self.startIndex, offsetBy: currentIndex)
                  let endIndex = index(startIndex, offsetBy: len, limitedBy: self.endIndex) ?? self.endIndex
                  let substr = String( self[startIndex...endIndex] )
                  array.append(substr)
                  currentIndex += len
              }
              
              return array
              
          }
          

          }

          我们可以将它概括为一个 Int 数组而不是单个 Int。这样我们就可以将一个字符串拆分成不同长度的子字符串,如下所示:

          extension String {
          func split(len: [Int]) -> [String] {
              
              var currentIndex = 0
              var array = [String]()
              let length = self.count
              var i = 0
              
              while currentIndex < length {
                  let startIndex = index(self.startIndex, offsetBy: currentIndex)
                  let endIndex = index(startIndex, offsetBy: len[i], limitedBy: self.endIndex) ?? self.endIndex
                  let substr = String( self[startIndex..<endIndex] )
                  array.append(substr)
                  currentIndex += len[i]
                  i += 1
              }
              
              return array
              
          }
          

          }

          用法:

          func testSplitString() throws {
          var retVal = "Hello, World!".split(len: [6, 1, 6])
          XCTAssert( retVal == ["Hello,", " ", "World!"] )
                                
          retVal = "Hello, World!".split(len: [5, 2, 5, 1])
          XCTAssert( retVal == ["Hello", ", ", "World", "!"] )
          
          retVal = "hereyouare".split(len: [4, 3, 3])
          XCTAssert( retVal == ["here", "you", "are"] )
          

          }

          【讨论】:

            猜你喜欢
            • 2011-04-11
            • 2011-04-15
            • 2016-04-17
            • 1970-01-01
            • 2021-08-11
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多