Kavincat
Kavincat

Reputation: 390

How to get specific attributes from array from hash?

I have an array of hash that needs to be sorted by imp and I want to get the specific attribute out of it.

array_of_hash is 
[
  {
      :id => "9",
      :subsystem => "xyz",
      :component => "xyz",
      :imp => "1",
      :old_serial => "55q",
      :current_serial => nil,
      :old_num => "same",
      :current_num => nil,
      :acceptable_nums => [
         "asdf",
         "qwer",
         "zxcv",
         "poiu"
    ]
},
 {
     :id => "10",
     :subsystem => "xyz",
     :component => "xyz",
     :imp => "4",
     :old_serial => "56t",
     :current_serial => nil,
     :old_num => "same",
     :current_num => nil,
     :acceptable_nums => [
         "asdf",
         "qwer",
         "zxcv",
         "poiu"
    ]
},
{
      :id => "11",
      :subsystem => "xyz",
      :component => "xyz",
      :imp => "3",
      :old_serial => "57s",
      :current_serial => nil,
      :old_num => "same",
      :current_num => nil,
      :acceptable_nums => [
        "asdf",
        "qwer",
        "zxcv",
        "poiu"
    ]
},
  {
     :id => "14",
     :subsystem => "xyz",
     :component => "xyz",
     :imp => "2",
     :old_serial => "58r",
     :current_serial => nil,
     :old_num => "same",
     :current_num => nil,
     :acceptable_nums => [
        "asdf",
        "qwer",
        "zxcv",
        "poiu"
    ]
}
]

First step, sorting

array_of_hash.sort_by {|hash| hash[:imp].to_i}

Then i want specific attribute

Desired output with some condition

{
      :imp => "1-4",  #It should be range
      :old_serial => "55q,56r,57s,58t",  #old_serial number should be separated with comma respectively 
      :old_num => "same",
      :acceptable_nums => [
         "asdf",
         "qwer",
         "zxcv",
         "poiu"
    ]
}

I am not able to figure out how to do this.

Upvotes: 0

Views: 3003

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110675

imps, old_serials = array_of_hash.map { |h| [h[:imp], h[:old_serial]] }.
                                  sort_by(&:first).
                                  transpose
  #=> [["1", "2", "3", "4"], ["55q", "58r", "57s", "56t"]]

{ imp: "%d-%d" % imps.map(&:to_i).minmax, old_serial: old_serials.join(',') }.
  merge(array_of_hash.first.select { |k,_| [:old_num, :acceptable_nums].include?(k) })
  #=> {:imp=>"1-4", :old_serial=>"55q,58r,57s,56t", :old_num=>"same",
  #    :acceptable_nums=>["asdf", "qwer", "zxcv", "poiu"]} 

Note

array_of_hash.first.select { |k,_| [:old_num, :acceptable_nums].include?(k) }
  # => {:old_num=>"same", :acceptable_nums=>["asdf", "qwer", "zxcv", "poiu"]}

Upvotes: 1

David Gross
David Gross

Reputation: 1873

It's a bit easier to read if you break this up in steps.

old_serial_numbers = array_of_hash.collect {|h| h[:old_serial]}.join(',')
reject_hash_keys = [:old_serial, :id, :subsystem, :component, :current_num, :current_serial]

clean_hash = array_of_hash.map do |hash| 
  hash.reject! {|k| reject_hash_keys.include? k }
  hash.merge(old_serial: old_serial_numbers)
end

clean_hash.sort_by {|hash| hash[:imp].to_i}

Upvotes: 0

Eric Duminil
Eric Duminil

Reputation: 54223

You'll need a combination of sort_by, group_by and map :

p array_of_hash.sort_by { |h| h[:imp] }
               .group_by{ |h| h.values_at(:acceptable_nums, :old_num) }
               .map{ |(old_num, nums), hashes|
  {
    imp: hashes.map{ |h| h[:imp].to_i },
    old_serial: hashes.map{ |h| h[:old_serial] }.join(','),
    old_num: old_num,
    acceptable_nums: nums
  }
}
# [{:imp=>[1, 2, 3, 4], :old_serial=>"55q,58r,57s,56t", :old_num=>["asdf", "qwer", "zxcv", "poiu"], :acceptable_nums=>"same"}]

The output is an array of hashes. There will be one hash for each unique pair of old_num and acceptable_nums. In your example, all the hashes had this same pair, so only one hash is outputted.

As for the desired conversion from [1,2,3,4] to "1-4", the documentation for slice_when does just that :

a = [1,2,4,9,10,11,12,15,16,19,20,21]
b = a.slice_when {|i, j| i+1 != j }
p b.to_a #=> [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
p c #=> [[1, 2], [4], "9-12", [15, 16], "19-21"]
d = c.join(",")
p d #=> "1,2,4,9-12,15,16,19-21"

Upvotes: 1

Related Questions