Swift split string by whitespace expect for text inside parentheses - swift

If i have a string something like: "Hello world (this is Sam)" i need to get the following array: ["Hello world", "this is Sam"] and the following ["Hello","World","this is Sam"] What would be the best way to achieve this in Swift?

Not sure if you still need this but you can try this.
let originalString = "Hello world (this is Sam) Hello world (this is Sam) (this is Sam) Hello world Hello world (this is Sam)"
let newArr = originalString.components(separatedBy: ["(", ")"])
var finalArr = [String]()
for (index, value) in newArr.enumerated() {
if (index + 1) % 2 == 1 {
finalArr.append(contentsOf: value.components(separatedBy: " ").filter { $0 != "" })
}
else {
finalArr.append(value)
}
}
print(finalArr) //["Hello", "world", "this is Sam", "Hello", "world", "this is Sam", "this is Sam", "Hello", "world", "Hello", "world", "this is Sam"]

Here is the way you can achieve that:
let originalString = "Hello world (this is Sam)"
let newArr = originalString.components(separatedBy: ["(", ")"]).filter { $0 != "" }
print(newArr) //["Hello world ", "this is Sam"]
I have mixed this and this post to achieve it.

Concerning the first case, you could just split the string using parentheses as separators. Then, omittingEmptySubsequences will remove any empty string that goes into the split. Finally, trim any whitespaces or new lines that were left on any of the splitted elements.
let splittedText = text.split(omittingEmptySubsequences: true) { separator in
return separator == "(" || separator == ")"
}.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }

Related

Split String into Array keeping delimiter/separator in Swift

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.
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", " "]
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)
}
}
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"]
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"])

Swift split string to array with exclusion

I write Swift application that parse log file.
log file string:
substr1 substr2 "substr 3" substr4
I need to get array: [substr1, substr2, substr 3, substr4]
But if I use something like:
print(stringLine.components(separatedBy: " "))
I got: [substr1, substr2, "substr, 3", substr4].
How to receive array: [substr1, substr2, substr 3, substr4]?
One of the possible solutions is to use map:
let testSting = "substr1 substr2 \"substr3\" substr4"
let mappedString = testString.components(separatedBy: " ").map({$0.replacingOccurrences(of: "\"", with: "")})
print(mappedString) //["substr1", "substr2", "substr3", "substr4"]
This case of the issue is required to use regular expression but this example is provided. So to solve problem in you're case it is possible to go in this way:
var testStingArray = testSting.replacingOccurrences(of: "\"", with: "").components(separatedBy: " ")
var arr = [String]()
var step = 0
while step < testStingArray.count {
var current = testStingArray[step]
var next = step + 1
if next < testStingArray.count {
if testStingArray[next].characters.count == 1 {
current += " " + testStingArray[next]
testStingArray.remove(at: next)
}
}
arr.append(current)
step += 1
}
print(arr)//["substr1", "substr2", "substr 3", "substr4"]
You'd better work with regular expression:
let pattern = "([^\\s\"]+|\"[^\"]+\")"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
let line = "substr1 substr2 \"substr 3\" substr4"
let arr = regex.matches(in: line, options: [], range: NSRange(0..<line.utf16.count))
.map{(line as NSString).substring(with: $0.rangeAt(1)).trimmingCharacters(in: CharacterSet(charactersIn: "\""))}
print(arr) //->["substr1", "substr2", "substr 3", "substr4"]
Alternatively you could split the string based on a CharacterSet and then filter out the empty occurrences:
let stringLine = "substr1 substr2 \"substr3\" substr4"
let array = stringLine.components(separatedBy: CharacterSet(charactersIn: "\" ")).filter { !$0.isEmpty }
print (array)
Output: ["substr1", "substr2", "substr3", "substr4"]
But this will not work correctly if there is a " somewhere in one of the 'substrings', then that specific substring will also be split.
Or, simply iterate over the characters and maintain state about the quoted parts:
//: Playground - noun: a place where people can play
import UIKit
extension String {
func parse() -> [String] {
let delimiter = Character(" ")
let quote = Character("\"")
var tokens = [String]()
var pending = ""
var isQuoted = false
for character in self.characters {
if character == quote {
isQuoted = !isQuoted
}
else if character == delimiter && !isQuoted {
tokens.append(pending)
pending = ""
}
else {
pending.append(character)
}
}
// Add final token
if !pending.isEmpty {
tokens.append(pending)
}
return tokens
}
}
print ("substr1 substr2 \"substr 3\" substr4".parse()) // ["substr1", "substr2", "substr 3", "substr4"]
print ("\"substr 1\" substr2 \"substr 3\" substr4".parse()) // ["substr 1", "substr2", "substr 3", "substr4"]
print ("a b c d".parse()) // ["a", "b", "c", "d"]
Note: this code doesn't take into account that double quotes "" might be used to escape a single quote. But I don't know if that's a possibility in your case.
https://tburette.github.io/blog/2014/05/25/so-you-want-to-write-your-own-CSV-code/

