HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyrailsMinor

Mapping arrays to ranges in Ruby

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
rangesmappingarraysruby

Problem

I have a simple Rails app, which is used to run some clinical surveys. Participants answer sets of questions (multiple-choice, valued 1-5), and, within each set, the answers are summed up and the resulting score placed into to certain score brackets, which are different for each set of questions, and for each participant's age group.

The brackets are based on empirical research, so there's no way to derive the relevant bracket from the score itself; the brackets are essentially an opaque lookup table.

In hard-coded Ruby, one could write it like so (not actual values:

def find_bracket(age, score)
  case age
  # minimum age is 18
  when 18...25
    case score
    # minimum score is 10
    when 10...15 then "bracket A"
    when 16...28 then "bracket B"
    when 29...37 then "bracket C"
    else "bracket D" # score of 38 or more
    end
  when 26...40
     # ... another nested case switch ...
  else # 41 or older
    # ...
  end
end


The bracket names are the same ("bracket A" to "bracket D" in this faked example) for each age range, but the score ranges they cover are different.

Since I really don't want to hardcode all that, I've implemented it as a serialized score_brackets attribute on the QuestionSet model like so:

question_set_a.score_brackets #=> [
  {
    min_age: 18,
    min_scores: [10, 16, 29, 38]
  },
  {
    min_age: 26,
    min_scores: [10, 15, 31, 42]
  },
  {
    min_age: 41,
    min_scores: [10, 17, 22, 36]
  }
]


The structure seems neat; everything can seemingly be derived from it (the bracket labels are, as mentioned, constant). There are other ways of doing it of course, but bear with me - my question concerns the code below.

Since the score_brackets attribute is YAML serialized, it can't handle Ruby's Range objects very well, but those seem ideal for this purpose. So I wrote the following, which, frankly, isn't pretty:

```
# in question_set.rb
def ranged_brackets
brackets = self.score_brackets.dup
brackets.each_

Solution

You repeat this pattern a couple of times:

score_ranges = []
bracket[:min_scores].each_cons(2) do |bounds|
  score_ranges << Range.new(bounds.first, bounds.last, true)
end


Note that this can be written in functional style. Also, you can unpack the args in a block:

score_ranges = bracket[:min_scores].each_cons(2).map { |from, to| from...to }


Other than this, your code looks ok to me. Conceptually you should use a binary search - O(log n) in time - but, with those small data sizes, a O(n) detect is just fine. Anyway, some references on binary search:

  • http://ruby-doc.org/core-2.0/Array.html#method-i-bsearch



  • https://github.com/tyler/binary_search



  • https://github.com/dtinth/bsearch

Code Snippets

score_ranges = []
bracket[:min_scores].each_cons(2) do |bounds|
  score_ranges << Range.new(bounds.first, bounds.last, true)
end
score_ranges = bracket[:min_scores].each_cons(2).map { |from, to| from...to }

Context

StackExchange Code Review Q#25959, answer score: 4

Revisions (0)

No revisions yet.