How can I pattern match on an array of hashes in Ruby 2.7?

367 views Asked by At

I'm experimenting with the new pattern matching feature in Ruby 2.7 by pattern matching against a JSON document. I would like to use it to match only on array elements with certain characteristics. In my case, there's an array of region data stored as hashes that each have an id. I only need to match against the hash with an id of 10 as seen below:

require 'net/http'
require 'json'

HOSPITAL_DATA_URI = URI("https://www.dph.illinois.gov/sitefiles/COVIDHospitalRegions.json?nocache=1").freeze

hospital_data = JSON.parse(Net::HTTP.get(HOSPITAL_DATA_URI), symbolize_names: true)
region_values = hospital_data[:regionValues]

case region_values
  in [{id: 10, **unimportant_attributes}, **non_region_10_hashes]
    puts 'We found region 10!'
else
  puts 'aw shucks'
end

puts "...but Region 10 does exist" if region_values.find {|region| region[:id] == 10}

#=> aw shucks
#=> ...but Region 10 does exist

I expect it to print "we found region 10!" but it does not.

Is it possible to use Ruby pattern matching to do this?

2

There are 2 answers

1
Cary Swoveland On

We find that the given code retrieves the following value for region_values:

region_values
  #=> [
    {:region=>"Region01", :id=>1, :region_description=>"1 - Rockford Region Hospitals",
     :ICUAvail=>126, :ICUCapacity=>234, :VentsAvailable=>296, :VentsCapacity=>359},
    {:region=>"Region02", :id=>2, :region_description=>"2 - Peoria Region Hospitals",
     :ICUAvail=>140, :ICUCapacity=>304, :VentsAvailable=>398, :VentsCapacity=>496},
    {:region=>"Region03", :id=>3, :region_description=>"3 - Springfield Region Hospitals",
     :ICUAvail=>77, :ICUCapacity=>150, :VentsAvailable=>350, :VentsCapacity=>410},
    {:region=>"Region04", :id=>4, :region_description=>"4 - Edwardsville Region Hospitals",
     :ICUAvail=>61, :ICUCapacity=>113, :VentsAvailable=>134, :VentsCapacity=>158},
    {:region=>"Region05", :id=>5, :region_description=>"5 - Marion Region Hospitals",
     :ICUAvail=>64, :ICUCapacity=>107, :VentsAvailable=>294, :VentsCapacity=>309},
    {:region=>"Region06", :id=>6, :region_description=>"6 - Champaign Region Hospitals",
     :ICUAvail=>70, :ICUCapacity=>162, :VentsAvailable=>260, :VentsCapacity=>287},
    {:region=>"Region07", :id=>7, :region_description=>"7 - Southwest Suburbs Region Hospitals",
     :ICUAvail=>175, :ICUCapacity=>484, :VentsAvailable=>444, :VentsCapacity=>589},
    {:region=>"Region08", :id=>8, :region_description=>"8 - West Suburbs Region Hospitals",
     :ICUAvail=>176, :ICUCapacity=>480, :VentsAvailable=>319, :VentsCapacity=>550},
    {:region=>"Region09", :id=>9, :region_description=>"9 - Northwest Suburbs Region Hospitals",
     :ICUAvail=>238, :ICUCapacity=>452, :VentsAvailable=>438, :VentsCapacity=>533},
    {:region=>"Region10", :id=>10, :region_description=>"10 - Northeast Suburbs Region Hospitals",
     :ICUAvail=>112, :ICUCapacity=>206, :VentsAvailable=>190, :VentsCapacity=>234},
    {:region=>"Region11", :id=>11, :region_description=>"11 - City of Chicago Region Hospitals",
     :ICUAvail=>395, :ICUCapacity=>1064, :VentsAvailable=>1392, :VentsCapacity=>1803}
]

Before posting this I was unaware of the new pattern matching feature in Ruby v2.7. After reading this article I don't really see how this applies to your problem (though the feature itself is interesting and seems potentially useful). You could write:

region_values.each do |h|
  puts case h
  in {id: 10}
    'We found region 10!'
  else
    'aw shucks'
  end
end
aw shucks
aw shucks
aw shucks
aw shucks
aw shucks
aw shucks
aw shucks
aw shucks
aw shucks
We found region 10!
aw shucks

or

found = region_values.find do |h|
  case h
  in {id: 10}
    true
  end
end
puts found ? 'We found region 10!' : 'aw shucks'
We found region 10!

but neither is a very interesting application of the new feature.

1
Brian Kung On

u/zvero_kha has an answer here. Basically what I'm looking for is called a "find pattern" and has only just recently been merged into Ruby. It's expected to be in the Ruby 3.0 release. How you would use it in my case is:

case region_values
  # Note the "splat-like" asterisks. These are the "find patterns."
  in [*, {id: 10} => r10, {id: 11} => r11, *]
    puts "Region 10: #{r10}"
    puts "Region 11: #{r11}"
    # Note that both r10 and r11 are available in this block of code here
else
  puts 'aw shucks'
end

Matz's explanation for the use of these 'find patterns' is quite useful.