简体   繁体   中英

Rails hash combine by their value

I have a questions list, and I need to separate them. The relationship is

Question_set has_many questions
BookVolume has_many questions
Subject has_many  book_volumes
Publisher has_many subjects
Section has_many :questions

Now I only put questions and their relative model id , name into hash inside an array .

data = []
question_set.questions.each do |q|
    data << {publisher: {id: q.publisher.id, name: q.publisher.name}, subject: {id: q.book_volume.subject.id, name: q.book_volume.subject.name}, volume: {id: q.book_volume_id, name: q.book_volume.name}, chapter: [{id: q.section_id, name: q.section.name}]}  
end

Therefore, the data basically will be

>>data

[
    {
        :publisher => {
              :id => 96,
            :name => "P1"
        },
          :subject => {
              :id => 233,
            :name => "S1"
        },
           :volume => {
              :id => 1136,
            :name => "V1"
        },
          :chapter => [
            {
                  :id => 16155,
                :name => "C1"
            }
        ]
    },
       {
        :publisher => {
              :id => 96,
            :name => "P1"
        },
          :subject => {
              :id => 233,
            :name => "S1"
        },
           :volume => {
              :id => 1136,
            :name => "V1"
        },
          :chapter => [
            {
                  :id => 16158,
                :name => "C2"
            }
        ]
    }
]

However, I want the chapter to be combined if they got the same publisher , subject and volume So, in this case, it will be

>>data 

[
    {
        :publisher => {
              :id => 96,
            :name => "P1"
        },
          :subject => {
              :id => 233,
            :name => "S1"
        },
           :volume => {
              :id => 1136,
            :name => "V1"
        },
          :chapter => [
            {
                  :id => 16155,
                :name => "C2"
            },
            {
                  :id => 16158,
                :name => "C2"
            }
        ]
    }
]

Code

def group_em(data)
  data.group_by { |h| [h[:publisher], h[:subject], h[:volume]] }.
       map do |k,v|
         h = { publisher: k[0], subject: k[1], volume: k[2] }
         h.update(chapters: v.each_with_object([]) { |f,a|
           a << f[:chapter] }.flatten)
       end
end

Example

Let data equal the array of hashes (the first array above).

group_em(data)
  #=> [{:publisher=>{:id=>96, :name=>"P1"},
  #     :subject=>{:id=>233, :name=>"S1"},
  #     :volume=>{:id=>1136, :name=>"V1"},
  #     :chapters=>[{:id=>16155, :name=>"C1"}, {:id=>16158, :name=>"C2"}]
  #    } 
  #   ] 

Here data contains only two hashes and those hashes have the same values for the keys :publisher , :subject and :volume . This code allows the array to have any number of hashes, and will group them by an array of the values of those three keys, producing one hash for each of those groups. Moreover, the values of the key :chapters are arrays containing a single hash, but this code permits that array to contain multiple hashes. (If that array will always have exactly one hash, consider making the value of :chapters the hash itself rather than an array containing that hash.)

Explanation

See Enumerable#group_by and Hash#update (aka Hash#merge! ).

The steps are as follows.

h = data.group_by { |h| [h[:publisher], h[:subject], h[:volume]] }
  #=> {
  #    [{:id=>96, :name=>"P1"},
  #     {:id=>233, :name=>"S1"},
  #     {:id=>1136, :name=>"V1"}
  #    ]=>[{:publisher=>{:id=>96, :name=>"P1"},
  #         :subject=>{:id=>233, :name=>"S1"},
  #         :volume=>{:id=>1136, :name=>"V1"},
  #         :chapter=>[{:id=>16155, :name=>"C1"}]
  #        },
  #        {:publisher=>{:id=>96, :name=>"P1"},
  #         :subject=>{:id=>233, :name=>"S1"},
  #         :volume=>{:id=>1136, :name=>"V1"},
  #         :chapter=>[{:id=>16158, :name=>"C2"}]
  #        }
  #       ]
  #   } 

The first key-value pair is passed to map 's block and the block variables are assigned.

k,v = h.first
  #=> [[{:id=>96, :name=>"P1"}, {:id=>233, :name=>"S1"}, {:id=>1136, :name=>"V1"}],
  #   [{:publisher=>{:id=>96, :name=>"P1"}, :subject=>{:id=>233, :name=>"S1"},
  #     :volume=>{:id=>1136, :name=>"V1"}, :chapter=>[{:id=>16155, :name=>"C1"}]},
  #    {:publisher=>{:id=>96, :name=>"P1"}, :subject=>{:id=>233, :name=>"S1"},
  #     :volume=>{:id=>1136, :name=>"V1"}, :chapter=>[{:id=>16158, :name=>"C2"}]}]]
k #=> [{:id=>96, :name=>"P1"}, {:id=>233, :name=>"S1"}, {:id=>1136, :name=>"V1"}]
v #=> [{:publisher=>{:id=>96, :name=>"P1"},
  #     :subject=>{:id=>233, :name=>"S1"},
  #     :volume=>{:id=>1136, :name=>"V1"},
  #     :chapter=>[{:id=>16155, :name=>"C1"}]},
  #    {:publisher=>{:id=>96, :name=>"P1"},
  #     :subject=>{:id=>233, :name=>"S1"},
  #     :volume=>{:id=>1136, :name=>"V1"},
  #     :chapter=>[{:id=>16158, :name=>"C2"}]}] 

and the block calculation is performed.

h = { publisher: k[0], subject: k[1], volume: k[2] }
  #=> {:publisher=>{:id=>96, :name=>"P1"},
  #    :subject=>{:id=>233, :name=>"S1"},
  #    :volume=>{:id=>1136, :name=>"V1"}
  #   } 
a = v.each_with_object([]) { |f,a| a << f[:chapter] }
  #=> [[{:id=>16155, :name=>"C1"}], [{:id=>16158, :name=>"C2"}]] 
b = a.flatten
  #=> [{:id=>16155, :name=>"C1"}, {:id=>16158, :name=>"C2"}]
h.update(chapters: b)
  #=> {:publisher=>{:id=>96, :name=>"P1"},
  #    :subject=>{:id=>233, :name=>"S1"},
  #    :volume=>{:id=>1136, :name=>"V1"},
  #    :chapters=>[{:id=>16155, :name=>"C1"}, {:id=>16158, :name=>"C2"}]
  #   } 

Hash#merge could be used in place of Hash#update .

How about:

    data = {}

    question_set.questions.each do |q|
      key = "#{q.publisher.id}:#{q.book_volume.subject.id}:#{q.book_volume_id}"
      if data[key].present?
        data[key][:chapter] << {id: q.section_id, name: q.section.name}
      else
        data[key] = {publisher: {id: q.publisher.id, name: q.publisher.name}, subject: {id: q.book_volume.subject.id, name: q.book_volume.subject.name}, volume: {id: q.book_volume_id, name: q.book_volume.name}, chapter: [{id: q.section_id, name: q.section.name}]}  
      end
    end

    result = data.values

use the combination of publisher'id, subject'id and volume'id as a unique key to combine your data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM