Regular expression to find 3 consecutive numbers in a string equal to or increasing by 1 in Swift - swift

I am writing a code that takes a string and verifies it. First, we created a regular expression that has a string length of 5 or more and less than 24 and can use only upper and lower case letters and dashes, and first verified.
func iDValidator(_ id: String) -> Bool {
let lengthAndElementRegEx = "^\\w{5,23}[a-zA-Z0-9-]$"
let continuousRegEx = "I Need!!!"
let lengthAndElementValidate = NSPredicate(format:"SELF MATCHES %#", lengthAndElementRegEx)
guard lengthAndElementValidate.evaluate(with: id) == true else {
return false
}
let continuousValidate = NSPredicate(format:"SELF MATCHES %#", continuousRegEx)
guard continuousValidate.evaluate(with: id) == true else {
print("###false")
return false
}
return true
}
After that, I would like to verify that a number appears 3 times in a row or 3 consecutive numbers in a string such as "111", "222", "123", and "456". Do you have a simple regex? Help!

You can use something like this:
([0-9])\1{2}|(012|123|234|345|456|567|678|789)
Where:
([0-9]) - Group 1 capturing 1 digit
\1{2} - Captures 2 more occurrences (due to the limiting quantifier {2}) of the value captured in Group 1 (where \1 is a backreference to Group 1 value)
(012|123|234|345|456|567|678|789) - simply matches all possible number sequences
You can try it yourself: https://regex101.com/r/noBKsE/2
Source

Related

Swift 5: How to calculate correct Date from provided string?

