Regex for certain string and digit for time - ruby-on-rails

anyone can help me?
I have a string with format: 10 mins - 20 mins OR 1 min - 10 mins OR 10 mins - 1 hour OR 10 mins - 1 hour 30 mins OR 10 mins - 2 hours OR 10 mins - 2 hours 30 mins.
I wanna create a regex for all formats but I can't :disappointed: . Please help me, thanks a lot.
A regex possible with all above formats.

I might split on the dash first, then match out the components in each side using the same regular expression.
E.g.
"1 hour 10 mins - 3 hours".split(/\s+-\s+/, 2).map do |s|
s.scan(
/
\d+ \s+ mins
| \d+ \s+ hour(?=s?)
| \d+ \s+ hour(?=s?)
\s+
\d+ \s+ min(?=s?)
/x
)
end
# => [["1 hour", "10 mins"], ["3 hour"]]
"1 hour 10 mins - 40 mins".split(/\s+-\s+/, 2).map do |s|
s.scan(
/
\d+ \s+ mins
| \d+ \s+ hour(?=s?)
| \d+ \s+ hour(?=s?)
\s+
\d+ \s+ min(?=s?)
/x
)
end
# [["1 hour", "10 mins"], ["40 mins"]]

I've assumed:
every string contains only minutes (not hours) to the left of the minus sign; and
hours, where present, are between 1 and 9.
These assumptions are consistent with the examples given in the question.
One can then use String#match and MatchData#named_captures with the regular expression
rgx = /(?<lmin>(?:[1-9]|[1-5]\d)) mins? \- (?:(?<hr>[1-9]) hours?(?: (?<rminwhr>\g<lmin>) mins?)?|(?<rminwohr>\g<lmin>) mins?)/
to extract the values of interest. Let's try it for the examples given in the question.
["10 mins - 20 mins",
"1 min - 10 mins",
"10 mins - 1 hour",
"10 mins - 1 hour 30 mins",
"10 mins - 2 hours",
"10 mins - 2 hours 30 mins"].each do |str|
m = str.match(rgx)
puts str
puts "str.match(rgx) = #{m.inspect}"
puts "captures = #{m.named_captures}"
puts
end
displays the following.
10 mins - 20 mins
str.match(rgx) = #<MatchData "10 mins - 20 mins" lmin:"20" hr:nil rminwhr:nil rminwohr:"20">
captures = {"lmin"=>"20", "hr"=>nil, "rminwhr"=>nil, "rminwohr"=>"20"}
1 min - 10 mins
str.match(rgx) = #<MatchData "1 min - 10 mins" lmin:"10" hr:nil rminwhr:nil rminwohr:"10">
captures = {"lmin"=>"10", "hr"=>nil, "rminwhr"=>nil, "rminwohr"=>"10"}
10 mins - 1 hour
str.match(rgx) = #<MatchData "10 mins - 1 hour" lmin:"10" hr:"1" rminwhr:nil rminwohr:nil>
captures = {"lmin"=>"10", "hr"=>"1", "rminwhr"=>nil, "rminwohr"=>nil}
10 mins - 1 hour 30 mins
str.match(rgx) = #<MatchData "10 mins - 1 hour 30 mins" lmin:"30" hr:"1" rminwhr:"30" rminwohr:nil>
captures = {"lmin"=>"30", "hr"=>"1", "rminwhr"=>"30", "rminwohr"=>nil}
10 mins - 2 hours
str.match(rgx) = #<MatchData "10 mins - 2 hours" lmin:"10" hr:"2" rminwhr:nil rminwohr:nil>
captures = {"lmin"=>"10", "hr"=>"2", "rminwhr"=>nil, "rminwohr"=>nil}
10 mins - 2 hours 30 mins
str.match(rgx) = #<MatchData "10 mins - 2 hours 30 mins" lmin:"30" hr:"2" rminwhr:"30" rminwohr:nil>
captures = {"lmin"=>"30", "hr"=>"2", "rminwhr"=>"30", "rminwohr"=>nil}
lmin reads "minutes to left of minus sign"; rminwhr (rminwohr) reads "minutes to right of minus sign with (without) hours".
One can see a demonstration of the regular expression here. Hover the cursor over the regular expression to see an explanation of the function of each part of the expression. These results are shown for the PCRE regex engine but they are the same for Ruby's regex engine (Oniguruma).
We can write the regular expression in free-spacing mode to make it self-documenting.
/
(?<lmin> # begin capture group 'lmin'
(?: # begin non-capture group
[1-9] # match a digit other than zero
| # or
[1-5]\d # match a digit between 1 and 5, followed by a digit
) # end non-capture group
) # end capture group 'lmin'
[ ]mins? # match a space followed by 'min', 's' optional
[ ]\-[ ] # match ' - '
(?: # begin non-capture group
(?<hr> # begin capture group 'hr'
[1-9] # match a digit other than zero
) # end capture group 'hr'
[ ]hours? # match a space followed by 'hour', 's' optional
(?: # begin a non-capture group
[ ] # match a space
(?<rminwhr> # begin capture group 'rminwhr'
\g<lmin> # invoke subexpression 'lmin'
) # end capture group 'rminwhr'
[ ]mins? # match a space followed by 'min', 's' optional
) # end non-capture group
? # make the preceding non-capture group optional
| # or
(?<rminwohr> # begin capture group 'rminwohr'
\g<lmin> # invoke subexpression 'lmin'
) # end capture group 'rminwohr'
[ ]mins? # match a space followed by 'min', 's' optional
) # end non-capture group
/x # invoke free-spacing regex definition mode
g<lmin> causes the code used to create named capture group named lmin to be reused at that location. This is called a "subexpression" or "subrouting". For an explanation search for "Subexpression" in Regexp. The use of subexpressions reduces the size of the expression, makes it easier to read and reduces the chances of errors, particularly if the expression is modified in future.
Notice that in free-spacing mode spaces that are intended to part of the expression must be protected in some way to prevent them from being stripped out before the expression is parsed. I've chosen to put each one in a character class, but there are other ways of doing that (for one, escaping the space character).

