Convert String to Postal code Format in swift - swift

In App i have string like
1A11A1
I want to convert it to
1A1 1A1
There should be space after 3characters.
What i tried is : code = 1A11A1
let end = code.index(code.startIndex, offsetBy: code.count)
let range = code.startIndex..<end
if code.count < 3 {
code = code.replacingOccurrences(of: "(\\d+)", with: "$1", options: .regularExpression, range: range)
}
else {
code = code.replacingOccurrences(of: "(\\d{3})(\\d+)", with: "$1 $2", options: .regularExpression, range: range)
}

If your rule is that you want a "space after 3 characters," take three characters, add a space and then the rest:
let result = "\(code.prefix(3)) \(code.dropFirst(3))"
// "1A1 1A1"

Rob's solution is fine, just for the sake of it, there's also an option to use insert(" ", at: index), something like this:
extension String {
var postalCode: String {
var result = self
// Check that this string is the right length
guard result.count == 6 else {
return result
}
let index = result.index(result.startIndex, offsetBy: 3)
result.insert(" ", at: index)
return result
}
}
Test:
let str: String = "1A11A1"
print(str.postalCode) // prints 1A1 1A1
let str2: String = "1A1 1A1"
print(str2.postalCode) // prints 1A1 1A1 (doesn't change format)
let str3: String = "12345"
print(str3.postalCode) // prints 12345 (doesn't change format)

Related

Replacing two ranges in a String simultaneously

Say you have a string that looks likes this:
let myStr = "Hello, this is a test String"
And you have two Ranges,
let rangeOne = myStr.range(of: "Hello") //lowerBound: 0, upperBound: 4
let rangeTwo = myStr.range(of: "this") //lowerBound: 7, upperBound: 10
Now you wish to replace those ranges of myStr with new characters, that may not be the same length as their original, you end up with this:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
myStr.replaceSubrange(rangeOne, with: "Bonjour") //Bonjour, this is a test String
myStr.replaceSubrange(rangeTwo, with: "ce") //Bonjourceis is a test String
Because rangeTwo is based on the pre-altered String, it fails to properly replace it.
I could store the length of the replacement and use it to reconstruct a new range, but there is no guarantee that rangeOne will be the first to be replaced, nor that rangeOne will actually be first in the string.
The solution is the same as removing multiple items from an array by index in a loop.
Do it backwards
First replace rangeTwo then rangeOne
myStr.replaceSubrange(rangeTwo, with: "ce")
myStr.replaceSubrange(rangeOne, with: "Bonjour")
An alternative could be also replacingOccurrences(of:with:)
This problem can be solved by shifting the second range based on the length of first the replaced string.
Using your code, here is how you would do it:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
let shift = "Bonjour".count - "Hello".count
let shiftedTwo = myStr.index(rangeTwo.lowerBound, offsetBy: shift)..<myStr.index(rangeTwo.upperBound, offsetBy: shift)
myStr.replaceSubrange(rangeOne, with: "Bonjour") // Bonjour, this is a test String
myStr.replaceSubrange(shiftedTwo, with: "ce") // Bonjour, ce is a test String
You can sort the range in descending order, then replace backwards, from the end to the start. So that any subsequent replacement will not be affect by the previous replacements. Also, it is safer to use replacingCharacters instead of replaceSubrange in case when dealing with multi-codepoints characters.
let myStr = "Hello, this is a test String"
var ranges = [myStr.range(of: "Hello")!,myStr.range(of: "this")!]
ranges.shuffle()
ranges.sort(by: {$1.lowerBound < $0.lowerBound}) //Sort in reverse order
let newWords : [String] = ["BonjouršŸ˜€","ce"].reversed()
var newStr = myStr
for i in 0..<ranges.count
{
let range = ranges[i]
//check overlap
if(ranges.contains(where: {$0.overlaps(range)}))
{
//Some range over lap
throw ...
}
let newWord = newWords[i]
newStr = newStr.replacingCharacters(in: range, with: newWord)
}
print(newStr)
My solution ended up being to take the ranges and replacement strings, work backwards and replace
extension String {
func replacingRanges(_ ranges: [NSRange], with insertions: [String]) -> String {
var copy = self
copy.replaceRanges(ranges, with: insertions)
return copy
}
mutating func replaceRanges(_ ranges: [NSRange], with insertions: [String]) {
var pairs = Array(zip(ranges, insertions))
pairs.sort(by: { $0.0.upperBound > $1.0.upperBound })
for (range, replacementText) in pairs {
guard let textRange = Range(range, in: self) else { continue }
replaceSubrange(textRange, with: replacementText)
}
}
}
Which works out to be useable like this
var myStr = "Hello, this is a test."
let rangeOne = NSRange(location: 0, length: 5) // ā€œHelloā€
let rangeTwo = NSRange(location: 7, length: 4) // ā€œthisā€
myStr.replaceRanges([rangeOne, rangeTwo], with: ["Bonjour", "ce"])
print(myStr) // Bonjour, ce is a test.

