Regex - Cost of an Item - ruby-on-rails

What is the regular expression to check a cost has been provided correctly:
Number must be greater than or equal to 0.01
Number must be less than or equal to 99.99
Possible matches are:
9 | 23.3 | 25.69
Not allowed:
| 2.

Of course, the correct way would be to take the provided string, convert it to a number (catching errors if it's not a parseable number) and then compare that with the valid values.
If it has to be a regex, it's of course possible but ugly:
^(?:[1-9][0-9]?(?:\.[0-9]{1,2})?|0?.0[1-9]|0?.[1-9][0-9]?)$
Explanation:
^ # start of string
(?: # either match
[1-9][0-9]? # a number between 1 and 99
(?:\.[0-9]{1,2})? # optionally followed by a decimal point and up to two digits
| # or
0?.0[1-9] # a number between 0.01 and 0.09 (optionally without leading 0)
| # or
0?.[1-9][0-9]? # a number between 0.1 and 0.99
) # end of alternation
$ # end of string
Of course, in most regex dialects, you can use \d in place of [0-9] without a change in meaning, but I think in this case sticking to the longer version helps readability.
In Ruby, assuming your input string never contains a newline:
if subject =~ /^(?:[1-9][0-9]?(?:\.[0-9]{1,2})?|0?.0[1-9]|0?.[1-9][0-9]?)$/
# Successful match
else
# Match attempt failed
end
Since you care about the number of significant digits, another solution would be to first check if the input looks like a number, and if it passes that test, convert it to a float and check if it's in range.
^(\d{1,2}|\d{0,2}\.\d{1,2})$
would match any number (integer or decimal up to two digits after the decimal point) between 0 and 99.99. Then you just need to check whether the number is >= 0.01. The advantage of this approach is that you can easily extend the range of digits allowed before/after the decimal point if the requirements for valid numbers change, and then adjust the value check accordingly.

I am not sure you need to do this using regular expression why dont you just split the string on '|' into an array and check each element in array is greater than 0.01 and less than 99.99

A solution using a regexp would be hard to maintain since it is a very strange and unintuitive way to solve the problem. It would be hard for anyone reading your code (including yourself in a couple of weeks) to understand what the purpose of the check is.
That said, assuming values 0.00 - 99.99 are valid, the regexp could be
^\d{0,2}(\.\d\d?)?$
Assuming 0.01 - 99.99, it's a bit more complicated:
^0{0,2}(\.(0[1-9]|[1-9]\d?))?|\d{0,2}(\.\d\d)?$
And don't get me started on 0.02 - 99.98... :-)
So basically, don't do this. Convert the string to a numerical value and then do a regular interval check.

try
^\d{1,2}(\.\d{1,2})?$
it checks whether there are 1 or 2 digits and optionally if there is a dot it checks if there are 1 or 2 digits after the dot.
as answer to the comments of your question: nothing speaks against checking for the format before sending the request to a server or something else. range checks could be done somewhere else.

It's not the best but works
(0\.01|0\.02|0\.03| ... |99\.98|99\.99)

Since you're in Rails, you might be better served using validates_numericality_of in your model:
validates_numericality_of :cost, :greater_than_or_equal_to => 0.01,
:less_than_or_equal_to => 0.99
To prevent fractional pennies, use this in conjunction with validates_format_of:
validates_format_of :cost, :with => /\A\d*(\.\d{1,2})?\Z/
This leverages the strength of each validation.

Related

How to specify a range in Ruby

I've been looking for a good way to see if a string of items are all numbers, and thought there might be a way of specifying a range from 0 to 9 and seeing if they're included in the string, but all that I've looked up online has really confused me.
def validate_pin(pin)
(pin.length == 4 || pin.length == 6) && pin.count("0-9") == pin.length
end
The code above is someone else's work and I've been trying to identify how it works. It's a pin checker - takes in a set of characters and ensures the string is either 4 or 6 digits and all numbers - but how does the range work?
When I did this problem I tried to use to_a? Integer and a bunch of other things including ranges such as (0..9) and ("0..9) and ("0".."9") to validate a character is an integer. When I saw ("0-9) it confused the heck out of me, and half an hour of googling and youtube has only left me with regex tutorials (which I'm interested in, but currently just trying to get the basics down)
So to sum this up, my goal is to understand a more semantic/concise way to identify if a character is an integer. Whatever is the simplest way. All and any feedback is welcome. I am a new rubyist and trying to get down my fundamentals. Thank You.
Regex really is the right way to do this. It's specifically for testing patterns in strings. This is how you'd test "do all characters in this string fall in the range of characters 0-9?":
pin.match(/\A[0-9]+\z/)
This regex says "Does this string start and end with at least one of the characters 0-9, with nothing else in between?" - the \A and \z are start-of-string and end-of-string matchers, and the [0-9]+ matches any one or more of any character in that range.
You could even do your entire check in one line of regex:
pin.match(/\A([0-9]{4}|[0-9]{6})\z/)
Which says "Does this string consist of the characters 0-9 repeated exactly 4 times, or the characters 0-9, repeated exactly 6 times?"
Ruby's String#count method does something similar to this, though it just counts the number of occurrences of the characters passed, and it uses something similar to regex ranges to allow you to specify character ranges.
The sequence c1-c2 means all characters between c1 and c2.
Thus, it expands the parameter "0-9" into the list of characters "0123456789", and then it tests how many of the characters in the string match that list of characters.
This will work to verify that a certain number of numbers exist in the string, and the length checks let you implicitly test that no other characters exist in the string. However, regexes let you assert that directly, by ensuring that the whole string matches a given pattern, including length constraints.
Count everything non-digit in pin and check if this count is zero:
pin.count("^0-9").zero?
Since you seem to be looking for answers outside regex and since Chris already spelled out how the count method was being implemented in the example above, I'll try to add one more idea for testing whether a string is an Integer or not:
pin.to_i.to_s == pin
What we're doing is converting the string to an integer, converting that result back to a string, and then testing to see if anything changed during the process. If the result is =>true, then you know nothing changed during the conversion to an integer and therefore the string is only an Integer.
EDIT:
The example above only works if the entire string is an Integer and won’t properly deal with leading zeros. If you want to check to make sure each and every character is an Integer then do something like this instead:
pin.prepend(“1”).to_i.to_s(1..-1) == pin
Part of the question seems to be exactly HOW the following portion of code is doing its job:
pin.count("0-9")
This piece of the code is simply returning a count of how many instances of the numbers 0 through 9 exist in the string. That's only one piece of the relevant section of code though. You need to look at the rest of the line to make sense of it:
pin.count("0-9") == pin.length
The first part counts how many instances then the second part compares that to the length of the string. If they are equal (==) then that means every character in the string is an Integer.
Sometimes negation can be used to advantage:
!pin.match?(/\D/) && [4,6].include?(pin.length)
pin.match?(/\D/) returns true if the string contains a character other than a digit (matching /\D/), in which case it it would be negated to false.
One advantage of using negation here is that if the string contains a character other than a digit pin.match?(/\D/) would return true as soon as a non-digit is found, as opposed to methods that examine all the characters in the string.

How to generate a sequence code string in Rails

I have a model which has a column named code, which is a combination of the model's name column and its ID with leading zeros.
name = 'Rocky'
id = 16
I have an after_create callback which runs and generates the code:
update(code: "#{self.name[0..2].upcase}%.4d" % self.id)
The generated code will be:
"ROC0016"
The code is working.
I found (%.4d" % self.id) from another project, but I don't know how it works.
How does it determine the number of zeros to be preceded based on the passed integer.
You’re using a "format specifier". There are many specifiers, but the one you’re using, "%d", is the decimal specifier:
% starts it. 4 means it should always use at least four numbers, so if the number is only two digits, it gets padded with 0s to fill in the rest of the numbers. The second % means replace 4d with whatever comes after it. So in your case, 4d is getting replaced with "0016".
sprintf has more information about format specifiers.
You can read more about String#% in the documentation also.
After the percentage sign ("%") is a decimal (".") and a number. That number is the number of total digits in the result. If the result is less than this value, additional zeros will be added.
Thus, in this first example, the result is "34" but length was set to "4". The result will have two leading zeros to fill it into four digits.
"This is test string %.4d" % 34
result => "This is test string 0034"
"I want more zeroes in my code %.7d" % 34
result => "I want more zeroes in my code 0000034"

Define a regex, which matches one digit twice and all others once

As part of a larger regex I would like to match the following restrictions:
The string has 11 digits
All digits are numbers
Within the first 10 digits one number [0-9] (and one only!) must be listed twice
This means the following should match:
12345678914
12235879600
Whereas these should not:
12345678903 -> none of the numbers at digits 1 to 10 appears twice
14427823482 -> one number appears more than twice
72349121762 -> two numbers appear twice
I have tried to use a lookahead, but all I'm managing is that the regex counts a certain digit, i.e.:
(?!.*0\1{2})
That does not do what I need. Is my query even possible with regex?
You can use this kind of pattern:
\A(?=\d{11}\z)(?:(\d)(?!\d*\1\d))*(\d)(?=\d*\2\d)(?:(\d)(?!\d*\3\d))+\d\z
online demo
pattern details:
the idea is to describe string as a duplicate digit surrounded by non duplicate digits.
Finding a duplicate digit is easy with a capture group, a lookahead assertion and a backreference:(\d)(?=\d*\1)
You can use the same pattern to ensure that a digit has no duplicate, but this time with a negative lookahead: (\d)(?!\d*\1)
To not take in account the last digit (digit n°11) in the search of duplicates, you only need to add a digit after the backreference. (\d)(?=\d*\1\d) (in this way you ensure there is at least one digit between the backreference and the end of the string.)
Note that in the present context, what is called a duplicate digit is a digit that is not followed immediatly or later with the same digit. (i.e. in 1234567891 the first 1 is a duplicate digit, but the last 1 is no more a duplicate digit because it is not followed by an other 1)
\A # begining of the string
(?=\d{11}\z) # check the string length (if not needed, remove it)
(?:(\d)(?!\d*\1\d))* # zero or more non duplicate digits
(\d)(?=\d*\2\d) # one duplicate digit
(?:(\d)(?!\d*\3\d))+ # one or more non duplicate digits
\d # the ignored last digit
\z # end of the string
an other way
This time you check the duplicates at the begining of the pattern with lookaheads. One lookahead to ensure there is one duplicate digit, one negative lookahead to ensure there are not two duplicate digits:
\A(?=\d*(\d)(?=\d*\1\d))(?!\d*(\d)(?=\d*\2\d)\d*(\d)(?=\d*\3\d))\d{11}\z
pattern details:
\A
(?= # check if there is one duplicate digit
\d*(\d)(?=\d*\1\d)
)
(?! # check if there are not two duplicate digits
\d*(\d)(?=\d*\2\d) # the first
\d*(\d)(?=\d*\3\d) # the second
)
\d{11}
\z
Note: However it seems that the first way is more efficient.
The code way
You can easily check if your string fit the requirements with array methods:
> mydigs = "12345678913"
=> "12345678913"
> puts (mydigs.split(//).take 10).uniq.size == 9
true
=> nil

Strip out thousands delineator specific to the locale

I have a rails app in which users input numbers in large quantities. They often use the thousands delimiter (e.g. 1,000,000,000) to help keep their large numbers human-readable (I don't want to disallow delimiter because doing so would increase the chance of incorrect data).
ActiveSupport/Rails has the handy method number_with_delimiter so that an int 1234567 is displayed as 1,234,567. Is there a method to do the reverse?
note: I don't want to simply strip out a comma, since commas are used as a decimal point in many locales (e.g. European)
To answer your general question, you can determine the "delimiter" (thousands-separator) and the "separator" (decimal separator) from the Rails localization system directly:
I18n.t('number.format.separator') # <= '.' on a US English system
I18n.t('number.format.delimiter') # <= ',' on a US English system
So you can do this:
better = input_string.gsub(I18n.t('number.format.delimiter'), '')
Or, if you prefer be more aggressive and remove all non-numerical and non-decimal input:
better = input_string.gsub(/[^\d#{I18n.t('number.format.separator')}]/, '')
Note, though, that the second example will also remove negative signs, if that matters to you.
It is also worth noting that ActiveRecord will do this for you:
my_model.update_attributes(some_float: "1,234.50") # <= sets some_float to 1234.5

Conditional Regular Expression testing of a CSV

I am doing some client side validation in ASP.NET MVC and I found myself trying to do conditional validation on a set of items (ie, if the checkbox is checked then validate and visa versa). This was problematic, to say the least.
To get around this, I figured that I could "cheat" by having a hidden element that would contain all of the information for each set, thus the idea of a CSV string containing this information.
I already use a custom [HiddenRequired] attribute to validate if the hidden input contains a value, with success, but I thought as I will need to validate each piece of data in the csv, that a regular expression would solve this.
My regular expression work is extremely weak and after a good 2 hours I've almost given up.
This is an example of the csv string:
true,3,24,over,0.5
to explain:
true denotes if I should validate the rest. I need to conditionally switch in the regex using this
3 and 24 are integers and will only ever fall in the range 0-24.
over is a string and will either be over or under
0.5 is a decimal value, of unknown precision.
In the validation, all values should be present and at least of the correct type
Is there someone who can either provide such a regex or at least provide some hints, i'm really stuck!
Try this regex:
#"^(true,([01]?\d|2[0-4]),([01]?\d|2[0-4]),(over|under),\d+\.?\d+|false.*)$"
I'll try to explain it using comments. Feel free to ask if anything is unclear. =)
#"
^ # start of line
(
true, # literal true
([01]?\d # Either 0, 1, or nothing followed by a digit
| # or
2[0-4]), # 20 - 24
([01]?\d|2[0-4]), # again
(over|under), # over or under
\d+\.?\d+ # any number of digits, optional dot, any number of digits
| #... OR ...
false.* # false followed by anything
)
$ # end of line
");
I would probably use a Split(',') and validate elements of the resulting array instead of using a regex. Also you should watch out for the \, case (the comma is part of the value).

Resources