Related

Minimum number of states in DFA

Minimum number states in the DFA accepting strings (base 3 i.e,, ternary form) congruent to 5 modulo 6?
I have tried but couldn't do it.
At first sight, It seems to have 6 states but then it can be minimised further.
Let's first see the state transition table:
Here, the states q0, q1, q2,...., q5 corresponds to the states with modulo 0,1,2,..., 5 respectively when divided by 6. q0 is our initial state and since we need modulo 5 therefore our final state will be q5
Few observations drawn from above state transition table:
states q0, q2 and q4 are exactly same
states q1, q3 and q5 are exactly same
The states which make transitions to the same states on the same inputs can be merged into a single state.
Note: Final and Non-final states can never be merged.
Therefore, we can merge q0, q2, q4 together and q1, q3 together leaving the state q5 aloof from collation.
The final Minimal DFA has 3 states as shown below:
Let's look at a few strings in the language:
12 = 1*3 + 2 = 5 ~ 5 (mod 6)
102 = 1*9 + 0*3 + 2 = 11 ~ 5 (mod 6)
122 = 1*9 + 2*3 + 2 = 17 ~ 5 (mod 6)
212 = 2*9 + 1*3 + 2 = 23 ~ 5 (mod 6)
1002 = 1*18 + 0*9 + 0*9 + 2 = 29 ~ 5 (mod 6)
We notice that all the strings end in 2. This makes sense since 6 is a multiple of 3 and the only way to get 5 from a multiple of 3 is to add 2. Based on this, we can try to solve the problem of strings congruent to 3 modulo 6:
10 = 3
100 = 9
120 = 15
210 = 21
1000 = 27
There's not a real pattern emerging, but consider this: every base-3 number ending in 0 is definitely divisible by 3. The ones that are even are also divisible by 6; so the odd numbers whose base-3 representation ends in 0 must be congruent to 3 mod 6. Because all the powers of 3 are odd, we know we have an odd number if the number of 1s in the string is odd.
So, our conditions are:
the string begins with a 1;
the string has an odd number of 1s;
the string ends with 2;
the string can contain any number of 2s and 0s.
To get the minimum number of states in such a DFA, we can use the Myhill-Nerode theorem beginning with the empty string:
the empty string can be followed by any string in the language. Call its equivalence class [e]
the string 0 cannot be followed by anything since valid base-3 representations don't have leading 0s. Call its equivalence class [0].
the string 1 must be followed with stuff that has an even number of 1s in it ending with a 2. Call its equivalence class [1].
the string 2 can be followed by anything in the language. Indeed, you can verify that putting a 2 at the front of any string in the language gives another string in the language. However, it can also be followed by strings beginning with 0. Therefore, its class is new: [2].
the string 00 can't be followed by anything to fix it; its class is the same as its prefix 0, [0]. same for the string 01.
the string 10 can be followed by any string with an even number of 1s that ends in a 2; it is therefore equivalent to the class [1].
the string 11 can be followed by any string in the language whatever; indeed, you can verify prepending 11 in front of any string in the language gives another solution. However, it can also be followed by strings beginning with 0. Therefore, its class is the same as [2].
12 can be followed by a string with an even number of 1s ending in 2, as well as by the empty string (since 12 is in fact in the language). This is a new class, [12].
21 is equivalent to 1; class [1]
22 is equivalent to 2; class [2]
20 is equivalent to 2; class [2]
120 is indistinguishable from 1; its class is [1].
121 is indistinguishable from [2].
122 is indistinguishable from [12].
We have seen no new equivalence classes on new strings of length 3; so, we know we have seen all the equivalence classes. They are the following:
[e]: any string in the language can follow this
[0]: no string can follow this
[1]: a string with an even number of 1s ending in 2 can follow this
[2]: same as [e] but also strings beginning with 0
[12]: same as [1] but also the empty string
This means that a minimal DFA for our language has five states. Here is the DFA:
[0]
^
|
0
|
----->[e]--2-->[2]<-\
| ^ |
| | |
1 __1__/ /
| / /
| | 1
V V |
[1]--2-->[12]
^ |
| |
\___0___/
(transitions not pictured are self-loops on the respective states).
Note: I expected this DFA to have 6 states, as Welbog pointed out in the other answer, so I might have missed an equivalence class. However, the DFA seems right after checking a few examples and thinking about what it's doing: you can only get to accepting state [12] by seeing a 2 as the last symbol (definitely necessary) and you can only get to state [12] from state [1] and you must have seen an odd number of 1s to get to [1]…
The minimum number of states for almost all modulus problems is the base of the modulus. The general strategy is one state for every modulus, as transitions between moduli are independent of what the previous numbers were. For example, if you're in state r4 (representing x = 4 (mod 6)), and you encounter a 1 as your next input, your new modulus is 4x6+1 = 25 = 1 (mod 6), so the transition from r4 on input 1 is to r1. You'll find that the start state and r0 can be merged, for a total of 6 states.