Replace in string with regex

I am struggling to modify captured value with regex.
For example, I wanna change "Hello, he is hero" to "HEllo, HE is HEro" using Regex.
I know there are ways to change this without regex, but it is just an example to show the problem. I actually use the regex instead of just he, but I cannot provide it here. That is why using regex is required.
The code below somehow does not work. Are there any ways to make it work?
"Hello, he is hero".replacingOccurrences(
of: #"(he)"#,
with: "$1".uppercased(), // <- uppercased is not applied
options: .regularExpression
)
You need to use your regex in combination with Range (range(of:)) to find matches and then replace each found range separately
Here is a function as an extension to String that does this by using range(of:) starting from the start of the string and then moving the start index to match from forward to after the last match. The actual replacement is done inside a separate function that is passed as an argument
extension String {
func replace(regex: String, with replace: (Substring) -> String) -> String {
var string = self
var startIndex = self.startIndex
let endIndex = self.endIndex
while let range = string.range(of: regex, options: [.regularExpression] , range: startIndex..<endIndex) {
if range.isEmpty {
startIndex = string.index(startIndex, offsetBy: 1)
if startIndex >= endIndex { break }
continue
}
string.replaceSubrange(range, with: replace(string[range]))
startIndex = range.upperBound
}
return string
}
}
Example where we do an case insensitive search for words starting with "he" and replace each match with the uppercased version
let result = "Hello, he is hero. There he is".replace(regex: #"(?i)\bhe"#) {
$0.uppercased()
}
Output
HEllo, HE is HEro. There HE is
You can try NSRegularExpression. Something like:
import Foundation
var sourceStr = "Hello, he is hero"
let regex = try! NSRegularExpression(pattern: "(he)")
let matches = regex.matches(in: sourceStr, range: NSRange(sourceStr.startIndex..., in: sourceStr))
regex.enumerateMatches(in: sourceStr, range: NSRange(sourceStr.startIndex..., in: sourceStr)) { (match, _, _) in
guard let match = match else { return }
guard let range = Range(match.range, in: sourceStr) else { return }
let sub = sourceStr[range]
sourceStr = sourceStr.replacingOccurrences(of: sub, with: sub.uppercased(), options: [], range: range)
}
print(sourceStr)
this is the solution i can provide
var string = "Hello, he is hero"
let occurrence = "he"
string = string.lowercased().replacingOccurrences(
of: occurrence,
with: occurrence.uppercased(),
options: .regularExpression
)
print(string)

Analyzing speech and doing an action