Search multiple words in one string in swift

I have a long string in swift3 and want to check if it contains word1 and word2. It could also be more than 2 search words. I found out following solution:
var Text = "Hello Swift-world"
var TextArray = ["Hello", "world"]
var count = 0
for n in 0..<TextArray.count {
if (Text.contains(TextArray[n])) {
count += 1
}
}
if (count == TextArray.count) {
print ("success")
}
But this seems very complicated, is there not an easier way to solve this? (Xcode8 and swift3)
If you are looking for less code:
let text = "Hello Swift-world"
let wordList = ["Hello", "world"]
let success = !wordList.contains(where: { !text.contains($0) })
print(success)
It is also a little more efficient than your solution because
the contains method returns as soon as a "not contained" word
is found.
As of Swift 4 or later, you can use allSatisfy:
let success = wordList.allSatisfy(text.contains)
A more Swifty solution that will stop searching after it found a non-existent word:
var text = "Hello Swift-world"
var textArray = ["Hello", "world"]
let match = textArray.reduce(true) { !$0 ? false : (text.range(of: $1) != nil ) }
Another way to do it which stops after it found a non-match:
let match = textArray.first(where: { !text.contains($0) }) == nil
Another possibility is regular expressions:
// *'s are wildcards
let regexp = "(?=.*Hello*)(?=.*world*)"
if let range = Text.range(of:regexp, options: .regularExpression) {
print("this string contains Hello world")
} else {
print("this string doesn't have the words we want")
}

swift: how can I delete a specific character?

