patternrubyrailsMinor
Mapping arrays to ranges in Ruby
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:
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
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
```
# in question_set.rb
def ranged_brackets
brackets = self.score_brackets.dup
brackets.each_
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
endThe 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:
Note that this can be written in functional style. Also, you can unpack the args in a block:
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)
score_ranges = []
bracket[:min_scores].each_cons(2) do |bounds|
score_ranges << Range.new(bounds.first, bounds.last, true)
endNote 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)
endscore_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.