Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any case to prefer for instead of each in Ruby?

Tags:

ruby

I know that for i in arr slightly differs from arr.each scope and everyone keeps saying that iterators are preferrable, but I wonder if there is any case when cycle is preferrable and why it is (since iterators are more idiomatic)?

like image 419
p0deje Avatar asked Nov 25 '13 16:11

p0deje


2 Answers

TL;DR

  • Use for loop for performance in Ruby 1.8
  • Use for loop to standards in existing projects
  • Use each loop to minimize side effects
  • Prefer each loop.

each minimizes side effects

The primary difference between for and each is scoping.

The each function takes a block. Blocks create a new lexical scope. This means that any variables declared within the scope of the function will no longer be available after the function.

[1, 2, 3].each do |i|
  a = i
end

puts a
# => NameErrror: undefined local variable or method `a' for main:Object

Whereas:

for i in [1, 2, 3]
  a = i
end

puts a
# => 3

Thus, using the each syntax minimizes the risk of side effects.

Determing exit point?

This said, there are special instances where the for loop may be helpful. Specifically, when finding out where a loop exited.

for i in 1..3
  a = i

  break if i % 2 == 0
end

puts a
# => 0

There is a better way to do this though:

a = (1..3).each do |i|
  break i if i % 2 == 0
end

Each is faster (in Ruby 2.0)

Benchmark.bm(8) do |x|
  x.report "For" do
    max.times do
      for i in 1..100
        1 + 1
      end
    end
  end

  x.report "Each" do
    max.times do
      (1..100).each do |t|
        1+1
      end
    end
  end
end

Ruby 2.0

               user     system      total        real
For        6.420000   0.000000   6.420000 (  6.419870)
Each       5.830000   0.000000   5.830000 (  5.829911)

Ruby 1.8.6 (Slower Machine)

              user     system      total        real
For      17.360000   0.000000  17.360000 ( 17.409992)
Each     21.130000   0.000000  21.130000 ( 21.250754)

Benchmarks 2

If you read the comment trail, there is a discussion about the speed of creating objects in for vs each. The link provided had the following benchmarks (although, I have cleaned up the formatting and fixed the syntax errors).

b = 1..10e5

Benchmark.bmbm (10) do |x|
  x.report "each {}" do
    b.each { |r| r + 1 }
  end

  x.report "each do end" do
    b.each do |r|
      r + 1
    end
  end

  x.report "for do end" do
    for r in b do
      r + 1
    end
  end
end

Ruby 2.0

                  user     system      total        real
each {}       0.150000   0.000000   0.150000 (  0.144643)
each do end   0.140000   0.000000   0.140000 (  0.143244)
for do end    0.150000   0.000000   0.150000 (  0.147112)

Ruby 1.8.6

                  user     system      total        real
each {}       0.840000   0.000000   0.840000 (  0.851634)
each do end   0.730000   0.000000   0.730000 (  0.732737)
for do end    0.650000   0.000000   0.650000 (  0.647186)
like image 182
Dan Grahn Avatar answered Nov 20 '22 21:11

Dan Grahn


I have written a few plugins for SketchUp's Ruby API and I found that when iterating large collections (of geometry entities) I would get better performance with a for in loop over an each block.

I believe this to be because the for in loop doesn't create it's local scope and objects are reused instead of being created for every iteration as it would in the each loop.


EDIT: The speed gain depends on Ruby version. Using the test snippet used in this article http://blog.shingara.fr/each-vs-for.html:

Ruby 1.8.6:

              user     system      total        real
For      14.742000   0.000000  14.742000 ( 14.777000)
Each     18.190000   0.000000  18.190000 ( 18.194000)

Ruby 2.0.0

               user     system      total        real
For        5.975000   0.000000   5.975000 (  5.990000)
Each       5.444000   0.000000   5.444000 (  5.438000)

Things has greatly improved since the old 1.8.6. (Though, SketchUp extension developers still need to optimize against this version.)

like image 20
thomthom Avatar answered Nov 20 '22 20:11

thomthom