如何将一个字符串分割成相等长度的子串

33
所以
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是否小于string.endIndex之前,我会收到"fatal error: can not increment endIndex"的错误信息,因此下面的代码无法正常工作。
var string = "12345"
var currentIndex = string.startIndex
currentIndex = advance(currentIndex, 6)
if currentIndex > string.endIndex {currentIndex = string.endIndex}

更新了一个问题。 - yshilov
这里的advance()函数的三个参数版本非常方便,可以参考http://stackoverflow.com/questions/30128282/how-to-use-advance-function-in-swift-with-three-parameters。 - Martin R
您可以使用此链接 https://dev59.com/KKfja4cB1Zd3GeqP3fX1#48089097 惰性地生成子字符串。它也适用于子串。 - Leo Dabus
13个回答

39

我刚在 Stack Overflow 上回答了一个类似的问题,想着我可以提供一个更简洁的解决方案:

Swift 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]
    }
}

Swift 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]
    }
}

Swift 4

按照广泛要求,将其更改为 while 循环以提高效率,并成为 String 的扩展:

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

1
谢谢!真正的Swifty解决方案,可能最好将其添加到String扩展中。 - Igor Palaguta
真的,最好的Swift解决方案,这里是一个扩展: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 return self[startIndex..<endIndex] } } } - Nik

18

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

1
这将优化每个子字符串的上限偏移量,但下限总是从起始索引不必要地偏移。您应该保留最后一个上限以从那里进行偏移。 - Leo Dabus

11

只需要一遍扫描字符序列就可以轻松解决这个问题:

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

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

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

希望这能帮到您。


10

基于“Code Different”答案的字符串扩展:

Swift 5

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

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

5
以下是一个字符串扩展,如果您想在特定长度处拆分字符串,但也考虑单词,则可以使用它: Swift 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." ]

4

我的解决方案是使用字符数组:

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

这将不必要地从起始索引偏移所有索引 - Leo Dabus

3

您不能使用超过字符串大小的范围。以下方法将演示如何解决此问题:

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:版本特别有用。


1
看起来是一个可行的变体,谢谢。 附言:我会将长度更改为str.characters.count。 - yshilov
这个解决方案已经过时,不适用于Swift 2.0。使用UTF8视图可能会在我们心爱的表情符号存在时导致奇怪的行为。 - Matteo Piombo
@yshilov,这很有道理,我已经更新了答案。 - Adam
@MatteoPiombo 这个答案并不是“相对于Swift 2.0已经过时的”。事实上,它是使用Xcode7和Swift2编写的。UTF8问题与Swift版本无关。 - Adam
@Adam,我应该更具体地说明一下Swift 2.0。我指的是最新的Xcode 7 Beta 6。在这个beta版本中,索引方面有了重大变化。substringWithRange已经不存在了。 - Matteo Piombo
@MatteoPiombo更新了适用于新Xcode的答案。substringWithRange 运行良好,但是advance函数已被替换。 - Adam

2

一种现代的(2021+)解决方案是使用ChunkedSwift Algorithms包。

let string = "There are fourty-eight characters in this string"
let chunked = string.chunks(ofCount: 20)
print(Array(chunked))

2

endIndex 不是一个有效的索引;它比有效范围多一个。


是的,我甚至不尝试使用此索引调用任何字符串方法,我只是使用任意移位推进我的变量currentIndex,并在验证此新索引之前出现错误。 - yshilov
你比较了 currentIndex > endIndex 但是 currentIndex 永远不会超过 endIndex - 在到达那里之前就会抛出异常。 - GoZoner

1

代码

人们不应该使用 stride() 来实现这个功能。
一个普通的 Range<Int> 就足够了。

这是一个简单但优化的 Swift5 解决方案:

extension String {
    func split(by length: Int) -> [String] {
        guard length > 0 else { return [] }
        var start: Index!
        var end = startIndex
        return (0...count/length).map { _ in
            start = end
            end = index(start, offsetBy: length, limitedBy: endIndex) ?? endIndex
            return String(self[start..<end])
        }
    }
}
  • 由于在 map 函数中跟踪了 startend 索引,因此它不会重复自身。
  • count/length 处理了当 length 超过 count 的情况。
  • guardlength <= 0 的情况下是必要的。

用法

let splittedHangeul = "체르노빌같던후쿠시마원전폭발".split(by: 3)
let splittedEnglish = "THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG".split(by: 6)

print(splittedHangeul)
print(splittedEnglish)
//["체르노", "빌같던", "후쿠시", "마원전", "폭발"]
//["THEQUI", "CKBROW", "NFOXJU", "MPSOVE", "RTHELA", "ZYDOG"] 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接