21

I am trying to split (or explode) a string in Swift (1.2) using multiple delimiters, or seperators as Apple calls them.

My string looks like this:

KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value 

I have formatted it for easy reading:

KEY1=subKey1=value&subkey2=value KEY2=subkey1=value&subkey2=value KEY3=subKey1=value&subkey3=value 

The uppercase "KEY" are predefined names.
I was trying to do this using:

var splittedString = string.componentsSeparatedByString("KEY1") 

But as you can see, I can only do this with one KEY as the separator, so I am looking for something like this:

var splittedString = string.componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"]) 

So the result would be:

[ "KEY1" => "subKey1=value&subkey2=value", "KEY2" => "subkey1=value&subkey2=value", "KEY3" => "subkey1=value&subkey2=value" ] 

Is there anything built into Swift 1.2 that I can use? Or is there some kind of extension/library that can do this easily?

Thanks for your time, and have a great day!

2
  • Can value, KEYN and subKeyN have & or = in their parameters? Or Can also KEYN be in subKeyN (as a subString?) Commented Sep 8, 2015 at 19:52
  • Do you have control over the way this string is generated ? How do you know when value ends and the key begins ? Could you add another separator ? Commented Sep 8, 2015 at 20:11

8 Answers 8

29

One can also use the following approach to split a string with multiple delimiters in case keys are single characters:

//swift 4+ let stringData = "K01L02M03" let res = stringData.components(separatedBy: CharacterSet(charactersIn: "KLM")) //older swift syntax let res = stringData.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "KLM")); 

res will contain ["01", "02", "03"]

If anyone knows any kind of special syntax to extend the approach to multiple characters per key you are welcome to suggest and to improve this answer

Sign up to request clarification or add additional context in comments.

1 Comment

Some extra things to be aware of here: stackoverflow.com/a/61119561/2079103
17

Swift 4.2 update to @vir us's answer:

let string = "dots.and-hyphens" let array = string.components(separatedBy: CharacterSet(charactersIn: ".-")) 

Comments

8

Swift provides a new function split here:

let line = "BLANCHE: I don't want realism. I want magic!" print(line.split(whereSeparator: { $0 == " " || $0 == "."})) 

2 Comments

This looks like a great updated approach. Documentation says it's been available since Swift 8.0+
@clearlight You mean iOS 8.0+ (ain't no Swift 8.0 yet)
5

This isn't very efficient, but it should do the job:

import Foundation extension String { func componentsSeperatedByStrings(ss: [String]) -> [String] { let inds = ss.flatMap { s in self.rangeOfString(s).map { r in [r.startIndex, r.endIndex] } ?? [] } let ended = [startIndex] + inds + [endIndex] let chunks = stride(from: 0, to: ended.count, by: 2) let bounds = map(chunks) { i in (ended[i], ended[i+1]) } return bounds .map { (s, e) in self[s..<e] } .filter { sl in !sl.isEmpty } } } "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value".componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"]) // ["=subKey1=value&subkey2=value", "=subkey1=value&subkey2=value", "=subKey1=value&subkey3=value"] 

Or, if you wanted it in dictionary form:

import Foundation extension String { func componentsSeperatedByStrings(ss: [String]) -> [String:String] { let maybeRanges = ss.map { s in self.rangeOfString(s) } let inds = maybeRanges.flatMap { $0.map { r in [r.startIndex, r.endIndex] } ?? [] } let ended = [startIndex] + inds + [endIndex] let chunks = stride(from: 0, to: ended.count, by: 2) let bounds = map(chunks) { i in (ended[i], ended[i+1]) } let values = bounds .map { (s, e) in self[s..<e] } .filter { sl in !sl.isEmpty } let keys = filter(zip(maybeRanges, ss)) { (r, _) in r != nil } var result: [String:String] = [:] for ((_, k), v) in zip(keys, values) { result[k] = v } return result } } "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value".componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"]) // ["KEY3": "=subKey1=value&subkey3=value", "KEY2": "=subkey1=value&subkey2=value", "KEY1": "=subKey1=value&subkey2=value"] 

For Swift 2:

import Foundation extension String { func componentsSeperatedByStrings(ss: [String]) -> [String] { let unshifted = ss .flatMap { s in rangeOfString(s) } .flatMap { r in [r.startIndex, r.endIndex] } let inds = [startIndex] + unshifted + [endIndex] return inds.startIndex .stride(to: inds.endIndex, by: 2) .map { i in (inds[i], inds[i+1]) } .flatMap { (s, e) in s == e ? nil : self[s..<e] } } } 

2 Comments

Thanks man! worked great, until I updated to the new xcode 7GM and swift 2.0 syntax, do you have any idea how I could change the code so it would work? The "stride()" function is giving me an error: let chunks = stride(from: 0, to: ended.count, by: 2) "Stride(from:to:by) is unavailable" Thanks in advance
Thanks a lot! Works like a charm :)
3