a string such as ! !! yuahl! ! , I want delete ! and , when I code like this
for index in InputName.characters.indices {
if String(InputName[index]) == "" || InputName.substringToIndex(index) == "!" {
InputName.removeAtIndex(index)
}
}
have this error " fatal error: subscript: subRange extends past String end ", how should I do? THX :D
Swift 5+
let myString = "aaaaaaaabbbb"
let replaced = myString.replacingOccurrences(of: "bbbb", with: "") // "aaaaaaaa"
If you need to remove characters only on both ends, you can use stringByTrimmingCharactersInSet(_:)
let delCharSet = NSCharacterSet(charactersInString: "! ")
let s1 = "! aString! !"
let s1Del = s1.stringByTrimmingCharactersInSet(delCharSet)
print(s1Del) //->aString
let s2 = "! anotherString !! aString! !"
let s2Del = s2.stringByTrimmingCharactersInSet(delCharSet)
print(s2Del) //->anotherString !! aString
If you need to remove characters also in the middle, "reconstruct from the filtered output" would be a little bit more efficient than repeating single character removal.
var tempUSView = String.UnicodeScalarView()
tempUSView.appendContentsOf(s2.unicodeScalars.lazy.filter{!delCharSet.longCharacterIsMember($0.value)})
let s2DelAll = String(tempUSView)
print(s2DelAll) //->anotherStringaString
If you don't mind generating many intermediate Strings and Arrays, this single liner can generate the expected output:
let s2DelAll2 = s2.componentsSeparatedByCharactersInSet(delCharSet).joinWithSeparator("")
print(s2DelAll2) //->anotherStringaString
I find that the filter method is a good way to go for this sort of thing:
let unfiltered = "! !! yuahl! !"
// Array of Characters to remove
let removal: [Character] = ["!"," "]
// turn the string into an Array
let unfilteredCharacters = unfiltered.characters
// return an Array without the removal Characters
let filteredCharacters = unfilteredCharacters.filter { !removal.contains($0) }
// build a String with the filtered Array
let filtered = String(filteredCharacters)
print(filtered) // => "yeah"
// combined to a single line
print(String(unfiltered.characters.filter { !removal.contains($0) })) // => "yuahl"
Swift 3
In Swift 3, the syntax is a bit nicer. As a result of the Great Swiftification of the old APIs, the factory method is now called trimmingCharacters(in:). Also, you can construct the CharacterSet as a Set of single-character Strings:
let string = "! !! yuahl! !"
string.trimmingCharacters(in: [" ", "!"]) // "yuahl"
If you have characters in the middle of the string you would like to remove as well, you can use components(separatedBy:).joined():
let string = "! !! yu !ahl! !"
string.components(separatedBy: ["!", " "]).joined() // "yuahl"
H/T #OOPer for the Swift 2 version
func trimLast(character chars: Set<Character>) -> String {
let str: String = String(self.reversed())
guard let index = str.index(where: {!chars.contains($0)}) else {
return self
}
return String((str[index..<str.endIndex]).reversed())
}
Note:
By adding this function in String extension, you can delete the specific character of string at last.
for index in InputName.characters.indices.reversed() {
if String(InputName[index]) == "" || InputName.substringToIndex(index) == "!" {
InputName.removeAtIndex(index)
}
}
Also you can add such very helpful extension :
import Foundation
extension String{
func exclude(find:String) -> String {
return stringByReplacingOccurrencesOfString(find, withString: "", options: .CaseInsensitiveSearch, range: nil)
}
func replaceAll(find:String, with:String) -> String {
return stringByReplacingOccurrencesOfString(find, withString: with, options: .CaseInsensitiveSearch, range: nil)
}
}
you can use this:
for example if you want to remove "%" the percent from 10%
if let i = text.firstIndex(of: "%") {
text.remove(at: i) //10
}

Swift: Separate String into String Array and Append Separator

How would I split a string to include the separators?
Lets say I had a string such as...
let myString = "apple banana orange grapes"
If I used
let separatedString = myString.componentsSeparatedByString(" ")
my resulting array would be
["apple","banana","orange","grapes"]
How would I achieve a result of
["apple ","banana ","orange ","grapes"]
array.map lets you process the resulting array an add the space back in.
let separatedString = myString
.componentsSeparatedByString(" ")
.map { "\($0) " }
That last line iterates over all strings in the split up array and puts them in $0, and returns a new string with the space added back in which gets used as the replacement for the original string.
Alternative using regular expression:
let myString = "apple banana orange grapes"
let pattern = "\\w+\\s?"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matchesInString(myString, options:[], range: NSMakeRange(0, myString.characters.count))
.map { (myString as NSString).substringWithRange($0.range)}
print(matches) // -> ["apple ", "banana ", "orange ", "grapes"]
Solution
Since you updated your question, it looks now you no longer want a new space on the last word.
So here's my updated code
let text = "apple banana orange grapes"
let chunks: [String] = text
.componentsSeparatedByString(" ")
.reverse()
.enumerate()
.map { $0.element + ( $0.index == 0 ? "" : " ") }
.reverse()
print(chunks) // ["apple ", "banana ", "orange ", "grapes"]
Multiple separators
Thank to #vadian for the suggestion
let text = "apple banana\norange grapes"
let chunks: [String] = text
.componentsSeparatedByCharactersInSet(.whitespaceAndNewlineCharacterSet())
.reverse()
.enumerate()
.map { $0.element + ( $0.index == 0 ? "" : " ") }
.reverse()
print(chunks) // ["apple ", "banana ", "orange ", "grapes"]