Mason
Mason

Reputation: 121

Each loop inside Case for Ruby

I'm trying to dynamically generate a case statement based on an array of values. For example let's say I have an array of ranges

[1..3,4..6,7..20,21..38]

and I want to write a dynamic case statement that returns the first number of whatever range

case n
  ranges.each do |r|
  when r
    r.first
  end
end

Is this possible, or will I have to find another way to do it (my actual code is more complex)?

Upvotes: 1

Views: 520

Answers (5)

3limin4t0r
3limin4t0r

Reputation: 21160

In addition to #detect (or #find) with #include? from Jagdeep Singhs answer you can also use the case equality operator (Range#===). This operator is used by the case statement to compare the input value with the scenario's you're providing.

ranges.find { |range| range === n }.first

Keep in mind both #detect and #find return nil if no value can be found. This means you might want to use the safe navigation operator (}&.first) to prevent a no method exception of #first on nil if the value can't be found.

Upvotes: 1

iGian
iGian

Reputation: 11203

My two cents..

ranges = [1..3,4..6,7..20,21..38]

num = 15

ranges.bsearch { |range| range.member? num }.begin

Upvotes: 0

Kimmo Lehto
Kimmo Lehto

Reputation: 6041

Well, this works, but is kind of pointless and thread unsafe:

def get_range(n)
  ranges = [1..3,4..6,7..20,21..38]

  case n
  when 3
    # special case
    199
  when ->(x) { @_get_range = ranges.find { |r| r.cover?(x) } }
    @_get_range.first
  else
    0
  end
ensure
  remove_instance_variable(:@_get_range) if instance_variable_defined?(:@_get_range)
end

get_range(3) # => 199
get_range(5) # => 4
get_range(50) # => 0

You could just do:

ranges.find { |r| r.cover?(n) }&.first || 0

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121020

Just out of curiosity:

number = 5
instance_eval [
  "case number",
  *ranges.map { |r| "when #{r} then (#{r}).first" },
  "end"
].join($/)
#⇒ 4

Upvotes: 3

Jagdeep Singh
Jagdeep Singh

Reputation: 4920

If i get your question right, then you can forget case statement and do it using detect:

ary = [1..3, 4..6, 7..20, 21..38]

num = 15    # say

ary.detect { |sub_ary| sub_ary.include?(num) }
 => 7..20

ary.detect { |sub_ary| sub_ary.include?(num) }.first     # call `first` on result of above, which is a range, to get the first element.
 => 7

Upvotes: 4

Related Questions