Group Users by Age Range in ruby

后端 未结 2 776
天命终不由人
天命终不由人 2021-01-12 23:42

I\'m trying to list the number of users by age-range:

Range  : #Users
10-14  : 16
15-21  : 120
22-29  : 312
30-40  : 12131
41-70  : 612
71-120 : 20


        
相关标签:
2条回答
  • 2021-01-12 23:58

    You want to build some SQL that looks like this:

    select count(*),
           case
               when age between 10 and 14 then '10 - 14'
               when age between 15 and 21 then '15 - 21'
               -- ...
           end as age_range
    from users
    where age between 10 and 120
    group by age_range
    

    In ActiveRecord terms, that would be:

    # First build the big ugly CASE, we can also figure out the
    # overall max and min ages along the way.
    min   = nil
    max   = nil
    cases = AGE_RANGES.map do |r|
        min = [r[:min], min || r[:min]].min
        max = [r[:max], max || r[:max]].max
        "when age between #{r[:min]} and #{r[:max]} then '#{r[:min]} - #{r[:max]}'"
    end
    
    # Then away we go...
    age_ranges = Users.select("count(*) as n, case #{cases.join(' ')} end as age_range")
                      .where(:age => min .. max)
                      .group('age_range')
                      .all
    

    That will leave you with an array of objects in age_ranges and those objects will have n and age_range methods. If you want a Hash out of that, then:

    age_ranges = Hash[age_ranges.map { |r| [r.age_range, r.n] }]
    

    That won't include ranges that don't have any people in them of course; I'll leave that as an exercise for the reader.

    0 讨论(0)
  • 2021-01-13 00:08

    I find the accepted answer to be a bit dense. Fast but hard to understand and write. Today, I came up with a slower but simpler solution. Since we are grouping ages into ranges, we can assume that we won't have values over 125

    That means that if you use a ruby filter on a grouped and counted result set, you won't iterate over more than 125 items. This will be slower than a sql range based group/count, but it was fast enough for my purposes while still relying on the DB for most of the heavy lifting. Iterating over a hash with less than 125 items doesn't seem like a big deal. Especially when the key value pairs are just ints like this:

    {
      0 => 0,
      1 => 1,
      3 => 5,
      25 => 3,
      99 => 3
    }
    

    Here's the psudo-code:

    users = User
      .where(age: (min..max))
      .group(:age)
      .count(:age)
    group = Hash.new(0)
    users.each{|age, count|
          case
          when age <= 10
            group['under 10'] += count
          when age <= 25
            group['11-25'] += count
          when age <= 40
            group['26-40'] += count
          else
            group['41+'] += count
          end
    }
    

    Note: this solution provides the count of users in a given range.

    0 讨论(0)
提交回复
热议问题