分组哈希数组

时间:2016-12-22 15:30:40

标签: ruby-on-rails ruby

所以我正在开发一个我有一系列哈希的项目:

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

但这似乎不起作用,我得到一个错误,表示没有将Symbol隐式转换为整数。

任何帮助,了解我出错的地方和做什么都将非常感激。

4 个答案:

答案 0 :(得分: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

它使用ActiveSupport中的Hash#except

输出:

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

答案 1 :(得分: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])

答案 2 :(得分:0)

//Runnable runnable = new Runnable() {
//        @Override
//        public void run() {
            for (int i = 0; i < data.size(); i++) {
                dbOpen();
                Map<String, Object> map = data.get(i);
                ContentValues cv = new ContentValues();
                cv.put("name", (String) map.get("name"));
                cv.put("version", (int) map.get("version"));
                cv.put("uuid", (String) map.get("uuid"));
                cv.put("measure", (String) map.get("measure"));
                database.insert("nomenclature", null, cv);
                dbClose();
            }
//        }
//    };
// new Thread(runnable).start();

这使用方法Hash#dig以及Hash#update(又名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#merge的形式,它们使用块来确定两者中存在的键的值哈希被合并。 (有关详细信息,请参阅文档。)请注意,在两个不同的级别上存在此类块。例如,如果

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 } } } } 的值。

答案 3 :(得分:-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 }

注意,这会修改输入,但我认为在转换后你对原始输入不感兴趣。

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}