Regular expression to match a simple logic of getting the proper digits from string

Here I have a several different strings with salaries:
"U3 000 Per Month"
"U10 000 - U12 000 Per Month"
"U12 000 Per Month"
"U125 000 - U130 000 Per Month"
"U130 000 Per Month"
I'm trying to build a regex using the following logic:
Match the first 1,2 or 3 digits that come after first U letter only, then match zeros that come after a single space after first 1,2 or 3 digit group. The rest of the string doesn't matter. For example, we have few strings:
"U20 000 - U30 000 Per Month"
"U3 000 Per Month"
"U125 000 - U130 000 Per Month"
I want a regex to return
20000, 3000, 125000
Please help, I'm too exhausted of digging Regex documentation
You could try with (?<=^U)\d{1,3}\s0*.
regex = /(?<=^U)\d{1,3}\s0*/
p 'U20 000 - U30 000 Per Month'.scan(regex).first.sub(/\s/, '') # "20000"
p 'U3 000 Per Month'.scan(regex).first.sub(/\s/, '') # "3000"
p 'U125 000 - U130 000 Per Month'.scan(regex).first.sub(/\s/, '') # "125000"
I thought in only zeros after the \s, so as you've added \d{1,3} would fit better.

iOS - Regular expression for 4 digit int that can't be sequential or repeated digits?

