What is the best way to create an array of hashes with date ranges from a list of dates. For example: If I have a list of dates in an array as below:
['12/11/2014','13/11/2014','14/11/2014','24/11/2014','25/11/2014','26/11/2014','27/11/2014','28/11/2014','29/11/2014','04/12/2014','05/12/2014','06/12/2014','07/12/2014','24/12/2014','25/12/2014','26/12/2014', '28/12/2014', '30/12/2014']
I am trying to get array of date ranges hashes something like below:
[{:from => '12/11/2014', :to => '14/11/2014'}, {:from => '24/11/2014', :to => '29/11/2014'}, {:from => '04/12/2014', :to => '07/12/2014'}, {:from => '24/12/2014', :to => '26/12/1014'}, {:from => '28/12/2014', :to => '28/12/2014'}, {:from => '30/12/2014', :to => '30/12/2014'}]
Anything available in Ruby to generate something like this ? Thanks.
Enumerable has the slice_before
method which can do what you want, with the addition of some added code. This is from the documentation :
If the block needs to maintain state over multiple elements, local variables can be used. For example, three or more consecutive increasing numbers can be squashed as follows:
a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")
#=> "0,2-4,6,7,9"
Obviously it isn't the complete answer, but I am not at my computer which has code that will do exactly what you want.
require 'date'
array = ['12/11/2014','13/11/2014','14/11/2014','24/11/2014','25/11/2014','26/11/2014','27/11/2014','28/11/2014','29/11/2014','04/12/2014','05/12/2014','06/12/2014','07/12/2014','24/12/2014','25/12/2014','26/12/2014', '28/12/2014', '30/12/2014']
dates = array.map { |datestr| [datestr, Date.parse(datestr)] }
prev = nil
ranges = dates.slice_before { |datestr, date|
((prev ? date - prev : 1) != 1).tap { prev = date }
}.map { |dates|
{ from: dates[0][0], to: dates[-1][0] }
}
It is a bit more complicated than it needs to be because you're using date strings instead of dates. Also, it might be better to use an array of Range
objects than of hashes:
dates = array.map(&Date.method(:parse))
prev = nil
pp dates.slice_before { |date|
((prev ? date - prev : 1) != 1).tap { prev = date }
}.map { |dates| dates[0]..dates[-1] }
One way to do this is to use an enumerator. Suppose you have an array:
a = %w[a b d e f i k l m n o t]
#=> ["a", "b", "d", "e", "f", "i", "k", "l", "m", "n", "o", "t"]
then
arr = [[a.first]]
enum = a[1..-1].to_enum
loop do
e = enum.next
last = arr.last
if last.last.succ == e
last << e
else
arr << [e]
end
end
arr
#=> [["a", "b"], ["d", "e", "f"], ["i"], ["k", "l", "m", "n", "o"], ["t"]]
Enumerator#next raises a StopIteraration
exception when an attempt is made to go beyond the last element of the enumerator enum
. Kernel#loop handles the exception by breaking out of the loop.
In your application, a
will be a sorted array of Date
objects. You can use Enumerable#map and the class method Date::parse to convert your strings to Date
objects (and sort if necessary). Note that this works with any collection of objects that respond to succ
, Date
objects being one ( Date#succ ).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.