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

发布于 2024-10-31 20:30:47 字数 432 浏览 1 评论 0原文

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

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

创建按年和月对日期元素进行分组的哈希的简单且 Ruby 风格的方法是什么,例如:

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

我可以这样做通过迭代所有内容并提取年、月等,然后将它们组合起来。

Ruby 为数组和哈希提供了如此多的方法和块,一定有更简单的方法吗?

Let's say I had a Ruby Array of Dates like:

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

What would be an easy and Ruby-esque way of creating a Hash that groups the Date elements by year and then month, like:

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

I can do this by iterating over everything and extracting years, months and so on, then comining them.

Ruby offers so many methods and blocks for Arrays and Hashes, there must be an easier way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

黑白记忆 2024-11-07 20:30:47
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,它可以按给定的表达式(块)对元素进行分组。所以:第一部分是按年份对原始数组进行分组:

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)>]}

这给了我们第一层:键是年份,值是给定年份的日期数组。但我们仍然需要对第二个级别进行分组:这就是我们按年映射哈希的原因 - 按对其值进行分组。首先,让我们忘记 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]

抱歉,如果解释听起来令人困惑,我发现由于嵌套,解释函数表达式比命令式更难。 :(

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)>]}} 

I noticed you have changed month names into numbers, so you may want to replace d.strftime('%B') above with d.month or whatever else.

Here's a step-by-step explanation:

You essentially want two-level grouping: first level by year, second by month. Ruby has very useful method group_by, which groups elements by given expression (a block). So: first part is grouping original array 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)>]}

That gives us first level: keys are years, values arrays of dates with given year. But we still need to group the second level: that's why we map by-year hash - to group its values by month. Let's for start forget strftime and say that we're grouping by 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)>]}]]

That way we got our second level grouping. Instead of array of all dates in a year, now we have hash whose keys are months, and values arrays of dates for a given month.

The only problem we have is that map returns an array and not a hash. Thats why we "surround" whole expression by Hash[], which makes a hash out of array of pairs, in our case pairs [year, hash_of_dates_by_month].

Sorry if the explanation sounds confusing, I found harder to explain functional expressions than imperative, because of the nesting. :(

荭秂 2024-11-07 20:30:47

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

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

This gets you pretty close, you just need to change the numerical month number into a textual month name:

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
秋千易 2024-11-07 20:30:47
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
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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文