开发者

Condensing a list of events into containers by hour, including "blank" containers (Ruby)

开发者 https://www.devze.com 2023-03-16 14:20 出处:网络
Say I have a list of events ranging over several weeks that are stored as hashes, each with a DateTime :date key.

Say I have a list of events ranging over several weeks that are stored as hashes, each with a DateTime :date key.

Is there any way I can turn this into an array of events, grouped by their hour. That is, the first entry is for events in hour #1, the second entry being for events in hour #2, etc.

Sample list of events:

e = [ { :date => (DateTime), :name => "event 1" },
      { :date => (DateTime), :name => "event 2" } ]

At first I was enumerating through all of the hours in the date range, and adding the ones that match into the array as I go through the hours. But this seems very non-ruby-like to me.

I have found the Enumerable#group_by method, but this only creates the hour "containers" that have events in them and none that are skipped.

Is there a clean functional approach to this problem? If not, is there an elegant and ruby-like way instead?

Also, what about grouping them by pairs of hours instead? As in, groups of two hou开发者_JAVA技巧rs. Or an arbitrary number of hours per group.


The standard each_with_object method is your friend, if h contains DateTime keys and events as values, then:

h = {
    DateTime.new('2011-06-03 12:34')    => 'event one',
    DateTime.new('2011-06-11 22:00:00') => 'event two'
}
a = h.each_with_object(Array.new(24) { [] }) { |(k,v), a| a[k.hour - 1].push(v) }
# a is now [[], [], [], [], [], [], [], [], [], [], [], ["event one"], [], [], [], [], [], [], [], [], [], ["event two"], [], []]

And you'll end up with 24 entries in a, each entry will be an array, and the non-empty ones will contain the events. You'll want to use the block form of Array.new so that you get distinct arrays as the default values.

And if you want to group the hours into two hour spans:

a = h.each_with_object(Array.new(12) { [] }) { |(k,v), a| a[((k.hour - 1) / 2).to_i].push(v) }
# a is now [[], [], [], [], [], ["event one"], [], [], [], [], ["event two"], []]

And then you can convert the indices of a to time ranges on output with little effort.

Further generalizations are left as an exercise for the reader.

If you're stuck with 1.8 and you're not in a Rails environment, then you can get the same results with inject:

h.inject(Array.new(24) { [ ] }) { |a, (k,v)| a[k.hour - 1].push(v); a }

But the argument order for the block is different and the block has to return the array.

0

精彩评论

暂无评论...
验证码 换一张
取 消