I am trying to analyze a sentence and make an action depending on that.
Here are examples of sentences received from speech recognition. I wrote couple of sentences, because we actually don't know what user is going to say for sure, and if he at all says the right pattern.
var str = "20 minutes to take a shower"
var sentence = "seven minutes to make 10 last homeworks"
var sentence2 = "strum guitar for 15 minutes"
var plans = "launch with friend 12:15, then drawing lesson"
I want to extract "20 minutes" ; assign it for the timeValue and launch the timer.
Also, to assign a task to taskValue to represent the task that I am doing. (I thought getting task value by removing "20 minutes" from the initial sentence).
What do you think is the best way to work with the String that I need to analyze?
I thought of finding index ranges and then cutting/copying with the help of indexes, but
The format of the indexes it returns is like this: and I don't know how to extract the number of index. (In this case: 0 through 10)
<NSSimpleRegularExpressionCheckingResult: 0x6000027f0140>{0, 10}{<NSRegularExpression: 0x600003cfcb40> [0-9]{1,} minutes 0x1}
How to ignite the timer? We have to validate that there's the proper command given. And, when we get the timeValue and taskValue back, then how do we ignite the timer? (The whole process was: User pushes button -> user speaking -> speech recognized(and displayed on the screen label) -> sentence analyzed(?) -> timer starts(?) and task displayed in the label(?) )
What is your recommendation for architecture of speech analysis system. Maybe you know some articles on this topic?
Here's the logic for the speech detection.
var timeValue: Int = Int()
var taskValue: String = ""
func stringDeduction(of inputText: String) -> (Int?, String?) {
let pattern = "[0-9]{1,} minutes"
let regexOptions: NSRegularExpression.Options = [.caseInsensitive]
let matchingOptions: NSRegularExpression.MatchingOptions = [.reportCompletion]
// TODO - catch errors with regex
let regex = try! NSRegularExpression(pattern: pattern, options: regexOptions)
// } catch {
// print("error in regex")
// }
let range = NSRange(location: 0, length: inputText.utf8.count)
// \d - matches any digit
// Pattern for time format like this 00:00
//let patternForTime = "[0-9]{1,}:[0-9]{1,2}"
if let matchIndex = regex.firstMatch(in: inputText, options: matchingOptions, range: range) {
print(matchIndex)
} else {
print("No match.")
}
// check whether the string matches and print one of two messages
if let match = regex.firstMatch(in: inputText, range: NSRange(location: 0, length: inputText.utf8.count)) {
print("*: Match!")
} else {
print("*: No match.")
}
/* Question - how to use "mathces" properly?!
if let match = regex.matches(in: testString, options: .reportCompletion ,range: NSRange(location: 0, length: testString.utf8.count)) {
print("*: Match!")
print(match)
} else {
print("*: No match.")
}
*/
func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text))
return results.map {
String(text[Range($0.range, in: text)!])
}
} catch {
print("invalid regex")
return []
}
}
var task = regex.stringByReplacingMatches(in: inputText, options: .withoutAnchoringBounds, range: range, withTemplate: "")
//var taskMutableString = NSMutableString(string: str)
//regex.replaceMatches(in: taskMutableString, options: .withoutAnchoringBounds, range: range, withTemplate: "")
//taskMutableString
var timeStringArray = matches(for: pattern, in: inputText)
var timeString = timeStringArray[0]
let time = Int(timeString.replacingOccurrences(of: " minutes", with: ""))
timeValue = time ?? Int()
taskValue = task.replacingOccurrences(of: "to" , with: "", options: .caseInsensitive, range: task.startIndex..<task.index(task.startIndex, offsetBy: 4))
let taskReturn = taskValue
return (time, taskReturn)
// the regex ^[ \t]+|[ \t]+$ matches excess whitespace at the beginning or end of a line.
// what regex, or string method matches exess whitespace at the beginning of a line
}
Here's the extension to work with string like with array. Like this, - str[0..2]
extension String {
subscript (bounds: CountableClosedRange<Int>) -> String {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return String(self[start...end])
}
subscript (bounds: CountableRange<Int>) -> String {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return String(self[start..<end])
}
}
Here's another approach I have tried: (Although it doesn't seem good)
let timeArray = ["one minute", "two minutes", "three minutes", "four minutes", "five minutes", "six minutes", "seven minutes", "eight minutes", "nine minutes"]
let timeValueCheck = "Answer: \(sentence.containsAny(of: timeArray) ?? "doesn't contain")"
//Dividing String into words
let abc: [String] = str.components(separatedBy: " ")
// Finding a number within an array of words
// There's a problem if there are couple of numbers in the sentence, it returns all of them and not only the needed time.
let numbers = abc.compactMap {
// convert each substring into an Int
return Int($0)
}
for i in 1...100 {
if str.contains(String(i) + " minutes") {
print(i)
}
}
Thank you for any of your help! I've just gone insane with this task for 3 months. Also, if something is unclear or a bit messy, please tell me! I'll try to correct.

How to use Swift NSRegularExpression to get uppercased letter?

