Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance: iterate over an array except for an element

I am using Ruby on Rails 3.0.7 and I would like to iterate over an array of objects (class istances) except for the element with id equal to 1 (the id refers to the array[1] index).

I know that I can use an if statement "internally" to an each statement and doing that I check for each "current"\"considered" element if id == 1. However, since the array is populated of a lot of data, I would like to find another way in order to accomplish the same things in a more performant way (avoiding to run the if each time).

How can I do?

like image 375
user502052 Avatar asked May 19 '26 10:05

user502052


2 Answers

  1. Make program work
  2. Profile
  3. Optimize

Donald Knuth said:

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

Now, you could do something like:

def f
  do_something
end

f 0
for i in 2..n
  f i
end

Or even:

def f
  yield 0
  for i in 2..@n
    yield i
  end
end

f do |i|
  do_something
end

But you probably do not want to do anything of the sort, and if you did, it would only be after finding out that it matters.

And finally, suppose this ugly trick actually makes your server run ever so slightly faster. Was it worth it?

like image 79
DigitalRoss Avatar answered May 21 '26 01:05

DigitalRoss


a = ['a', 'b', 'c']
a.each_with_index.reject {|el,i| i == 1}.each do |el,i|
  # do whatever with the element
  puts el
end

Is IMHO a better way of doing the selection instead of using your own explicit if statement. I believe, however, that it will result in approximately the same performance as using if, maybe even slightly lower.

If after benchmarking as others have suggested you know that the time this takes is definitely slower than what you can allow it, and it is the selection causing the slowness then this can be easily modified to remove the selection in a number of ways:

a = ['a', 'b', 'c']
n = 1
(a.first(n) + a.drop(n + 1)).each do |el|
  # do whatever with the element
  puts el
end

Unfortunately I believe this will also be slower than running the simple if. One that I believe might have the potential for speed is:

a = ['a', 'b', 'c']
n = 1
((0...n).to_a+((n+1)...a.size).to_a).map{|i| a[i]}.each do |el|
  # do whatever with the element
  puts el
end

But this again has a high possibility of being slower.

EDIT

Benchmark is in this gist. These results actually surprised me, reject is by far the slowest option, followed by the ranges. The highest performing after not removing the element at all was using first and drop to select all the elements around it.

The results as a percentage using no selection as a baseline:

with if             146%
with first and drop 104%
without if          100%

Obviously this is highly dependant on what you do with the elements, this was testing with probably the fastest operation Ruby can perform. The slower the operation the less difference these will have. As always: Benchmark, Benchmark, Benchmark

like image 24
Nemo157 Avatar answered May 20 '26 23:05

Nemo157



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!