Home > Net >  How can I split a String and keep the delimiters in Swift?
How can I split a String and keep the delimiters in Swift?

Time:02-12

I am new to Swift, I come from Java background. I want to make a struct for parsing input for a calculator. Here is my code

struct Parse {
var runningCalc: String

init(runningCalc: String) {
    self.runningCalc = runningCalc
}

mutating func toArray(String: runningCalc){
    runningCalc = runningCalc.split()
}}

I want to have input of a string, and have it return a string Array with delimiters of { -/*()%}.

In java you could do a string tokenizer to keep the delimiters in the returned string array.

How can I use the split or components method to separate my string input to a string array?

CodePudding user response:

I personally wouldn't use a struct. It's more common to extend an existing type with additional functionality. So, you want the components of a String that includes delimiters? Sounds like you want to extend String. Since String conforms to StringProtocol I'd extend StringProtocol so that any other types that conform to StringProtocol get this new functionality for free:

StringProtocol already has a method called components(separatedBy:) which takes a CharacterSet (a set of characters) to delimit the receiver.

Let's implement a method similar to the existing StringProtocol method.

extension StringProtocol {
    func components(separatedByIncluding delims: CharacterSet) -> [String] {
        var components: [String] = []
        var component = ""

        for character in self {
            if String(character).rangeOfCharacter(from: delims) != nil {
                if !component.isEmpty {
                    components.append(component)
                }
                components.append(String(character))
                component = ""
            } else {
                component  = [character]
            }
        }
        if !component.isEmpty {
            components.append(component)
        }

        return components
    }
}

The method loops over each Character and checks if it's a delimiter. If so, it adds a new String to the array consisting of the delimiter Character. If not, it constructs a substring consisting of non-delimiter Character values. The resulting array of delimiters and substrings are then returned.

Usage would be:

func testExample() throws {
    let delims = CharacterSet(charactersIn: "{  -/*()%}")
    let text = "{abc def ghi-jkl/mno"

    let components = text.components(separatedByIncluding: delims)

    print("================")
    print(components)
    print("================")
}

The result is:

================
["{", "abc", " ", "def", " ", "ghi", "-", "jkl", "/", "mno"]
================

CodePudding user response:

AFAIK there is no native Swift split method that allows you to keep the delimiters. You will need to implement your own split method extending Collection. Something like this:

extension Collection {
    func splitAndKeep(maxSplits: Int = .max, omittingEmptySubsequences: Bool = true, whereSeparator isSeparator: (Element) throws -> Bool) rethrows -> [SubSequence] {
        guard !isEmpty else { return [] }
        let positions = try indices.filter { try isSeparator(self[$0]) }
        var subSequences: [SubSequence] = []
        var start = startIndex
        for position in positions {
            if start < position {
                let subsequence = self[start..<position]
                if !subsequence.isEmpty || !omittingEmptySubsequences {
                    if subSequences.count   1 < maxSplits {
                        subSequences  = [subsequence, self[position...position]]
                    } else if subSequences.count < maxSplits {
                        subSequences.append(subsequence)
                    }
                    if subSequences.count == maxSplits {
                        return subSequences
                    }
                }
            } else {
                subSequences.append(self[position...position])
            }
            start = index(after: position)
        }
        let tail = self[start...]
        if subSequences.count < maxSplits, !omittingEmptySubsequences || !tail.isEmpty {
            subSequences.append(tail)
        }
        return subSequences
    }
}

let string = "3*(22 13)/20"
let subsequences = string.splitAndKeep(whereSeparator: Set<Character>("{ -/*()%}.").contains)  // ["3", "*", "(", "22", " ", "13", ")", "/", "20"]
  • Related