Merge Hashes and key gets both, old and new values

2019-05-11 09:01发布

问题:

Imagine I have an array like this

array = [4,"hello",[[3],:d,7,[:a,"seven"]]]



class Array
  def deep_collect_by_elem_type_as_hash()
    e = {}
    self.each(){|x|
      if x.is_a?(Array)
        e.merge(x.deep_collect_by_elem_type_as_hash)
      end
      if !x.is_a?(Array)
        if e.has_key?(x.class)
          e[x.class]<<x
        else
          e[x.class] = [x]
        end
      end
    }
    return e
  end

I want all my arrays to create an hash in which there are keys containing the different classes that are in my Array. Their values will be the actual elements for each class.

So it would look like this:

{Fixnum=>[4, 3, 7], String=>["hello", "seven"], Symbol=>[:d, :a]}

I want to solve the whole thing without using flatten, but doing it recrusive. Te flatten solution could look like this:

def deep_collect_by_elem_type_as_hash1()
    e = {}
    flat= self.flatten()
    flat.each(){|x|
      if e.has_key?(x.class)
        e[x.class]<<x
      else
        e[x.class] = [x]
      end
    }
    return e
  end

For those wondering why I do not want to use flatten: I still have problems fully understanding how to implement recursive methods, and therefore this is a question to give me a better understanding.

I think I somehow have to implement merge with a block, but I cannot figure out a proper block. merge(x.deep_collect_by_elem_type_as_hash(){|k,v1,v2| help}

回答1:

here we go:

a = [4,"hello",[[3],:d,7,[:a,"seven"]]]

def stuff(a)
  res = {}
  a.each do |e|
    if e.is_a?(Array)
      stuff(e).each do |k,v|
        res[k] ||= []
        v.each {|x| res[k] << x}
      end
    else
      k = e.class
      res[k] ||= []
      res[k] << e
    end
  end
  res
end

puts stuff(a).inspect

if you need to open and extend array, you can do something along the lines of:

class Array
  def stuff(a = self)
  ...
  end
end


回答2:

You can do that thus:

def group_by_class(arr, h = Hash.new { |h,k| h[k] = [] })
  arr.each { |e| e.is_a?(Array) ? group_by_class(e,h) : h[e.class] << e }
  h
end

array = [4,"hello",[[3],:d,7,[:a,"seven"]]]
group_by_class(array)
  #=> {Fixnum=>[4, 3, 7], String=>["hello", "seven"], Symbol=>[:d, :a]}

array = [4,"hello",[[3],:d,7,[:a,"seven",["b",9,[:e,["cat", {a: 0}, 5]]]]]]
group_by_class(array)
  #=> {Fixnum=>[4, 3, 7, 9, 5], String=>["hello", "seven", "b", "cat"],
  #    Symbol=>[:d, :a, :e], Hash=>[{:a=>0}]} 

You could also write:

def group_by_class(arr)
  arr.each { |e| e.is_a?(Array) ? group_by_class(e) : @h[e.class] << e }
end

@h = Hash.new { |h,k| h[k] = [] }
group_by_class(array)
@h
  #=> {Fixnum=>[4, 3, 7, 9, 5], String=>["hello", "seven", "b", "cat"],
  #    Symbol=>[:d, :a, :e], Hash=>[{:a=>0}]}