Reputation: 65
I have a long if else
statement:
rnd = rand(1..1000)
if rnd >= 600
0
elsif rnd < 600 && rnd >= 350
1
elsif rnd < 350 && rnd >= 270
2
elsif rnd < 270 && rnd >= 200
3
elsif rnd < 200 && rnd >= 150
4
elsif rnd < 150 && rnd >= 100
5
elsif rnd < 100 && rnd >= 80
6
elsif rnd < 80 && rnd >= 50
7
elsif rnd < 50 && rnd >= 30
8
else
9
end
I would like to shorten it. Is it possible? My rubocop swears at this long method.
Upvotes: 1
Views: 127
Reputation: 110675
MX = 1000
LIMITS = [600, 350, 270, 200, 150, 100, 80, 50, 30, 0]
The required index can be computed as follows.
def doit
rnd = rand(1..MX)
LIMITS.index { |n| n <= rnd }
end
doit
#=> 5
In this example rnd #=> 117
.
If this must be repeated many times, and speed is paramount, you could do the following.
LOOK_UP = (1..MX).each_with_object({}) do |m,h|
h[m] = LIMITS.index { |n| n <= m }
end
#=> {1=>9, 2=>9,..., 29=>9,
# 30=>8, 31=>8,..., 49=>8,
# ...
# 600=>0, 601=>0,..., 1000=>0}
Then simply
def doit
LOOK_UP[rand(1..MX)]
end
doit
#=> 3
In this example rand(1..MX) #=> 262
.
If speed were paramount but MX
were so large that the previous approach would require excessive memory, you could use a binary search.
def doit
rnd = rand(1..MX)
LIMITS.bsearch_index { |n| n <= rnd }
end
doit
#=> 5
In this example rnd #=> 174
.
See Array#bsearch_index. bsearch_index
returns the correct index in O(log n), n being LIMITS.size)
. bsearch_index
requires the array on which it operates to be ordered.
Upvotes: 2
Reputation: 3260
Great answers already! Just chiming in since I had a suspicion that ruby could handle this with a case
statement, and it appears to be able to do so:
rnd = rand(1..1000)
case rnd
when 600.. then 0
when 350...600 then 1
when 270...350 then 2
...
else 9
end
Regardless of the approach taken, you're going to have to specify the ranges somewhere, so I think using something like a case statement is appropriate here (sorry! It doesn't shorten the code more than a few lines). Using a hash would also be a great approach (and might allow you to move the hash elsewhere), as other commenters have already shown.
It's worth mentioning, with ruby ranges, ..
means that the range is inclusive and includes the last value (1..10 includes the number 10), and ...
means the range is exclusive where it does not include the last value.
The top case 600..
is an endless range, which means it will match anything greater than 600. (That functionality was added in ruby 2.6)
Upvotes: 5
Reputation: 343
You can simplify your conditions by using only the lower bound.
And you can avoid repeting elsif
because it is cumbersome
rnd = rand(1..1000)
lower_bounds = {
600 => 0,
350 => 1,
270 => 2,
200 => 3,
150 => 4,
100 => 5,
80 => 6,
50 => 7,
30 => 8,
0 => 9,
}
lower_bounds.find { |k, _| k <= rnd }.last
Upvotes: 2
Reputation: 106802
I would start with something like this:
RANGES = {
(0...30) => 9,
(30...50) => 8,
(50...80) => 7,
# ...
(350...600) => 1,
(600...1000) => 0
}
rnd = rand(1..1000)
RANGES.find { |k, _| k.cover?(rnd) }.last
Upvotes: 7