Поэтому я работаю над проектом, в котором у меня есть массив хэшей:
[{:year=>2016, :month=>12, :account_id=>133, :price=>5},
{:year=>2016, :month=>11, :account_id=>134, :price=>3},
{:year=>2016, :month=>11, :account_id=>135, :price=>0},
{:year=>2015, :month=>12, :account_id=>145, :price=>4},
{:year=>2015, :month=>12, :account_id=>163, :price=>11}]
И в основном я хочу сжать это до формы:
{ 2016 => { 12 => { 1 => {:account_id=>133, :price=>5}},
11 => { 1 => {:account_id=>134, :price=>3},
2 => {:account_id=>135, :price=>0}}},
2015 => { 12 => { 1 => {:account_id=>145, :price=>4},
2 => {:account_id=>163, :price=>11}}}}
Но у меня настоящие проблемы с этим, на данный момент у меня есть:
data_array = data_array.group_by{|x| x[:year]}
data_array.each{|x| x.group_by{|y| y[:month]}}
Но это, похоже, не работает, я получаю сообщение об отсутствии неявного преобразования символа в целое число.
Любая помощь в понимании того, где я ошибся и что делать, будет принята с благодарностью.
4 ответа
Реорганизованное решение
Вот более длинное, но, возможно, лучшее решение с 3 вспомогательными методами:
class Array
# Remove key from array of hashes
def remove_key(key)
map do |h|
h.delete(key)
h
end
end
# Group hashes by values for given key, sort by value,
# remove key from hashes, apply optional block to array of hashes.
def to_grouped_hash(key)
by_key = group_by { |h| h[key] }.sort_by { |value, _| value }
by_key.map do |value, hashes|
hashes_without = hashes.remove_key(key)
new_hashes = block_given? ? yield(hashes_without) : hashes_without
[value, new_hashes]
end.to_h
end
# Convert array to indexed hash
def to_indexed_hash(first = 0)
map.with_index(first) { |v, i| [i, v] }.to_h
end
end
Ваш сценарий может быть записан как:
data.to_grouped_hash(:year) do |year_data|
year_data.to_grouped_hash(:month) do |month_data|
month_data.to_indexed_hash(1)
end
end
Ему не нужны Rails или Activesupport, и он возвращает:
{2015=>
{12=>
{1=>{:account_id=>145, :balance=>4}, 2=>{:account_id=>163, :balance=>11}}},
2016=>
{11=>
{1=>{:account_id=>134, :balance=>3}, 2=>{:account_id=>135, :balance=>0}},
12=>{1=>{:account_id=>133, :price=>5}}}}
Можно использовать уточнения, чтобы избежать загрязнения класса Array.
Оригинальный однострочный
# require 'active_support/core_ext/hash'
# ^ uncomment in plain ruby script.
data.group_by{|h| h[:year]}
.map{|year, year_data|
[
year,
year_data.group_by{|month_data| month_data[:month]}.map{|month, vs| [month, vs.map.with_index(1){|v,i| [i,v.except(:year, :month)]}.to_h]}
.to_h]
}.to_h
Он использует Hash # за исключением из ActiveSupport.
Он выводит:
{
2016 => {
12 => {
1 => {
:account_id => 133,
:price => 5
}
},
11 => {
1 => {
:account_id => 134,
:balance => 3
},
2 => {
:account_id => 135,
:balance => 0
}
}
},
2015 => {
12 => {
1 => {
:account_id => 145,
:balance => 4
},
2 => {
:account_id => 163,
:balance => 11
}
}
}
}
require 'active_support/core_ext/hash'
Знаю, я опаздываю с этим, но у этой проблемы есть красивая рекурсивная структура, которая заслуживает внимания.
Входные данные - это массив хэшей и список ключей для группировки.
Для базового случая список ключей пуст. Просто преобразуйте массив хешей в хеш с индексным значением.
В противном случае используйте первый ключ в списке для накопления хэша с соответствующими входными значениями в качестве ключей, каждый из которых сопоставлен со списком хэшей с удаленным ключом. Каждый из этих списков - всего лишь меньший по размеру экземпляр той же проблемы с использованием оставшегося хвоста ключей! Так что вернитесь, чтобы позаботиться о них.
def group_and_index(a, keys)
if keys.empty?
a.each_with_object({}) {|h, ih| ih[ih.size + 1] = h }
else
r = Hash.new {|h, k| h[k] = [] }
a.each {|h| r[h.delete(keys[0])].push(h) }
r.each {|k, a| r[k] = group_and_index(a, keys[1..-1]) }
end
end
Если ключ отсутствует в любом из входных хэшей, будет использоваться nil
. Обратите внимание, что эта функция изменяет исходные хэши. Если это нежелательно, позвоните a.map{|h| h.clone}
. Чтобы получить результат примера:
group_and_index(array_of_hashes, [:year, :month])
arr = [{:year=>2016, :month=>12, :account_id=>133, :price=>5},
{:year=>2016, :month=>11, :account_id=>134, :price=>3},
{:year=>2016, :month=>11, :account_id=>135, :price=>0},
{:year=>2015, :month=>12, :account_id=>145, :price=>4},
{:year=>2015, :month=>12, :account_id=>163, :price=>11}]
arr.each_with_object({}) do |g,h|
f = h.dig(g[:year], g[:month])
counter = f ? f.size+1 : 1
h.update(g[:year]=>{ g[:month]=>
{ counter=>{ account_id: g[:account_id], price: g[:price] } } }) { |_yr,oh,nh|
oh.merge(nh) { |_mon,ooh,nnh| ooh.merge(nnh) } }
end
#=> {2016=>{12=>{1=>{:account_id=>133, :price=>5}},
# 11=>{1=>{:account_id=>134, :price=>3},
# 2=>{:account_id=>135, :price=>0}}
# },
# 2015=>{12=>{1=>{:account_id=>145, :price=>4},
# 2=>{:account_id=>163, :price=>11}}
# }
# }
При этом используются методы Hash # dig и формы Hash # update (также известного как merge!
) и Hash # merge a>, которые используют блок для определения значений ключей, которые присутствуют в обоих сливаемых хэшах. (Подробности см. В документации.) Обратите внимание, что есть такие блоки на двух уровнях различия. Если, например,
{ 2016=>{ 11=>{ {1=>{:account_id=>133, :price=>5 } } } } }
{ 2016=>{ 11=>{ {2=>{:account_id=>135, :price=>0 } } } } }
Объединяются, блок будет определять значение 2016
. Это включает в себя объединение двух хешей
{ 11=>{ {1=>{:account_id=>133, :price=>5 } } } }
{ 11=>{ {2=>{:account_id=>135, :price=>0 } } } }
Который вызовет внутренний блок для определения значения 11
.
Вот простой двухстрочный
h = Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = [] }}
ary.each { |each| h[each.delete(:year)][each.delete(:month)] << each }
Обратите внимание, это изменяет ввод, но я предполагаю, что вас не интересует исходный ввод после его преобразования.
Значение h
{
2016=>{12=>[{:account_id=>133, :price=>5}], 11=>[{:account_id=>134, :price=>3}, {:account_id=>135, :price=>0}]},
2015=>{12=>[{:account_id=>145, :price=>4}, {:account_id=>163, :price=>11}]}
}
Вы можете получить доступ к значениям в h
с помощью
h[2016][11][1] # => {:account_id=>135, :price=>0}
Похожие вопросы
Новые вопросы
ruby-on-rails
Ruby on Rails - это полнофункциональная платформа веб-приложений с открытым исходным кодом, написанная на Ruby. Он следует популярной модели фреймворка MVC и известен своим подходом «соглашение поверх конфигурации» при разработке приложений.
.each
. Затем вы можете обработать каждый хеш отдельно и поместить его в новый хеш. Сначала заставьте это работать долгий путь.