Swift 5:

extension String { func components<T>(separatedBy separators: [T]) -> [String] where T : StringProtocol { var result = [self] for separator in separators { result = result .map { $0.components(separatedBy: separator)} .flatMap { $0 } } return result } } 

It's for the sake of nice and neat code, don't use it if you need something efficiently

Comments

0

Swift 2 for forward compatibility

Using a regular expression:

let string = "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value" let nsString :NSString = string let stringRange = NSMakeRange(0, string.utf16.count) let pattern = "(KEY\\d)=([^=]+=[^&]+[^=]+?=[^K]+)" var results = [String:String]() do { var regEx = try NSRegularExpression(pattern:pattern, options:[]) regEx.enumerateMatchesInString(string, options: [], range: stringRange) { (result : NSTextCheckingResult?, _, _) in if let result = result { if result.numberOfRanges == 3 { let key = nsString.substringWithRange(result.rangeAtIndex(1)) let value = nsString.substringWithRange(result.rangeAtIndex(2)) results[key] = value } } } } catch { print("Bad Pattern") } 

results: ["KEY3": "subKey1=value&subkey3=value", "KEY2": "subkey1=value&subkey2=value", "KEY1": "subKey1=value&subkey2=value"]

Comments

0

You could do it with regular expressions. The below snippet is a bit clumsy and not really fail-safe but it should give you an idea.

let string = "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value" let re = NSRegularExpression(pattern: "(KEY1|KEY2|KEY3)=", options: nil, error: nil)! let matches = re.matchesInString(string, options: nil, range: NSMakeRange(0, count(string))) var dict = [String: String]() for (index, match) in enumerate(matches) { let key = (string as NSString).substringWithRange( NSMakeRange(match.range.location, match.range.length - 1)) let valueStart = match.range.location + match.range.length let valueEnd = index < matches.count - 1 ? matches[index + 1].range.location : count(string) let value = (string as NSString).substringWithRange( NSMakeRange(valueStart, valueEnd - valueStart)) dict[key] = value } 

The final value of dict is

[KEY3: subKey1=value&subkey3=value, KEY2: subkey1=value&subkey2=value, KEY1: subKey1=value&subkey2=value] 

3 Comments

Thanks for the answer, I see how it works. But the actual string im using does not have keys that are formatted like "KEYX" where X is the Int. I have keys that are completely different from each other, more like "NS", "AAAA" and "MX". How could I implement an array with these keys in your code? Thank you very much for your time!
@Rick Sorry for the delay. For the record, I updated my answer to support arbitrary key names. The idea is to use an either-or-like expression (KEY1|KEY2|KEY3).
Thanks man, I am currently using oisdk's method of the string extension, which works great, but I will give your method a try and see what works best.
0

This works:

extension StringProtocol { func split(by breaks: [String]) -> [any StringProtocol] { for split in breaks { guard self.contains(split) else { continue } return self.components(separatedBy: split).flatMap({ $0.split(by: breaks) }).filter({!$0.isEmpty}) } return [self] } } 

Then you can use it like this

let str = "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value" str.split(by: ["KEY1=", "KEY2=", "KEY3="]) // Output: ["subKey1=value&subkey2=value", "subkey1=value&subkey2=value", "subKey1=value&subkey3=value"] 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.