5

Looking for an (elegant) solution for splitting a string and keeping the separator as item(s) in the array

example 1:

"hello world"

["hello", " ", "world"]

example 2:

" hello world"

[" ", "hello", " ", "world"]

thx.

2
  • 1
    What about this stackoverflow.com/q/32361412 ? Commented Feb 25, 2019 at 16:13
  • @MartinR thx for the link. I searched for solutions, and havent found this one. the solution(s) of that question are inspiring, yet don't work with separators longer than one char. example 2 seems to not work as well. Commented Feb 25, 2019 at 16:24

4 Answers 4

11

Suppose you are splitting the string by a separator called separator, you can do the following:

let result = yourString.components(separatedBy:  separator) // first split
                        .flatMap { [$0, separator] } // add the separator after each split
                        .dropLast() // remove the last separator added
                        .filter { $0 != "" } // remove empty strings

For example:

let result = " Hello World ".components(separatedBy:  " ").flatMap { [$0, " "] }.dropLast().filter { $0 != "" }
print(result) // [" ", "Hello", " ", "World", " "]
Sign up to request clarification or add additional context in comments.

2 Comments

@Sweeper I thought there was a version of components(separatedBy:) that had an optional parameter to exclude empty results. Have you seen anything like that?
I know you can do that in C# but I don't think that exists in Swift. @Alexander
0

For people who have a condition for their split, for example: splitting a camelCaseString based on uppercase condition:

extension Sequence {
    func splitIncludeDelimiter(whereSeparator shouldDelimit: (Element) throws -> Bool) rethrows -> [[Element]] {
        try self.reduce([[]]) { group, next in
            var group = group
            if try shouldDelimit(next) {
                group.append([next])
            } else {
                group[group.lastIdx].append(next)
            }
            return group
        }
    }
}

For example:

"iAmCamelCase".splitIncludeDelimiter(whereSeparator: \.isUppercase)
=>
["i", "Am", "Camel", "Case"]

(If you want the imp of isUppercase)

extension CharacterSet {
    static let uppercaseLetters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
}

extension Unicode.Scalar {
    var isUppercase: Bool {
        CharacterSet.uppercaseLetters.contains(self)
    }
}

1 Comment

Nice idea, but this doesn't compile. If you move the extension on RandomAccessCollection you do have an endIndex which can be used instead of lastIdx
0

Just for fun, the Swift Algorithms package contains an algorithm called Intersperse

After adding the package and

import Algorithms

you can write

let string = "hello world"
let separator = " "

let result = Array(string
                     .components(separatedBy: separator)
                     .interspersed(with: separator))
print(result)

Your second example is barely correct, the result of splitting " hello world" by space is

["", "hello", "world"]

Comments

0
let sample = "a\nb\n\nc\n\n\nd\n\nddddd\n \n  \n  \n\n"

let sep = "\n"

let result = sample.components(separatedBy: sep).flatMap {
    $0 == "" ? [sep] : [$0, sep]
}.dropLast()


debugPrint(result) // ArraySlice(["a", "\n", "b", "\n", "\n", "c", "\n", "\n", "\n", "d", "\n", "\n", "ddddd", "\n", " ", "\n", "  ", "\n", "  ", "\n", "\n"])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.