Reputation: 4614
I would like to split the following array by introducing a split where the difference in dates is > 10 days:
dates = [Date.parse('2017-06-26'), Date.parse('2017-07-04'), Date.parse('2017-11-30')]
#=> [Mon, 26 Jun 2017, Tue, 04 Jul 2017, Thu, 30 Nov 2017]
the result should be the following:
[[Mon, 26 Jun 2017, Tue, 04 Jul 2017], [Thu, 30 Nov 2017]]
So far I've got a very procedural method. It accepts as parameters the array to be split, the minimum difference between members of different groups, and the attribute to evaluate to get the difference. (In the case of my array of Date objects, I would leave off the last parameter, as the value used to determine diffs would just be the Date itself)
def split_by_attribute_diff array, split_size, attribute = :itself
groups = []
current_group = []
previous = current = nil
array.sort_by(&attribute).each do |e|
previous = current
current = e
if previous && current.send(attribute) - previous.send(attribute) > split_size
if current_group.count > 0
groups << current_group
current_group = []
end
end
current_group << current
end
if current_group.count > 0
groups << current_group
end
groups
end
The things I like about this method are that 1) it works, 2) the algorithmic complexity is just that of the sort_by - after the array is sorted, it is traversed just once. I guess the only thing I don't like is that it looks like it should be simpler. Is there a more Ruby-ish way to accomplish what I'm doing here?
Upvotes: 0
Views: 553
Reputation: 110725
My preference would be to use Enumerable#chunk_while (new in v2.3) or Enumerable#slice_when (new in v2.2) for this and similar problems. If, however, Ruby versions prior to 2.2 must be supported, an approach similar to the following can be used.
require 'date'
def group_em(dates)
fmt = '%Y-%m-%d'
dates.each_with_object([[]]) do |d,a|
if a.last.empty? || Date.strptime(d,fmt) <= Date.strptime(a.last.last,fmt) + 10
a.last << d
else
a << [d]
end
end
end
group_em %w| 2017-06-26 2017-07-04 2017-11-21 2017-11-30 2017-12-30 |
#=> [["2017-06-26", "2017-07-04"], ["2017-11-21", "2017-11-30"], ["2017-12-30"]]
Upvotes: 1
Reputation: 211690
It doesn't have to be hard if you properly leverage the Enumerable library in Ruby and in particular chunk_while
which is specifically for carving up an array into little chunks based on a logical test:
require 'date'
dates = %w[
2017-06-26
2017-07-04
2017-11-21
2017-11-30
2017-12-30
].map { |d| Date.parse(d) }
r = dates.chunk_while do |a,b|
a + 10 > b
end
r.to_a.map { |a| a.map { |d| d.strftime('%Y-%m-%d') } }
# => [["2017-06-26", "2017-07-04"], ["2017-11-21", "2017-11-30"], ["2017-12-30"]]
Upvotes: 4