Case-sensitive character replacement in a Swift string - swift

I need to replace characters in a Swift string case-sensitively.
I've been using the replacingOccurrences(of:with:options:range:) built-in string function to change every "a" to a "/a/", every "b" to a "/b/", and so on, like this:
stringConverted = stringConverted.replacingOccurrences(of: "a", with: "/a/", options: [])
Then I change every "/a/" to its corresponding letter, which is "a". I change every "/b/" to its corresponding letter, which is "q", and so on.
My problem is that I need this replacement to be case-sensitive. I've looked this up, but I've tried what I found and it didn't help.
Do I need to use the range argument? Or am I doing something else wrong?

As #Orkhan mentioned you can pass options: .caseInsensitive like below
let a = "a"
let start = a.index(a.startIndex, offsetBy: 0)
let end = a.index(a.startIndex, offsetBy: a.count)
let range = start..<end
let value = a.replacingOccurrences(of: "a", with: "/a", options: .caseInsensitive, range: range)
print(value)

Related

Split String or Substring with Regex pattern in Swift

First let me point out... I want to split a String or Substring with any character that is not an alphabet, a number, # or #. That means, I want to split with whitespaces(spaces & line breaks) and special characters or symbols excluding # and #
In Android Java, I am able to achieve this with:
String[] textArr = text.split("[^\\w_##]");
Now, I want to do the same in Swift. I added an extension to String and Substring classes
extension String {}
extension Substring {}
In both extensions, I added a method that returns an array of Substring
func splitWithRegex(by regexStr: String) -> [Substring] {
//let string = self (for String extension) | String(self) (for Substring extension)
let regex = try! NSRegularExpression(pattern: regexStr)
let range = NSRange(string.startIndex..., in: string)
return regex.matches(in: string, options: .anchored, range: range)
.map { match -> Substring in
let range = Range(match.range(at: 1), in: string)!
return string[range]
}
}
And when I tried to use it, (Only tested with a Substring, but I also think String will give me the same result)
let textArray = substring.splitWithRegex(by: "[^\\w_##]")
print("substring: \(substring)")
print("textArray: \(textArray)")
This is the out put:
substring: This,is a #random #text written for debugging
textArray: []
Please can Someone help me. I don't know if the problem if from my regex [^\\w_##] or from splitWithRegex method
The main reason why the code doesn't work is range(at: 1) which returns the content of the first captured group, but the pattern does not capture anything.
With just range the regex returns the ranges of the found matches, but I suppose you want the characters between.
To accomplish that you need a dynamic index starting at the first character. In the map closure return the string from the current index to the lowerBound of the found range and set the index to its upperBound. Finally you have to add manually the string from the upperBound of the last match to the end.
The Substring type is a helper type for slicing strings. It should not be used beyond a temporary scope.
extension String {
func splitWithRegex(by regexStr: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: regexStr) else { return [] }
let range = NSRange(startIndex..., in: self)
var index = startIndex
var array = regex.matches(in: self, range: range)
.map { match -> String in
let range = Range(match.range, in: self)!
let result = self[index..<range.lowerBound]
index = range.upperBound
return String(result)
}
array.append(String(self[index...]))
return array
}
}
let text = "This,is a #random #text written for debugging"
let textArray = text.splitWithRegex(by: "[^\\w_##]")
print(textArray) // ["This", "is", "a", "#random", "#text", "written", "for", "debugging"]
However in macOS 13 and iOS 16 there is a new API quite similar to the java API
let text = "This,is a #random #text written for debugging"
let textArray = Array(text.split(separator: /[^\w_##]/))
print(textArray)
The forward slashes indicate a regex literal

Converting numbers to string in a given string in Swift

I am given a string like 4eysg22yl3kk and my output should be like this:
foureysgtweny-twoylthreekk or if I am given 0123 it should be output as one hundred twenty-three. So basically, as I scan the string, I need to convert numbers to string.
I do not know how to implement this in Swift as I iterate through the string? Any idea?
You actually have two basic problems.
The first is convert a "number" to "spelt out" value (ie 1 to one). This is actually easy to solve, as NumberFormatter has a spellOut style property
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
let text = formatter.string(from: NSNumber(value: 1))
which will result in "one", neat.
The other issue though, is how to you separate the numbers from the text?
While I can find any number of solutions for "extract" numbers or characters from a mixed String, I can't find one which return both, split on their boundaries, so, based on your input, we'd end up with ["4", "eysg", "22", "yl", "3", "kk"].
So, time to role our own...
func breakApart(_ text: String, withPattern pattern: String) throws -> [String]? {
do {
let regex = try NSRegularExpression(pattern: "[0-9]+", options: .caseInsensitive)
var previousRange: Range<String.Index>? = nil
var parts: [String] = []
for match in regex.matches(in: text, options: [], range: NSRange(location: 0, length: text.count)) {
guard let range = Range(match.range, in: text) else {
return nil
}
let part = text[range]
if let previousRange = previousRange {
let textRange = Range<String.Index>(uncheckedBounds: (lower: previousRange.upperBound, upper: range.lowerBound))
parts.append(String(text[textRange]))
}
parts.append(String(part))
previousRange = range
}
if let range = previousRange, range.upperBound != text.endIndex {
let textRange = Range<String.Index>(uncheckedBounds: (lower: range.upperBound, upper: text.endIndex))
parts.append(String(text[textRange]))
}
return parts
} catch {
}
return nil
}
Okay, so this is a little "dirty" (IMHO), but I can't seem to think of a better approach, hopefully someone will be kind enough to provide some hints towards one ;)
Basically what it does is uses a regular expression to find all the groups of numbers, it then builds an array, cutting the string apart around the matching boundaries - like I said, it's crude, but it gets the job done.
From there, we just need to map the results, spelling out the numbers as we go...
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
let value = "4eysg22yl3kk"
if let parts = try breakApart(value, withPattern: pattern) {
let result = parts.map { (part) -> String in
if let number = Int(part), let text = formatter.string(from: NSNumber(value: number)) {
return text
}
return part
}.joined(separator: " ")
print(result)
}
This will end up printing four eysg twenty-two yl three kk, if you don't want the spaces, just get rid of separator in the join function
I did this in Playgrounds, so it probably needs some cleaning up
I was able to solve my question without dealing with anything extra than converting my String to an array and check char by char. If I found a digit I was saving it in a temp String and as soon as I found out the next char is not digit, I converted my digit to its text.
let inputString = Array(string.lowercased())

Swift finding and changing range in attributed string

What would be the best way to change this string:
"gain quickness for 5 seconds. <c=#reminder>(Cooldown: 90s)</c> only after"
into an Attributed String while getting rid of the part in the <> and I want to change the font of (Cooldown: 90s). I know how to change and make NSMutableAttributedStrings but I am stuck on how to locate and change just the (Cooldown: 90s) in this case. The text in between the <c=#reminder> & </c> will change so I need to use those to find what I need.
These seem to be indicators meant to be used for this purpose I just don't know ho.
First things first, you'll need a regular expression to find and replace all tagged strings.
Looking at the string, one possible regex could be <c=#([a-zA-Z-9]+)>([^<]*)</c>. Note that will will work only if the string between the tags doesn't contain the < character.
Now that we have the regex, we only need to apply it on the input string:
let str = "gain quickness for 5 seconds. <c=#reminder>(Cooldown: 90s)</c> only after"
let attrStr = NSMutableAttributedString(string: str)
let regex = try! NSRegularExpression(pattern: "<c=#([a-zA-Z-9]+)>([^<]*)</c>", options: [])
while let match = regex.matches(in: attrStr.string, options: [], range: NSRange(location: 0, length: attrStr.string.utf16.count)).first {
let indicator = str[Range(match.range(at: 1), in: str)!]
let substr = str[Range(match.range(at: 2), in: str)!]
let replacement = NSMutableAttributedString(string: String(substr))
// now based on the indicator variable you might want to apply some transformations in the `substr` attributed string
attrStr.replaceCharacters(in: match.range, with: replacement)
}

How can I substring this string?

how can I substring the next 2 characters of a string after a certian character. For example I have a strings str1 = "12:34" and other like str2 = "12:345. I want to get the next 2 characters after : the colons.
I want a same code that will work for str1 and str2.
Swift's substring is complicated:
let str = "12:345"
if let range = str.range(of: ":") {
let startIndex = str.index(range.lowerBound, offsetBy: 1)
let endIndex = str.index(startIndex, offsetBy: 2)
print(str[startIndex..<endIndex])
}
It is very easy to use str.index() method as shown in #MikeHenderson's answer, but an alternative to that, without using that method is iterating through the string's characters and creating a new string for holding the first two characters after the ":", like so:
var string1="12:458676"
var nr=0
var newString=""
for c in string1.characters{
if nr>0{
newString+=String(c)
nr-=1
}
if c==":" {nr=2}
}
print(newString) // prints 45
Hope this helps!
A possible solution is Regular Expression,
The pattern checks for a colon followed by two digits and captures the two digits:
let string = "12:34"
let pattern = ":(\\d{2})"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.characters.count)) {
print((string as NSString).substring(with: match.rangeAt(1)))
}

Find characters inside quotation marks in String

I'm trying to pull out the parts of a string that are in quotation marks, i.e. in "Rouge One" is an awesome movie I want to extract Rouge One.
This is what I have so far but can't figure out where to go from here: I create a copy of the text so that I can remove the first quotation mark so that I can get the index of the second.
if text.contains("\"") {
guard let firstQuoteMarkIndex = text.range(of: "\"") else {return}
var textCopy = text
let textWithoutFirstQuoteMark = textCopy.replacingCharacters(in: firstQuoteMarkIndex, with: "")
let secondQuoteMarkIndex = textCopy.range(of: "\"")
let stringBetweenQuotes = text.substring(with: Range(start: firstQuoteMarkIndex, end: secondQuoteMarkIndex))
}
There is no need to create copies or to replace substrings for this task.
Here is a possible approach:
Use text.range(of: "\"") to find the first quotation mark.
Use text.range(of: "\"", range:...) to find the second quotation mark, i.e. the first one after the range found in step 1.
Extract the substring between the two ranges.
Example:
let text = " \"Rouge One\" is an awesome movie"
if let r1 = text.range(of: "\""),
let r2 = text.range(of: "\"", range: r1.upperBound..<text.endIndex) {
let stringBetweenQuotes = text.substring(with: r1.upperBound..<r2.lowerBound)
print(stringBetweenQuotes) // "Rouge One"
}
Another option is a regular expression search with "positive lookbehind" and "positive lookahead" patterns:
if let range = text.range(of: "(?<=\\\").*?(?=\\\")", options: .regularExpression) {
let stringBetweenQuotes = text.substring(with: range)
print(stringBetweenQuotes)
}
var rouge = "\"Rouge One\" is an awesome movie"
var separated = rouge.components(separatedBy: "\"") // ["", "Rouge One", " is an awesome movie"]
separated.dropFirst().first
I would use .components(separatedBy:)
let stringArray = text.components(separatedBy: "\"")
Check if stringArray count is > 2 (there is at least 2 quotes).
Check if stringArray count is odd, aka count % 2 == 1.
If it is odd, all the even indices are between 2 quotes and they are what you want.
If it is even, all the even indices - 1 are between 2 quotes (the last one doesn't have an end quote).
This will allow you to also capture multiple sets of quoted strings, like:
"Rogue One" is a "Star Wars" movie.
Another option is to use regular expressions to find pairs of quotes:
let pattern = try! NSRegularExpression(pattern: "\\\"([^\"]+)\\\"")
// Small helper methods making it easier to work with enumerateMatches(in:...)
extension String {
subscript(utf16Range range: Range<Int>) -> String? {
get {
let start = utf16.index(utf16.startIndex, offsetBy: range.lowerBound)
let end = utf16.index(utf16.startIndex, offsetBy: range.upperBound)
return String(utf16[start..<end])
}
}
var fullUTF16Range: NSRange {
return NSRange(location: 0, length: utf16.count)
}
}
// Loop through *all* quoted substrings in the original string.
let str = "\"Rogue One\" is an awesome movie"
pattern.enumerateMatches(in: str, range: str.fullUTF16Range) { (result, flags, stop) in
// rangeAt(1) is the range representing the characters in the 1st
// capture group of the regular expression: ([^"]+)
if let result = result, let range = result.rangeAt(1).toRange() {
print("This was in quotes: \(str[utf16Range: range] ?? "<bad range>")")
}
}