简体   繁体   中英

create date range array from a list of dates using ruby

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM