Comparing datetimes and joining when they are within 5 minutes [closed] - ruby

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I have a large array of DateTimes. For example:
[2013-06-17 19:47:12,
2013-06-17 19:40:01,
2013-06-17 19:42:53,
2013-06-17 19:12:27,
2013-06-17 19:45:42,
2013-06-17 19:14:17]... etc
What I'd like to do is iterate through the array and come up with a bunch of ranges for DateTime objects that are within 5 minutes of one another.
So, the result I would get is:
[
{range_start: 2013-06-17 19:40:01, range_end: 2013-06-17 19:47:12},
{range_start: 2013-06-17 19:12:27, range_end: 2013-06-17 19:14:17},
]
As you can see, the first object in the result set would contain all 4 DateTime objects in the example above by getting the earliest time and latest time and making a range. Likewise for the second.
Basically, what I'm trying to do is group together DateTimes that are within 5 minutes of each other, however I'm not sure how to do it without excessive recursion. For example, once I grab the first DateTime and I find another that is within 5 minutes of it, I then need to find all the other DateTime items that are within 5 minutes of the recently found DateTime.
Start at minute 42
Search 5 minutes before and after
Find another DateTime at minute 44, so now range is 42-44
Need to search 5 minutes before and after range of 42-44 (so anything from 38 to 49)
If I find something at minute 49, then range goes to 42-49
Now search radius is 38 to 54, etc...

Assuming that the time array does not include the unix epoch:
array
.sort
.unshift(Time.at(0))
.each_cons(2)
.slice_before{|t1, t2| t1 + 300 < t2}
.map{|a| min, max = a.map(&:last).minmax; {range_start: min, range_end: max}}

