如何将字符串分割成等长的子串

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

那么

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 是否之前,我会收到“致命错误:无法增加 endIndex” < string.endIndex so the code below doesn't work

var string = "12345"
var currentIndex = string.startIndex
currentIndex = advance(currentIndex, 6)
if currentIndex > string.endIndex {currentIndex = string.endIndex}
string swift swift2
13个回答
39
投票

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

斯威夫特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) }
    }
}

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

11
投票

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

斯威夫特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)

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

希望这有帮助


10
投票

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

斯威夫特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
投票

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

斯威夫特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))] }
}

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
方法消失了,取而代之的是
advancedBy
Index
实例方法。
advancedBy:limit:
版本在这种情况下特别有用。


2
投票

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


2
投票

现代(2021+)解决方案是 Swift Algorithms 包的 Chunked

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

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])
        }
    }
}
  • 由于
    start
    end
    索引正在地图功能中进行跟踪, 它不会重复。
  • count/length
     超过 
    length
     时,
    count
    会进行处理。
  • guard
    length <= 0
    情况下需要的。

用法

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

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

0
投票

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

  • 给定的行长度为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])
    }
}

0
投票

使用 while 循环的解决方案实际上比使用 stride 的解决方案更灵活。这是 Adam 答案的轻微更新(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"] )

}


0
投票
extension String {
    func inserting(separator: String, every n: Int) -> String {
        enumerated().reduce("") { $0 + ((($1.offset + 1) % n == 0) ? String($1.element) + separator : String($1.element)) }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.