2016-12-22 8 views
2

поэтому я работаю над проектом, где у меня есть массив хэшей:Группировка массив хэшей

[{: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]}} 

, но это не похоже на работу, я получаю ошибку говоря нет неявное преобразование символа в целое.

Любая помощь с пониманием, где я пошла не так и что делать, будет очень признательна.

+0

Что это нужно делать с [тэгом: рубин-на-рельсы]? Получаете ли вы это в результате какого-либо веб-запроса? Можете ли вы изменить формат веб-запроса? –

+0

Извините, я просто положил рубины на рельсы, потому что это проект рельсов. – user2320239

+1

Вы пробовали просто сделать это долгий путь? цикл через массив с '.each'. Затем вы можете обрабатывать каждый хэш отдельно и вставлять его в новый хеш. Заставьте его работать в дальнем конце. – MageeWorld

ответ

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#except из 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 
      } 
     } 
    } 
} 
+0

Спасибо за ответ, хотя когда я запустил это, я получаю ошибку undefined method 'except '. Я включил активную поддержку. – user2320239

+0

Он должен работать в Rails. В простом Ruby вы можете использовать 'require 'active_support/core_ext/hash' ' –

+0

Спасибо, извините, что это сработало, как вы заявили. – user2320239

0
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, которые используют блок для определения значений ключей, которые присутствуют в обоих хешей является слиты. (Подробнее см. В документах.) Обратите внимание, что есть такие блоки на двух уровнях разности. Если, например,

{ 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.

1

Знаю, что я опоздал с этим, но эта проблема имеет красивую рекурсивную структуру, которая заслуживает того, чтобы ее видели.

Входы - это массив хэшей и список ключей для группировки.

Для базового случая список ключей пуст. Просто преобразуйте массив хэшей в хэш-индекс с индексом.

В противном случае используйте первый ключ в списке для накопления хэша с соответствующими входными значениями в виде ключей, каждый из которых отображается в список хэшей с удаленным этим ключом. Каждый из этих списков представляет собой лишь меньший экземпляр одной и той же проблемы, используя оставшийся хвост ключей! Поэтому возвращайтесь, чтобы позаботиться о них.

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

Вот простой два лайнера

h = Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = [] }} 
ary.each { |each| h[each.delete(:year)][each.delete(:month)] << each } 

NB, это изменяет входные данные, но я предполагаю, что вы не заинтересованы в первоначальном входе после преобразования.

Значение 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} 
+0

Почему downvotes? Лучше оставить комментарий, чтобы я мог улучшить ответ. – akuhn