I wasn't going to post this as it was so close to sawa's solution. However this is a working solution whereas his has a couple of major issues.
require 'time'
array = [
'2013-06-17 19:47:12',
'2013-06-17 19:40:01',
'2013-06-17 19:42:53',
'2013-06-17 19:12:27',
'2013-06-17 19:45:42',
'2013-06-17 19:14:17'
].map { |dt| DateTime.parse(dt) }
prev_dt = nil
ranges = array.sort.slice_before do |dt|
is_new_range = prev_dt && (dt - prev_dt) * 1440 > 5
prev_dt = dt
is_new_range
end.map { |range| { range_start: range.first, range_end: range.last } }
ranges.each { |r| p r }
output
{:range_start=>#<DateTime: 2013-06-17T19:12:27+00:00 ((2456461j,69147s,0n),+0s,2299161j)>, :range_end=>#<DateTime: 2013-06-17T19:14:17+00:00 ((2456461j,69257s,0n),+0s,2299161j)>}
{:range_start=>#<DateTime: 2013-06-17T19:40:01+00:00 ((2456461j,70801s,0n),+0s,2299161j)>, :range_end=>#<DateTime: 2013-06-17T19:47:12+00:00 ((2456461j,71232s,0n),+0s,2299161j)>}

This is how I'd go about it:
require 'time'
FIVE_MINUTES = 60 * 5
timestamps = [
'2013-06-17 19:47:12',
'2013-06-17 19:40:01',
'2013-06-17 19:42:53',
'2013-06-17 19:12:27',
'2013-06-17 19:45:42',
'2013-06-17 19:14:17'
].map{ |s| Time.parse(s) }.sort
ranges = [timestamps.first .. timestamps.shift]
loop do
break if timestamps.empty?
if (timestamps.first - ranges.last.max) <= FIVE_MINUTES
ranges[-1] = (ranges.last.min .. timestamps.shift)
else
ranges << (timestamps.first .. timestamps.shift)
end
end
pp ranges.map{ |r|
Hash[
:range_start, r.min,
:range_end, r.max
]
}
Which is an array of hashes:
[
{
:range_start => 2013-06-17 19:12:27 -0700,
:range_end => 2013-06-17 19:14:17 -0700
},
{
:range_start => 2013-06-17 19:40:01 -0700,
:range_end => 2013-06-17 19:47:12 -0700
}
]
I converted the DateTime strings to Time values because you get an integer in seconds when subtracting them. That worked well when comparing to FIVE_MINUTES. If you need DateTime objects, you can convert them easily using:
pp ranges.map{ |r|
Hash[
:range_start, r.min.to_datetime,
:range_end, r.max.to_datetime
]
}
Which now looks like:
[
{
:range_start=> #<DateTime: 2013-06-17T19:12:27-07:00 ((2456462j,7947s,0n),-25200s,2299161j)>,
:range_end=> #<DateTime: 2013-06-17T19:14:17-07:00 ((2456462j,8057s,0n),-25200s,2299161j)>
},
{
:range_start=> #<DateTime: 2013-06-17T19:40:01-07:00 ((2456462j,9601s,0n),-25200s,2299161j)>,
:range_end=> #<DateTime: 2013-06-17T19:47:12-07:00 ((2456462j,10032s,0n),-25200s,2299161j)>
}
]
I sorted the array because that made it pretty straightforward to find values that were within the five minute boundaries of each other. That results in the ranges being sorted also.

Related

How to sort given array list?

list = ["HM00", "HM01", "HM010", "HM011", "HM012", "HM013", "HM014", "HM015", "HM016", "HM017", "HM018", "HM019", "HM02", "HM020", "HM021", "HM022", "HM023", "HM024", "HM025", "HM026", "HM027", "HM028", "HM029", "HM03", "HM030", "HM031", "HM032", "HM033", "HM034", "HM035", "HM036", "HM037", "HM038", "HM039", "HM04", "HM040", "HM041", "HM042", "HM043", "HM044", "HM045", "HM046", "HM047", "HM05", "HM06", "HM07", "HM08", "HM09"]
I want the display the results as ["HM00","HM01","HM002"...] but using sort method it is giving the below results
["HM00", "HM01", "HM010", "HM011", "HM012", "HM013", "HM014", "HM015", "HM016", "HM017", "HM018", "HM019", "HM02"]
If every element has a number at the end
list.sort_by { |item| item.scan(/\d*$/).first.to_i }
match that number at the end, take the first one (because scan gives you an array of results), convert it to an integer
simpler
list.sort_by { |item| item[/\d*$/].to_i }
[] already takes the first match
There is a more general solution that will work with most strings that contain groups of numbers
number = /([+-]{0,1}\d+)/;
strings = [ '2', '-2', '10', '0010', '010', 'a', '10a', '010a', '0010a', 'b10', 'b2', 'a1b10c20', 'a1b2.2c2' ]
p strings.sort_by { |item| [item.split(number).each_slice(2).map {
|x| x.size == 1 ? [ x[0], '0' ] : [ x[0], x[1] ] }].map {|y| ret = y.inject({r:[],x:[]}) { |s, z| s[:r].push [ z[0], z[1].to_r]; s[:x].push z[1].size.to_s; s }; ret[:r] + ret[:x] }.flatten
}
You can adjust number to match the types of numbers you want to use: integers, floating point, etc.
There is some extra code to sort equal numbers by length so that '10' comes before '010'.

Subtract Time from CSV using Ruby

Hi I would like to subtract time from a CSV array using Ruby
time[0] is 12:12:00AM
time[1] is 12:12:01AM
Here is my code
time_converted = DateTime.parse(time)
difference = time_converted[1].to_i - time_converted[0].to_i
p difference
However, I got 0
p time[0].to_i gives me 12
is there a way to fix this?
You can use Time#strptime to define the format of the parsed string.
In your case the string is %I:%M:%S%p.
%I = 12 hour time
%M = minutes
%S = seconds
%p = AM/PM indicator
So to parse your example:
require 'time'
time = %w(12:12:00AM 12:12:01AM)
parsed_time = time.map { |t| Time.strptime(t, '%I:%M:%S%p').to_i }
parsed_time.last - parsed_time.first
=> 1
Use the Ruby DateTime class and parse your dates into objects of that class.

Match Multiple Patterns in a String and Return Matches as Hash

I'm working with some log files, trying to extract pieces of data.
Here's an example of a file which, for the purposes of testing, I'm loading into a variable named sample. NOTE: The column layout of the log files is not guaranteed to be consistent from one file to the next.
sample = "test script result
Load for five secs: 70%/50%; one minute: 53%; five minutes: 49%
Time source is NTP, 23:25:12.829 UTC Wed Jun 11 2014
D
MAC Address IP Address MAC RxPwr Timing I
State (dBmv) Offset P
0000.955c.5a50 192.168.0.1 online(pt) 0.00 5522 N
338c.4f90.2794 10.10.0.1 online(pt) 0.00 3661 N
990a.cb24.71dc 127.0.0.1 online(pt) -0.50 4645 N
778c.4fc8.7307 192.168.1.1 online(pt) 0.00 3960 N
"
Right now, I'm just looking for IPv4 and MAC address; eventually the search will need to include more patterns. To accomplish this, I'm using two regular expressions and passing them to Regexp.union
patterns = Regexp.union(/(?<mac_address>\h{4}\.\h{4}\.\h{4})/, /(?<ip_address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/)
As you can see, I'm using named groups to identify the matches.
The result I'm trying to achieve is a Hash. The key should equal the capture group name, and the value should equal what was matched by the regular expression.
Example:
{"mac_address"=>"0000.955c.5a50", "ip_address"=>"192.168.0.1"}
{"mac_address"=>"338c.4f90.2794", "ip_address"=>"10.10.0.1"}
{"mac_address"=>"990a.cb24.71dc", "ip_address"=>"127.0.0.1"}
{"mac_address"=>"778c.4fc8.7307", "ip_address"=>"192.168.1.1"}
Here's what I've come up with so far:
sample.split(/\r?\n/).each do |line|
hashes = []
line.split(/\s+/).each do |val|
match = val.match(patterns)
if match
hashes << Hash[match.names.zip(match.captures)].delete_if { |k,v| v.nil? }
end
end
results = hashes.reduce({}) { |r,h| h.each {|k,v| r[k] = v}; r }
puts results if results.length > 0
end
I feel like there should be a more "elegant" way to do this. My chief concern, though, is performance.

Calculating the difference between durations with milliseconds in Ruby

TL;DR: I need to get the difference between HH:MM:SS.ms and HH:MM:SS.ms as HH:MM:SS:ms
What I need:
Here's a tricky one. I'm trying to calculate the difference between two timestamps such as the following:
In: 00:00:10.520
Out: 00:00:23.720
Should deliver:
Diff: 00:00:13.200
I thought I'd parse the times into actual Time objects and use the difference there. This works great in the previous case, and returns 00:0:13.200.
What doesn't work:
However, for some, this doesn't work right, as Ruby uses usec instead of msec:
In: 00:2:22.760
Out: 00:2:31.520
Diff: 00:0:8.999760
Obviously, the difference should be 00:00:8:760 and not 00:00:8.999760. I'm really tempted to just tdiff.usec.to_s.gsub('999','') ……
My code so far:
Here's my code so far (these are parsed from the input strings like "0:00:10:520").
tin_first, tin_second = ins.split(".")
tin_hours, tin_minutes, tin_seconds = tin_first.split(":")
tin_usec = tin_second * 1000
tin = Time.gm(0, 1, 1, tin_hours, tin_minutes, tin_seconds, tin_usec)
The same happens for tout. Then:
tdiff = Time.at(tout-tin)
For the output, I use:
"00:#{tdiff.min}:#{tdiff.sec}.#{tdiff.usec}"
Is there any faster way to do this? Remember, I just want to have the difference between two times. What am I missing?
I'm using Ruby 1.9.3p6 at the moment.
Using Time:
require 'time' # Needed for Time.parse
def time_diff(time1_str, time2_str)
t = Time.at( Time.parse(time2_str) - Time.parse(time1_str) )
(t - t.gmt_offset).strftime("%H:%M:%S.%L")
end
out_time = "00:00:24.240"
in_time = "00:00:14.520"
p time_diff(in_time, out_time)
#=> "00:00:09.720"
Here's a solution that doesn't rely on Time:
def slhck_diff( t1, t2 )
ms_to_time( time_as_ms(t2) - time_as_ms(t1) )
end
# Converts "00:2:22.760" to 142760
def time_as_ms( time_str )
re = /(\d+):(\d+):(\d+)(?:\.(\d+))?/
parts = time_str.match(re).to_a.map(&:to_i)
parts[4]+(parts[3]+(parts[2]+parts[1]*60)*60)*1000
end
# Converts 142760 to "00:02:22.760"
def ms_to_time(ms)
m = ms.floor / 60000
"%02i:%02i:%06.3f" % [ m/60, m%60, ms/1000.0 % 60 ]
end
t1 = "00:00:10.520"
t2 = "01:00:23.720"
p slhck_diff(t1,t2)
#=> "01:00:13.200"
t1 = "00:2:22.760"
t2 = "00:2:31.520"
p slhck_diff(t1,t2)
#=> "00:00:08.760"
I figured the following could work:
out_time = "00:00:24.240"
in_time = "00:00:14.520"
diff = Time.parse(out_time) - Time.parse(in_time)
Time.at(diff).strftime("%H:%M:%S.%L")
# => "01:00:09.720"
It does print 01 for the hour, which I don't really understand.
In the meantime, I used:
Time.at(diff).strftime("00:%M:%S.%L")
# => "00:00:09.720"
Any answer that does this better will get an upvote or the accept, of course.
in_time = "00:02:22.760"
out_time = "00:02:31.520"
diff = (Time.parse(out_time) - Time.parse(in_time))*1000
puts diff
OUTPUT:
8760.0 millliseconds
Time.parse(out_time) - Time.parse(in_time) gives the result in seconds so multiplied by 1000 to convert into milliseconds.

Simplest way to display each hour of the day in Ruby

I have a calendar screen where I want to display the hours of the day like this:
12:00am
1:00am
2:00am
..
4:00pm
5:00pm
etc.
Being a total Ruby noob, I was wondering if anyone could help me figure out the simplest way to display this.
#!/usr/bin/env ruby
# without using actual `Date` objects ...
p ["12:00am"] + (1..11).map {|h| "#{h}:00am"}.to_a +
["12:00pm"] + (1..11).map {|h| "#{h}:00pm"}.to_a
["12:00am", "1:00am", "2:00am", "3:00am", "4:00am", "5:00am", "6:00am",
"7:00am", "8:00am", "9:00am", "10:00am", "11:00am", "12:00pm", "1:00pm",
"2:00pm", "3:00pm", "4:00pm", "5:00pm", "6:00pm", "7:00pm", "8:00pm",
"9:00pm", "10:00pm", "11:00pm"]
Or using actual DateTime objects and %I:%M%p as format:
#!/usr/bin/env ruby
require "Date"
for hour in 0..23 do
d = DateTime.new(2010, 1, 1, hour, 0, 0)
p d.strftime("%I:%M%p")
end
Which would print:
"12:00AM"
"01:00AM"
"02:00AM"
"03:00AM"
"04:00AM"
"05:00AM"
"06:00AM"
"07:00AM"
"08:00AM"
"09:00AM"
"10:00AM"
"11:00AM"
"12:00PM"
"01:00PM"
"02:00PM"
"03:00PM"
"04:00PM"
"05:00PM"
"06:00PM"
"07:00PM"
"08:00PM"
"09:00PM"
"10:00PM"
"11:00PM"
You could generate these like this:
array = ['12:00am'] + (1..11).map {|h| "#{h}:00am"} + ['12:00pm'] + (1..11).map {|h| "#{h}:00pm"}
or simply write out the array (this is more efficient):
array = ["12:00am", "1:00am", "2:00am", "3:00am", "4:00am", "5:00am", "6:00am", "7:00am", "8:00am", "9:00am", "10:00am", "11:00am", "12:00pm", "1:00pm", "2:00pm", "3:00pm", "4:00pm", "5:00pm", "6:00pm", "7:00pm", "8:00pm", "9:00pm", "10:00pm", "11:00pm"]
You can then print these however you want, eg.
array.each do |el|
puts el
end

Resources