I am not good at Swift so I would like to know how to calculate the current time is between two times.
I got the following response from the backend.
{"workHours":"M-F 9:00 - 18:00"}
How to get start(9:00) time, end(18:00) time from "M-F 11:00 - 20:00" response using regular expression and DateFormatter?(I am not sure it is the correct way to get Date object from this kind of string, if not please recommend the best way and experience)
You can use the following regex:
"^\\w-\\w\\s{1}(\\d{1,2}:\\d{2})\\s{1}-\\s{1}(\\d{1,2}:\\d{2})$"
break down
^ asserts position at start of a line
A-Z a single character in the range between A (index 65) and Z (index 90) (case sensitive)
- matches the character - literally (case sensitive)A-Z a single character in the range between A (index 65) and Z (index 90) (case sensitive)
\s matches any whitespace character (equal to [\r\n\t\f\v]) {1} Quantifier — Matches exactly one time meaningless quantifier)
1st Capturing Group (\d{1,2}:\d{2})
\d{1,2} matches a digit (equal to [0-9])
{1,2} Quantifier — Matches between 1 and 2 times, as many times as possible, giving back as needed (greedy)
: matches the character : literally (case sensitive)
\d{2} matches a digit (equal to [0-9]) \s matches any whitespace character (equal to [\r\n\t\f\v]) {1} Quantifier — Matches exactly one time (meaningless quantifier)
- matches the character - literally (case sensitive)
\s{1} matches any whitespace character (equal to [\r\n\t\f\v ]) {1} Quantifier — Matches exactly one time (meaningless quantifier)
2nd Capturing Group (\d{1,2}:\d{2})
$ asserts position at the end of a line
let response = "M-F 11:00 - 20:00"
let pattern = "^[A-Z]-[A-Z]\\s{1}(\\d{1,2}:\\d{2})\\s{1}-\\s{1}(\\d{1,2}:\\d{2})$"
let regex = try! NSRegularExpression(pattern: pattern)
if let match = regex.matches(in: response, range: .init(response.startIndex..., in: response)).first,
match.numberOfRanges == 3 {
match.numberOfRanges
let start = match.range(at: 1)
print(response[Range(start, in: response)!])
let end = match.range(at: 2)
print(response[Range(end, in: response)!])
}
This will print
11:00
20:00
To get the time difference between start and end times (this considers that the resulting string will be a match of the above regex:
extension StringProtocol {
var minutesFromTime: Int {
let index = firstIndex(of: ":")!
return Int(self[..<index])! * 60 + Int(self[index...].dropFirst())!
}
}
let start = response[Range(match.range(at: 1), in: response)!]
let end = response[Range(match.range(at:2), in: response)!]
let minutes = end.minutesFromTime - start.minutesFromTime // 540
let hours = Double(minutes) / 60 // 9

Why does my tap counter go back to 1 when it gest higher than 10?

I am trying to just simply have a button you tap and it adds 1 to a label. If that label is below 3 print its below three. If it's above 3 print that it's above. This works up until 10 which then prints out it's below three even though the label still shows 10 or higher.
var counter = 0
#IBOutlet weak var count: UILabel!
#IBAction func testigbutton(_ sender: UIButton) {
counter = counter + 1
count.text = String(format: "%i", counter)
if count.text! < "3" {
print("Less than 3")
} else if count.text! > "10" {
print("More than 3")
}
}
Comparison of String is done character by character.
"9" is greater than "3" because the character 9 is above the character 3 if sorted.
"10" is less than "3" because, as this does character by character comparison, "1" is less than "3" and it finishes there.
If you need to do numerical comparison (an actual number instead of strings), use:
if Int(count.text!) < 3 { ... } else { ... }
Note that I'm comparing an actual Int and not a String.
Change the line
if count.text! < "3" {
to:
if counter < 3 {
This way you'll compare numbers by their order instead of strings by their lexicographic order.
Since you are comparing a string, it checks each character. In other words, instead of comparing to 10, it compares to 1 then to 0. Since 1 is <3, it prints that. All you need to fix this is either to simply compare to your counter variable, or cast it to an Int or Double with something like
Int(count.text!)

Number validation and formatting

I want to format, in real time, the number entered into a UITextField. Depending on the field, the number may be an integer or a double, may be positive or negative.
Integers are easy (see below).
Doubles should be displayed exactly as the user enters with three possible exceptions:
If the user begins with a decimal separator, or a negative sign followed by a decimal separator, insert a leading zero:
"." becomes "0."
"-." becomes "-0."
Remove any "excess" leading zeros if the user deletes a decimal point:
If the number is "0.00023" and the decimal point is deleted, the number should become "23".
Do not allow a leading zero if the next character is not a decimal separator:
"03" becomes "3".
Long story short, one and only one leading zero, no trailing zeros.
It seemed like the easiest idea was to convert the (already validated) string to a number then use format specifiers. I've scoured:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html
and
http://www.cplusplus.com/reference/cstdio/printf/
and others but can't figure out how to format a double so that it does not add a decimal when there are no digits after it, or any trailing zeros. For example:
x = 23.0
print (String(format: "%f", x))
//output is 23.000000
//I want 23
x = 23.45
print (String(format: "%f", x))
//output is 23.450000
//I want 23.45
On How to create a string with format?, I found this gem:
var str = "\(INT_VALUE) , \(FLOAT_VALUE) , \(DOUBLE_VALUE), \(STRING_VALUE)"
print(str)
It works perfectly for integers (why I said integers are easy above), but for doubles it appends a ".0" onto the first character the user enters. (It does work perfectly in Playground, but not my program (why???).
Will I have to resort to counting the number of digits before and after the decimal separator and inserting them into a format specifier? (And if so, how do I count those? I know how to create the format specifier.) Or is there a really simple way or a quick fix to use that one-liner above?
Thanks!
Turned out to be simple without using NumberFormatter (which I'm not so sure would really have accomplished what I want without a LOT more work).
let decimalSeparator = NSLocale.current.decimalSeparator! as String
var tempStr: String = textField.text
var i: Int = tempStr.count
//remove leading zeros for positive numbers (integer or real)
if i > 1 {
while (tempStr[0] == "0" && tempStr[1] != decimalSeparator[0] ) {
tempStr.remove(at: tempStr.startIndex)
i = i - 1
if i < 2 {
break
}
}
}
//remove leading zeros for negative numbers (integer or real)
if i > 2 {
while (tempStr[0] == "-" && tempStr[1] == "0") && tempStr[2] != decimalSeparator[0] {
tempStr.remove(at: tempStr.index(tempStr.startIndex, offsetBy: 1))
i = i - 1
if i < 3 {
break
}
}
}
Using the following extension to subscript the string:
extension String {
subscript (i: Int) -> Character {
return self[index(startIndex, offsetBy: i)]
}
}

NSString.rangeOfString returns unusual result with non-latin characters

I need to get the range of two words in a string, for example:
ยัฟิแก ไฟหก
(this is literally me typing PYABCD WASD) - it's a non-sensical test since I don't speak Thai.
//Find all the ranges of each word
var words: [String] = []
var ranges: [NSRange] = []
//Convert to nsstring first because otherwise you get stuck with Ranges and Strings.
let nstext = backgroundTextField.stringValue as NSString //contains "ยัฟิแก ไฟหก"
words = nstext.componentsSeparatedByString(" ")
var nstextLessWordsWeHaveRangesFor = nstext //if you have two identical words this prevents just getting the first word's range
for word in words
{
let range:NSRange = nstextLessWordsWeHaveRangesFor.rangeOfString(word)
Swift.print(range)
ranges.append(range)
//create a string the same length as word
var fillerString:String = ""
for i in 0..<word.characters.count{
//for var i=0;i<word.characters.count;i += 1{
Swift.print("i: \(i)")
fillerString = fillerString.stringByAppendingString(" ")
}
//remove duplicate words / letters so that we get correct range each time.
if range.length <= nstextLessWordsWeHaveRangesFor.length
{
nstextLessWordsWeHaveRangesFor = nstextLessWordsWeHaveRangesFor.stringByReplacingCharactersInRange(range, withString: fillerString)
}
}
outputs:
(0,6)
(5,4)
Those ranges are overlapping.
This causes problems down the road where I'm trying to use NSLayoutManager.enumerateEnclosingRectsForGlyphRange since the ranges are inconsistent.
How can I get the correct range (or in this specific case, non-overlapping ranges)?
Swift String characters describe "extended grapheme clusters", and NSString
uses UTF-16 code points, therefore the length of a string differs
depending on which representation you use.
For example, the first character "ยั" is actually the combination
of "ย" (U+0E22) with the diacritical mark " ั" (U+0E31).
That counts as one String character, but as two NSString characters.
As a consequence, indices change when you replace the word with
spaces.
The simplest solution is to stick to one, either String or NSString
(if possible). Since you are working with NSString, changing
for i in 0..<word.characters.count {
to
for i in 0..<range.length {
should solve the problem. The creation of the filler string
can be simplified to
//create a string the same length as word
let fillerString = String(count: range.length, repeatedValue: Character(" "))
Removing nstextLessWordsWeHaveRangesFor solves the issue (at the bottom starting with range.length <= nstextLessWordsWeHaveRangesFor.length). The modification of that variable is changing the range and giving unexpected output. Here is the result when the duplicate word removal is removed:
var words: [String] = []
let nstext = "ยัฟิแก ไฟหก" as NSString
words = nstext.componentsSeparatedByString(" ")
for word in words {
let range = nstext.rangeOfString(word)
print(range)
}
Output is: (0,6) and (7,4)

How convert a *positive* number into an array of digits in Swift

I want to convert a positive number into the respective list of digits -- the digits should be Ints as well.
When converting, say 1024, it should return [1,0,2,4]
in Swift 4.1 or above
let number = 1024
let digits = String(number).compactMap { Int(String($0)) }
print(digits) // [1, 0, 2, 4]
in Swift4
let number = 1024
let digits = String(number).flatMap { Int(String($0)) }
print(digits) // [1, 0, 2, 4]
in Swift2 and also Swift3
let number = 1024
let digits = String(number).characters.flatMap { Int(String($0)) }
print(digits) // [1, 0, 2, 4]
You don’t need to convert it to an array first. Since strings are collections, you can use the free (non-member) version of map:
map(number) { String($0).toInt() }
But beware your !. If number ever contains a non-numeric digit, your code will crash at runtime. And if the number is negative, it'll start with a "-".
How you want to handle this depends on what you want to do with negative numbers (maybe you want all the digits to be negative). But if you just wanted to ditch the leading "-" you could do something like:
let strNum = number >= 0 ? String(number) : dropFirst(String(number))
let digits = map(strNum) { String($0).toInt()! }
But just in case there's another possible non-numeric character for string representations of integer, you might find it better to do:
let digits = map(String(number)) { String($0).toInt() }.filter { $0 != nil }.map { $0! }
After some searching and some trial and error approach using the Swift REPL, I came up with this
var digits:[Int] = Array(String(number)).map { String($0).toInt()! }
Note that the !is critical