Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to split a string into substrings of equal length

So

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

should return

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

If I make currentIndex = string.startIndex and then try to advance() it further than a string.endIndex, I get "fatal error: can not increment endIndex" before I check if my currentIndex < 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}
like image 803
yshilov Avatar asked Aug 25 '15 19:08

yshilov


3 Answers

I just answered a similar question on SO and thought I can provide a more concise solution:

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

Changed to a while loop for better efficiency and made into a String's extension by popular request:

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) }
    }
}
like image 91
Code Different Avatar answered Oct 18 '22 21:10

Code Different


Swift 5, based on @Ondrej Stocek solution

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])
        }
    }
}
like image 45
Wujo Avatar answered Oct 18 '22 21:10

Wujo


This problem could be easily solved with just one pass through the characters sequence:

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)

Since String is a pretty complicated type, ranges and indexes could have different computational costs depending on the view. These details are still evolving, thus the above one-pass solution might be a safer choice.

Hope this helps

like image 11
Matteo Piombo Avatar answered Oct 18 '22 21:10

Matteo Piombo