Сгруппируйте рубиновый массив дат по месяцам и годам в хэш - PullRequest
2 голосов
/ 12 апреля 2011

Допустим, у меня был Ruby Array, равный Date s, например:

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

Какой был бы простой и похожий на Ruby способ создания хэша, который группирует элементы Date по годам и месяцам?Например:

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

Я могу сделать это, повторяя все и извлекая годы, месяцы и т. д., затем объединяя их.

Ruby предлагает так много методов и блоков для массивов и хэшей, должен быть более простой способ?

Ответы [ 3 ]

17 голосов
/ 12 апреля 2011
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 голос
/ 12 апреля 2011
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
1 голос
/ 12 апреля 2011

Это довольно близко, вам просто нужно изменить номер месяца в текстовое название месяца:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...