I am trying to get a Regex that checks to make sure that a supplied int is 4 digits and it is not sequential nor contains all repeating digits (applied for 3 digits as well) whether in ascending or descending order. I don't really care if the regex returns a match for the non-allowed numbers, or returns a match of the original number if it is allowed.
So for example all of these numbers are what I would need to not pass validation with the regex:
1234
6543
4567
3333
3331
1333
1239
3219
1789
2543
While numbers like these would pass:
0443
6690
0420
6798
In other words,
Disallow ascending and descending patterns (1234, 4321).
Disallow repetitive patterns where the number is entered 4 times (1111, 3333).
No same three consecutive numeric digits. E.g. as 111 is not ok.
No continuous ascending or descending of three numeric digits. E.g. as
321 is not ok.
Thanks.
Description
^(?!.*?(?:0(?:12|98)|123|234|3(?:45|21)|4(?:56|32)|5(?:67|43)|6(?:78|54)|7(?:89|65)|876|987))(?!.*?(.)\1{2})[0-9]{4}
** To see the image better, simply right click the image and select view in new window
This regular expression will do the following:
Require the string to be 4 numbers long
Will not allow the same three consecutive digits. E.g. 111 is not ok.
Will not allow continuous ascending or descending of three or more digits. E.g.678 or 321 is not ok.
Example
Live Demo
https://regex101.com/r/qS3bO8/2
Sample text
So for example all of these numbers are what I would need to not pass validation with the regex:
1234
6543
4567
3333
3331
1333
1239
3219
1789
2543
While numbers like these would pass:
0443
6690
0420
6798
Sample Matches
MATCH 1
0. [186-190] `0443`
MATCH 2
0. [191-195] `6690`
MATCH 3
0. [196-200] `0420`
MATCH 4
0. [201-205] `6798`
Explanation
NODE EXPLANATION
----------------------------------------------------------------------
^ the beginning of the string
----------------------------------------------------------------------
(?! look ahead to see if there is not:
----------------------------------------------------------------------
.*? any character except \n (0 or more times
(matching the least amount possible))
----------------------------------------------------------------------
(?: group, but do not capture:
----------------------------------------------------------------------
0 '0'
----------------------------------------------------------------------
(?: group, but do not capture:
----------------------------------------------------------------------
12 '12'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
98 '98'
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
123 '123'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
234 '234'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
3 '3'
----------------------------------------------------------------------
(?: group, but do not capture:
----------------------------------------------------------------------
45 '45'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
21 '21'
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
4 '4'
----------------------------------------------------------------------
(?: group, but do not capture:
----------------------------------------------------------------------
56 '56'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
32 '32'
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
5 '5'
----------------------------------------------------------------------
(?: group, but do not capture:
----------------------------------------------------------------------
67 '67'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
43 '43'
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
6 '6'
----------------------------------------------------------------------
(?: group, but do not capture:
----------------------------------------------------------------------
78 '78'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
54 '54'
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
7 '7'
----------------------------------------------------------------------
(?: group, but do not capture:
----------------------------------------------------------------------
89 '89'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
65 '65'
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
876 '876'
----------------------------------------------------------------------
| OR
----------------------------------------------------------------------
987 '987'
----------------------------------------------------------------------
) end of grouping
----------------------------------------------------------------------
) end of look-ahead
----------------------------------------------------------------------
(?! look ahead to see if there is not:
----------------------------------------------------------------------
.*? any character except \n (0 or more times
(matching the least amount possible))
----------------------------------------------------------------------
( group and capture to \1:
----------------------------------------------------------------------
. any character except \n
----------------------------------------------------------------------
) end of \1
----------------------------------------------------------------------
\1{2} what was matched by capture \1 (2 times)
----------------------------------------------------------------------
) end of look-ahead
----------------------------------------------------------------------
[0-9]{4} any character of: '0' to '9' (4 times)
----------------------------------------------------------------------

Using time_diff to displays remaining days/hours/minutes/seconds in View

I'm using the time_diff gem in a Rails 4 application. I want to show how many days/hours/minutes/seconds are left between the created_at field and created_at + 7.days.
Time.diff(#auction.created_at + 7.days, #auction.created_at, '%d %H %N %S')[:diff]
Output:
7 days 0 hours 0 minutes 0 seconds
How can I get a result like:
7d 0h 0m 0s
Any suggestions?
As per their documentation, you can use %h %m %s instead of %H %N %S
documentation extract:
%h - hour (without adding 'hour' text to the hours. eg: 3 for 3 hours)
%m - minute (without adding 'minute' text)
%s - second (without adding 'second' text)
So your code should be
Time.diff(#auction.created_at + 7.days, #auction.created_at, '%d %hh %mm %ss')[:diff]
Now for the day you need to use a i18n yaml file
en:
day: "d"

Calculate number of business days between two days

I need to calculate the number of business days between two dates. How can I pull that off using Ruby (or Rails...if there are Rails-specific helpers).
Likewise, I'd like to be able to add business days to a given date.
So if a date fell on a Thursday and I added 3 business days, it would return the next Tuesday.
Take a look at business_time. It can be used for both the things you're asking.
Calculating business days between two dates:
wednesday = Date.parse("October 17, 2018")
monday = Date.parse("October 22, 2018")
wednesday.business_days_until(monday) # => 3
Adding business days to a given date:
4.business_days.from_now
8.business_days.after(some_date)
Historical answer
When this question was originally asked, business_time didn't provide the business_days_until method so the method below was provided to answer the first part of the question.
This could still be useful to someone who didn't need any of the other functionality from business_time and wanted to avoid adding an additional dependency.
def business_days_between(date1, date2)
business_days = 0
date = date2
while date > date1
business_days = business_days + 1 unless date.saturday? or date.sunday?
date = date - 1.day
end
business_days
end
This can also be fine tuned to handle the cases that Tipx mentions in the way that you would like.
We used to use the algorithm suggested in the mikej's answer and discovered that calculating 25,000 ranges of several years each takes 340 seconds.
Here's another algorithm with asymptotic complexity O(1). It does the same calculations in 0.41 seconds.
# Calculates the number of business days in range (start_date, end_date]
#
# #param start_date [Date]
# #param end_date [Date]
#
# #return [Fixnum]
def business_days_between(start_date, end_date)
days_between = (end_date - start_date).to_i
return 0 unless days_between > 0
# Assuming we need to calculate days from 9th to 25th, 10-23 are covered
# by whole weeks, and 24-25 are extra days.
#
# Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 # 1 2 3 4 5
# 6 7 8 9 10 11 12 # 6 7 8 9 ww ww ww
# 13 14 15 16 17 18 19 # ww ww ww ww ww ww ww
# 20 21 22 23 24 25 26 # ww ww ww ww ed ed 26
# 27 28 29 30 31 # 27 28 29 30 31
whole_weeks, extra_days = days_between.divmod(7)
unless extra_days.zero?
# Extra days start from the week day next to start_day,
# and end on end_date's week date. The position of the
# start date in a week can be either before (the left calendar)
# or after (the right one) the end date.
#
# Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 # 1 2 3 4 5
# 6 7 8 9 10 11 12 # 6 7 8 9 10 11 12
# ## ## ## ## 17 18 19 # 13 14 15 16 ## ## ##
# 20 21 22 23 24 25 26 # ## 21 22 23 24 25 26
# 27 28 29 30 31 # 27 28 29 30 31
#
# If some of the extra_days fall on a weekend, they need to be subtracted.
# In the first case only corner days can be days off,
# and in the second case there are indeed two such days.
extra_days -= if start_date.tomorrow.wday <= end_date.wday
[start_date.tomorrow.sunday?, end_date.saturday?].count(true)
else
2
end
end
(whole_weeks * 5) + extra_days
end
business_time has all the functionallity you want.
From the readme:
#you can also calculate business duration between two dates
friday = Date.parse("December 24, 2010")
monday = Date.parse("December 27, 2010")
friday.business_days_until(monday) #=> 1
Adding business days to a given date:
some_date = Date.parse("August 4th, 1969")
8.business_days.after(some_date) #=> 14 Aug 1969
Here is my (non gem and non holiday) weekday count example:
first_date = Date.new(2016,1,5)
second_date = Date.new(2016,1,12)
count = 0
(first_date...second_date).each{|d| count+=1 if (1..5).include?(d.wday)}
count
Take a look at Workpattern. It alows you to specify working and resting periods and can add/subtract durations to/from a date as well as calculate the minutes between two dates.
You can set up workpatterns for different scenarios such as mon-fri working or sun-thu and you can have holidays and whole or part days.
I wrote this as away to learn Ruby. Still need to make it more Ruby-ish.
Based on #mikej's answer. But this also takes into account holidays, and returns a fraction of a day (up to the hour accurancy):
def num_days hi, lo
num_hours = 0
while hi > lo
num_hours += 1 if hi.workday? and !hi.holiday?
hi -= 1.hour
end
num_hours.to_f / 24
end
This uses the holidays and business_time gems.
Simple script to calculate total number of working days
require 'date'
(DateTime.parse('2016-01-01')...DateTime.parse('2017-01-01')).
inject({}) do |s,e|
s[e.month]||=0
if((1..5).include?(e.wday))
s[e.month]+=1
end
s
end
# => {1=>21, 2=>21, 3=>23, 4=>21, 5=>22, 6=>22, 7=>21, 8=>23, 9=>22, 10=>21, 11=>22, 12=>22}
There are two problems with the most popular solutions listed above:
They involve loops to count every single day between each date (meaning that performance gets worse the further apart the dates are.
They are unclear about whether they count from the beginning of the day or the end. If you count from the morning, there is one weekday between Friday and Saturday. If you count from the night, there are zero weekdays between Friday and Saturday.
After stewing over it, I propose this solution that addresses both problems. The below takes a reference date and an other date and calculates the number of weekdays between them (returning a negative number if other is before the reference date). The argument eod_base controls whether counting is done from end of day (eod) or start of day. It could be written more compactly but hopefully it's relatively easy to understand and it doesn't require gems or rails.
require 'date'
def weekdays_between(ref,otr,eod_base=true)
dates = [ref,otr].sort
return 0 if dates[0] == dates[1]
full_weeks = ((dates[1]-dates[0])/7).floor
dates[eod_base ? 0 : 1] += (eod_base ? 1 : -1)
part_week = Range.new(dates[0],dates[1])
.inject(0){|m,v| (v.wday >=1 && v.wday <= 5) ? (m+1) : m }
return (otr <=> ref) * (full_weeks*5 + part_week)
end

Resources