简体   繁体   English

询问Ruby数组的哈希值

[英]interrogate a Ruby array of hashes

I need to group people by age in Ruby. 我需要在Ruby中按年龄分组人员。 I have their date of birth, and a method which returns their age in years. 我有他们的出生日期,以及一种多年回归年龄的方法。 So a solution like this works. 所以像这样的解决方案有效。

case
when (0..15).cover?(age_years)
  'child'
when (16..24).cover?(age_years)
  '16 to 24'
when (25..34).cover?(age_years)
  '25 to 34'
when (35..44).cover?(age_years)
  '35 to 44'
when (45..54).cover?(age_years)
  '45 to 54'
when (55..64).cover?(age_years)
  '55 to 64'
when age_years > 64
  'really old'
else
  'unknown'
end

However, I am trying to learn Ruby and am looking for a more elegant solution. 但是,我正在努力学习Ruby,并且正在寻找更优雅的解决方案。 I thought about putting the age_ranges into an array of hashes like this... 我想把age_ranges放到像这样的哈希数组中......

age_ranges = [{ name: 'child', min_age: 0, max_age: 15 },
              { name: '16 to 24', min_age: 16, max_age: 24 }]

but am at a loss as to how to interrogate this data to return the correct name where the age_years is within the appropriate ranges, or even a range like this 但我不知道如何查询这些数据以返回正确的名称,其中age_years在适当的范围内,甚至是这样的范围

age_ranges = [{ name: 'child', age_range: '0..15' },
              { name: '16 to 24', age_range: '16..24' }]

which looks neater but I have no idea if I have written gibberish as I don't know how to extract the name when the age years matches. 看起来更整洁,但我不知道我是否写了胡言乱语,因为我不知道如何在年龄匹配时提取名称。

Can someone point me in the right direction? 有人能指出我正确的方向吗?

Now that you have an map of age names and ranges (note I used range , not string as a value of age_range ), you want to search the age_ranges array of hashes for such, which value of age_range includes the age: 现在,你有年龄名称和范围的地图(注意我用的范围 ,而不是字符串的值age_range ),你要搜索的age_ranges散列这样的,它的价值的数组age_range包括年龄:

def age_ranges
  [
    { name: 'child',    age_range: 0..15 },
    { name: '16 to 24', age_range: 16..24 }
  ]
end

def find_age(age)
  age_ranges.find { |hash| hash[:age_range].include?(age) }[:name]
end

find_age(12)
#=> "child"
find_age(17)
#=> "16 to 24"

Note, that [:name] will fail if find returns nil (meaning, no matches found). 注意,如果find返回nil (意味着找不到匹配项), [:name]将失败。

To overcome it either add an infinite range as a last one in the array (I'd prefer this one, because it is simpler): 要克服它,要么添加一个无限范围作为数组中的最后一个(我更喜欢这个,因为它更简单):

def age_ranges
  [
    { name: 'child',    age_range: 0..15 },
    { name: '16 to 24', age_range: 16..24 },
    { name: 'unknown',  age_range: 25..Float::INFINITY }
  ]
end

Or handle it while fetching the age in the find_age method: 或者在find_age方法中获取年龄时处理它:

def find_age(age)
  age_ranges.each_with_object('unknown') { |hash, _| break hash[:name] if hash[:age_range].include?(age) }
end

Also, make sure to handle the negative numbers passed to the method (since age_ranges do not cover negatives): 此外,请确保处理传递给方法的负数(因为age_ranges不包括负数):

def find_age(age)
  return 'Age can not be less than 0' if age.negative?
  age_ranges.find { |hash| hash[:age_range].include?(age) }[:name]
end

PS After all these "note/make sure" I want to say that @mudasobwa's answer is the simplest way to go about it :) PS在所有这些“注意/确定”之后我想说@ mudasobwa的答案是最简单的方法:)

Use Range#=== triple equal directly, as it is supposed to be used: 使用Range#=== triple equal,因为它应该被使用:

case age_years
when 0..15 then 'child'
when 16..24 then '16 to 24'
when 25..34 then '25 to 34'
when 35..44 then '35 to 44'
when 45..54 then '45 to 54'
when 55..64 then '55 to 64'
when 64..Float::INFINITY then 'really old' # or when 64.method(:<).to_proc
else 'unknown'
end

To make case to accept floats, one should use triple-dot ranges: 要使case接受浮点数,应该使用三点范围:

case age_years
when 0...16 then 'child'
when 16...25 then '16 to 24'
when 25...35 then '25 to 34'
when 35...45 then '35 to 44'
when 45...55 then '45 to 54'
when 55...64 then '55 to 64'
when 64..Float::INFINITY then 'really old' # or when 64.method(:<).to_proc
else 'unknown'
end

Here's how I'd do it, to avoid code repetition between 16 and 64 : 这是我如何做到这一点,以避免代码重复在16和64之间:

def age_range(age, offset=4, span=10, lowest_age=16)
  i    = ((age-offset-1)/span).to_i
  min  = [i*span+offset+1, lowest_age].max
  max  = (i+1)*span + offset
  "#{min} to #{max}"
end

def age_description(age)
  case age
    when 0...16 then 'child'
    when 16..64 then age_range(age)
    when 64..999 then 'really old'
    else 'unknown'
  end
end

(0..99).each do |age|
  puts "%s (%s)" % [age_description(age), age]
end

It outputs : 它输出:

child (0)
child (1)
child (2)
child (3)
child (4)
child (5)
child (6)
child (7)
child (8)
child (9)
child (10)
child (11)
child (12)
child (13)
child (14)
child (15)
16 to 24 (16)
16 to 24 (17)
16 to 24 (18)
16 to 24 (19)
16 to 24 (20)
16 to 24 (21)
16 to 24 (22)
16 to 24 (23)
16 to 24 (24)
25 to 34 (25)
25 to 34 (26)
25 to 34 (27)
25 to 34 (28)
25 to 34 (29)
25 to 34 (30)
25 to 34 (31)
25 to 34 (32)
25 to 34 (33)
25 to 34 (34)
35 to 44 (35)
...

As a bonus, it also works with Floats (eg 15.9 and 16.0). 作为奖励,它也适用于Floats(例如15.9和16.0)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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