How can I select a statement only once, instead of repeating the test inside a loop?

Here's what I'm trying to refactor:

items.each_with_index do |item, ndx|
  if side == :left
    condition = ndx < max
  else
    condition = ndx >= max
  end

  result_items << item if condition
end

      

I would like to be able to move this if / else / end outside the block, since the value side

never changes inside the block. Since the only value-based change side

is the operator, I would like to do something like:

if side == :left
  oper = <
else
  oper = >=
end

items.each_with_index do |item, ndx|
  result_items << item if ndx oper max
end

      

But of course this won't work because of the syntax.

Is there anyway to keep the statement, or is there a better way to accomplish what I've done here?

+3


source to share


4 answers


Operators are just methods:

ndx.public_send(:<=, max)

      

Same as

ndx <= max

      



So outside of the loop, set oper

to the symbol for the required statement and inside

result_items << item if ndx.public_send(oper, max)

      

(you'll have to use send

, not public_send

if you're still stuck on 1.8)

+4


source


Check "proc" and "lambda". They allow you to reference the letsay function, an anonymous function:

[4] pry(main)> func = proc {|left, right| left < right}
=> #<Proc:0x34c83c0@(pry):4>

[5] pry(main)> func.call(4,5)
=> true

[6] pry(main)> func.call(5,2)
=> false

      

Note that "proc" expects a "block" to be passed and that block will later be "called" with the given parameters. This is the same syntax as if you were using map

or each

- all the blocks are there. proc

just returns that block to you, not calls it.



So your code might look like this:

if side == :left
  oper = proc{|a,b| a<b}
else
  oper = proc{|a,b| a>=b}
end

items.each_with_index do |item, ndx|
  result_items << item if oper.call(ndx, max)
end

      

I have not tested this code, it may have some typos.

+2


source


If I understood your intent here, another way might be to use take

and drop

:

result_items = if side == :left
    items.take max
else
    items.drop max
end

      

+2


source


If you can tweak this logic a bit, you can also achieve the same as:

items.each_with_index do |item, ndx|
  # assuming it to be Fixnum since you are using index value
  result_items << item if (max - ndx).abs >= 0
end

      

0


source







All Articles