I have a string like this:
"te_st" and like to replace all underscores followed by a character with the uppercased version of this character.
From "te_st" --> Found (regex: "_.") --------replace with next char (+ uppercase ("s"->"S")--------> "teSt"
From "te_st" ---> to "teSt"
From "_he_l_lo" ---> to "HeLLo"
From "an_o_t_h_er_strin_g" ---> to "anOTHErStrinG"
... but I can not really get it working using Swift's NSRegularExpression like this small snipped does:
var result = "te_st" // result should be teSt
result = try! NSRegularExpression(pattern: "_*").stringByReplacingMatches(in: result, range: NSRange(0..<result.count), withTemplate: ("$1".uppercased()))
There's no regular syntax to convert a match to uppercase. The code you posted is attempting to convert the string $1 to uppercase which is of course just $1. It isn't attempting to convert the value represented by the $1 match at runtime.
Here's another approach using a regular expression to find the _ followed by a lowercase letter. Those are enumerated and replaced with the uppercase letter.
extension String {
func toCamelCase() -> String {
let expr = try! NSRegularExpression(pattern: "_([a-z])")
var res = self
for match in expr.matches(in: self, range: NSRange(0..<res.count)).reversed() {
let range = Range(match.range, in: self)!
let letterRange = Range(match.range(at: 1), in: self)!
res.replaceSubrange(range, with: self[letterRange].uppercased())
}
return res
}
}
print("te_st".toCamelCase())
print("_he_l_lo".toCamelCase())
print("an_o_t_h_er_strin_g".toCamelCase())
This outputs:
teSt
HeLLo
anOTHErStrinG
Here is one implementation using NSRegularExpression. I use group match to get the character after _ and capitalize it and replace the string.
func capitalizeLetterAfterUnderscore(string: String) -> String {
var capitalizedString = string
guard let regularExpression = try? NSRegularExpression(pattern: "_(.)") else {
return capitalizedString
}
let matches = regularExpression.matches(in: string,
options: .reportCompletion,
range: NSMakeRange(0, string.count))
for match in matches {
let groupRange = match.range(at: 1)
let index = groupRange.location
let characterIndex = string.index(string.startIndex,
offsetBy: index)
let range = characterIndex ... characterIndex
let capitalizedCharacter = String(capitalizedString[characterIndex]).capitalized
capitalizedString = capitalizedString.replacingCharacters(in: range,
with: capitalizedCharacter)
}
capitalizedString = capitalizedString.replacingOccurrences(of: "_", with: "")
return capitalizedString
}
capitalizeLetterAfterUnderscore(string: "an_o_t_h_er_strin_g") // anOTHErStrinG
And here is other one without using regular expression. I made extension for method which could also be reused.
extension String {
func indexes(of character: String) -> [Index] {
precondition(character.count == 1, "character should be single letter string")
return enumerated().reduce([]) { (partial, component) in
let currentIndex = index(startIndex,
offsetBy: component.offset)
return String(self[currentIndex]) == character
? partial + [currentIndex]
: partial
}
}
func capitalizeLetter(after indexes: [Index]) -> String {
var modifiedString = self
for currentIndex in indexes {
guard let letterIndex = index(currentIndex,
offsetBy: 1,
limitedBy: endIndex)
else { continue }
let range = letterIndex ... letterIndex
modifiedString = modifiedString.replacingCharacters(in: range,
with: self[range].capitalized)
}
return modifiedString
}
}
let string = "an_o_t_h_er_strin_g"
let newString = string.capitalizeLetter(after: string.indexes(of: "_"))
.replacingOccurrences(of: "_",with: "")
You can use string range(of:, options:, range:) method with .regularExpression options to match the occurrences of "_[a-z]" and replace the subranges iterating the ranges found at reversed order by the character at the index after the range lowerbound uppercased:
let string = "an_o_t_h_er_strin_g"
let regex = "_[a-z]"
var start = string.startIndex
var ranges:[Range<String.Index>] = []
while let range = string.range(of: regex, options: .regularExpression, range: start..<string.endIndex) {
start = range.upperBound
ranges.append(range)
}
var finalString = string
for range in ranges.reversed() {
finalString.replaceSubrange(range, with: String(string[string.index(after: range.lowerBound)]).uppercased())
}
print(finalString) // "anOTHErStrinG\n"
The problem is that it is converting the string "$1" to upper case (which is, unsurprisingly unchanged, just "$1") and using "$1" as the template. If you want to use regex, you will have to enumerate through matches yourself.
The alternative is to split the string by _ characters and uppercase the first character of every substring (except the first) and joining it back together using reduce:
let input = "te_st"
let output = input.components(separatedBy: "_").enumerated().reduce("") { $0 + ($1.0 == 0 ? $1.1 : $1.1.uppercasedFirst()) }
Or, if your goal isn't to write code as cryptic as most regex, we can make that a tad more legible:
let output = input
.components(separatedBy: "_")
.enumerated()
.reduce("") { result, current in
if current.offset == 0 {
return current.element // because you donā€™t want the first component capitalized
} else {
return result + current.element.uppercasedFirst()
}
}
Resulting in:
teSt
Note, that uses this extension for capitalizing the first character:
extension String {
func uppercasedFirst(with locale: Locale? = nil) -> String {
guard count > 0 else { return self }
return String(self[startIndex]).uppercased(with: locale) + self[index(after: startIndex)...]
}
}
If you want to do sort of dynamic conversion with NSRegularExpression, you can subclass NSRegularExpression and override replacementString(for:in:offset:template:):
class ToCamelRegularExpression: NSRegularExpression {
override func replacementString(for result: NSTextCheckingResult, in string: String, offset: Int, template templ: String) -> String {
if let range = Range(result.range(at: 1), in: string) {
return string[range].uppercased()
} else {
return super.replacementString(for: result, in: string, offset: 0, template: templ)
}
}
}
func toCamelCase(_ input: String) -> String { //Make this a String extension if you prefer...
let regex = try! ToCamelRegularExpression(pattern: "_(.)")
return regex.stringByReplacingMatches(in: input, options: [], range: NSRange(0..<input.utf16.count), withTemplate: "$1")
}
print(toCamelCase("te_st")) //-> teSt
print(toCamelCase("_he_l_lo")) //-> HeLLo
print(toCamelCase("an_o_t_h_er_strin_g")) //-> anOTHErStrinG

How to get the first characters in a string? (Swift 3)

I want to get a substring out of a string which starts with either "<ONLINE>" or "<OFFLINE>" (which should become my substring). When I try to create a Range object, I can easily access the the first character by using startIndex but how do I get the index of the closing bracket of my substring which will be either the 8th or 9th character of the full string?
UPDATE:
A simple example:
let onlineString:String = "<ONLINE> Message with online tag!"
let substring:String = // Get the "<ONLINE> " part from my string?
let onlineStringWithoutTag:String = onlineString.replaceOccurances(of: substring, with: "")
// What I should get as the result: "Message with online tag!"
So basically, the question is: what do I do for substring?
let name = "Ajay"
// Use following line to extract first chracter(In String format)
print(name.characters.first?.description ?? "");
// Output : "A"
If you did not want to use range
let onlineString:String = "<ONLINE> Message with online tag!"
let substring:String = onlineString.components(separatedBy: " ")[0]
print(substring) // <ONLINE>
The correct way would be to use indexes as following:
let string = "123 456"
let firstCharIndex = string.index(string.startIndex, offsetBy: 1)
let firstChar = string.substring(to: firstCharIndex)
print(firstChar)
This Code provides you the first character of the string.
Swift provides this method which returns character? you have to wrap it before use
let str = "FirstCharacter"
print(str.first!)
Similar to OOPer's:
let string = "<ONLINE>"
let closingTag = CharacterSet(charactersIn: ">")
if let closingTagIndex = string.rangeOfCharacter(from: closingTag) {
let mySubstring = string.substring(with: string.startIndex..<closingTagIndex.upperBound)
}
Or with regex:
let string = "<ONLINE>jhkjhkh>"
if let range = string.range(of: "<[A-Z]+>", options: .regularExpression) {
let mySubstring = string.substring(with: range)
}
This code be some help for your purpose:
let myString = "<ONLINE>abc"
if let rangeOfClosingAngleBracket = myString.range(of: ">") {
let substring = myString.substring(to: rangeOfClosingAngleBracket.upperBound)
print(substring) //-><ONLINE>
}
Swift 4
let firstCharIndex = oneGivenName.index(oneGivenName.startIndex, offsetBy: 1)
let firstChar = String(oneGivenName[..<firstCharIndex])
let character = MyString.first
it's an simple way to get first character from string in swift.
In swift 5
let someString = "Stackoverflow"
let firstChar = someString.first?.description ?? ""
print(firstChar)
Swift 5 extension
extension String {
var firstCharactor: String? {
guard self.count > 0 else {
return nil
}
return String(self.prefix(1))
}
}