按月和年将Ruby日期日期分组为哈希

时间:2011-04-12 18:31:43

标签: ruby arrays date hash

假设我有一个Date的Ruby数组:

2011-01-20
2011-01-23
2011-02-01
2011-02-15
2011-03-21

创建一个按年和月分组Date元素的简单和Ruby方式是什么,如:

{
    2011 => {
        1   => [2011-01-20, 2011-01-23],
        2   => [2011-02-01, 2011-02-15],
        3  => [2011-03-21],
    }
}

我可以通过迭代所有内容并提取数年,数月等来实现这一目标,然后将它们合并。

Ruby为Arrays和Hashes提供了很多方法和块,必须有一个更简单的方法吗?

3 个答案:

答案 0 :(得分:17)

require 'date'

dates = [
  '2011-01-20',
  '2011-01-23',
  '2011-02-01',
  '2011-02-15',
  '2011-03-21'
].map{|sd| Date.parse(sd)}

Hash[
  dates.group_by(&:year).map{|y, items|
    [y, items.group_by{|d| d.strftime('%B')}]
  }
]
#=> {2011=>{"January"=>[#<Date: 2011-01-20 (4911163/2,0,2299161)>, #<Date: 2011-01-23 (4911169/2,0,2299161)>], "February"=>[#<Date: 2011-02-01 (4911187/2,0,2299161)>, #<Date: 2011-02-15 (4911215/2,0,2299161)>], "March"=>[#<Date: 2011-03-21 (4911283/2,0,2299161)>]}} 

我注意到您已将月份名称更改为数字,因此您可能希望将上面的d.strftime('%B')替换为d.month或其他任何内容。

以下是逐步说明:

你基本上想要两级分组:第一级逐级,逐级逐级。 Ruby有非常有用的方法group_by,它通过给定的表达式(块)对元素进行分组。所以:第一部分是按year分组原始数组:

hash_by_year = dates.group_by(&:year)
# => {2011=>[#<Date: 2011-01-20 (4911163/2,0,2299161)>, #<Date: 2011-01-23 (4911169/2,0,2299161)>, #<Date: 2011-02-01 (4911187/2,0,2299161)>, #<Date: 2011-02-15 (4911215/2,0,2299161)>, #<Date: 2011-03-21 (4911283/2,0,2299161)>]}

这给了我们第一级:键是年,值是给定年份的日期数组。但是我们仍然需要对第二级进行分组:这就是为什么我们按年份哈希进行映射 - 将其值分组为month。让我们开始忘记strftime并说我们按d.month进行分组:

hash_by_year.map{|year, dates_in_year|
  [year, dates_in_year.group_by(&:month)]
}
# => [[2011, {1=>[#<Date: 2011-01-20 (4911163/2,0,2299161)>, #<Date: 2011-01-23 (4911169/2,0,2299161)>], 2=>[#<Date: 2011-02-01 (4911187/2,0,2299161)>, #<Date: 2011-02-15 (4911215/2,0,2299161)>], 3=>[#<Date: 2011-03-21 (4911283/2,0,2299161)>]}]]

这样我们就得到了第二级分组。现在我们有一个哈希,它的键是几个月,而不是一年中所有日期的数组,而是指定给定月份的日期数组。

我们遇到的唯一问题是map返回一个数组而不是哈希值。这就是为什么我们通过Hash[]“围绕”整个表达式,它在对象数组中产生哈希,在我们的案例对中[year, hash_of_dates_by_month]

很抱歉,如果解释听起来令人困惑,我发现因为嵌套而难以解释功能表达而非命令。 :(

答案 1 :(得分:1)

这让你非常接近,你只需要将数字月份数字更改为文本月份名称:

dates = %w(
2011-01-20
2011-01-23
2011-02-01
2011-02-15
2011-03-21
)

grouped = dates.inject({}) do |ret, date|
  y,m,d = date.split('-')
  ret[y] ||= {}
  # Change 'm' into month name here
  ret[y][m] ||= []
  ret[y][m] << date
  ret
end

puts grouped.inspect

答案 2 :(得分:1)

dates = %w(
2011-01-20
2011-01-23
2011-02-01
2011-02-15
2011-03-21
)
hash = {}
dates.each do |date|
   year, month = date.strftime('%Y,%B').split(',')
   hash[year] ||= {}
   hash[year][month] = hash[year][month].to_a << date
end