Efficient algorithm to split a string based on multiple string delimiters

Viewed 124

I would like to know if there is an efficient way of splitting a string into multiple strings based on delimiters that are also strings. Eg. updateStr = "23+45 = 56 0" , delimiters = ["+"," ","="]

Result = [23,45,56,0]

I tried the following code in swift:

for i in 0..<delimiter.count {
    let res = updateStr.components(separatedBy: delimiter[i])
    updateStr = res.joined(separator: "unique%")
 }
splitTxt = updateStr.components(separatedBy: "unique%")

This works, but as the delimiters will be received dynamically I want a better approach. Are there any efficient ways to avoid multiple loops to solve this? An algorithm with more efficient solution that doesn't involve swift instance methods would also be appreciated. Thanks for the answers but

To be clearer, I don't just want characters but strings as delimiters:

Eg2. updateStr = "I like playing with friends" , delimiters = ["li"," "la","ie"]

Result = ["I ","ke p","ying with fr","nds"]

3 Answers

The efficient way to do this sort of thing is with a Set:

let equation = "23+45 = 56 0"
let delimiters : [Character] = ["+"," ","="]
let setOfSeparators = Set(delimiters)
let result = equation.split {setOfSeparators.contains($0)}
print(result)

That's efficient because contains on a Set is extremely fast, so that cost is negligible and we are looping implicitly through the original string just once.

On the other hand, you could take advantage of the Cocoa CharacterSet class. For that, I would say:

let equation = "23+45 = 56 0"
let delimiters = ["+"," ","="]
let characterSet = CharacterSet(charactersIn: delimiters.joined())
let result = equation.components(separatedBy: characterSet).filter {!$0.isEmpty}
print(result)

Another fun way is to use a Scanner (these are underutilized in my opinion):

let equation = "23+45 = 56 0"
let delimiters = ["+"," ","="]
let characterSet = CharacterSet(charactersIn: delimiters.joined())
let scanner = Scanner(string: equation)
var result = [String]()
while let word = scanner.scanUpToCharacters(from: characterSet) {
    result.append(word)
    scanner.scanCharacters(from: characterSet)
}
print(result)

One of the components(separatedBy:) overloads will handle this automatically using a CharacterSet:

let delimiters = ["+"," ","="].compactMap(UnicodeScalar.init)
let splitTxt = updateStr.components(separatedBy: CharacterSet(delimiters))

NSRegularExpression provides the facility to split on general regular expressions, so this would enable splitting on a finite set of string delimiters using a delim1|delim2|delim3 regex. The following split operation does this job:

static func stringSubrange(str : String, st : Int, en : Int) -> String
{ var result : [Character] = [Character]()
  var count : Int = 0

  for index in str.indices
  { let c : Character = str[index]
    count = count + 1
    if count >= st && count <= en
    { result.append(c) }
    else if count > en
    { return String(result) }
  }
  return String(result)
}


static func split(str: String, pattern: String) -> [String]
{ let rge = NSRange(location: 0, length: str.utf16.count)
  let regexp = try! NSRegularExpression(pattern: pattern)
  let pred = regexp.matches(in: str, options: [], range: rge)
  var result : [String] = [String]()
  var prev : Int = 1; 

  for p in pred
  { let range = p.range
    let splitString = Ocl.stringSubrange(str: str, st: prev, en: range.location)
    prev = range.location + range.length + 1
    if splitString.count > 0
    { result.append(splitString) }
  }

  if prev < str.count
  { result.append(Ocl.stringSubrange(str: str, st: prev, en: str.count)) } 
  return result
}
Related