Hakan Ensari Hakan Ensari - 5 months ago 7
Ruby Question

How do I break out of a map/collect and return whatever has been collected up to that point?

I'm rewriting this question in code:

many = 1000

# An expensive method.
#
# It returns some data or nil if no result is available.
expensive_method = lambda do
rand(5) == 0 ? nil : "foo"
end

# Now, let's collect some data and stop collecting when no more data is
# available.

# This is concise but doesn't work.
collection = many.times.map do
expensive_method.call || break
end

puts collection.is_a? Array # false

# This is less concise but works.
collection = []
many.times do
collection << (expensive_method.call || break)
end

puts collection.is_a? Array # true

# My inner Rubyist ponders: Is it possible to accomplish this more concisely
# using map?

Answer

If you really mean "up to the break", [0,1,2,1,0] should result in [0,1], not [0,1,1,0]. The only way in Ruby that I know about is break in a loop. Functional approach could be much slower as you don't actually break:

r = 
  [0,1,2,1,0].inject([true, []]) do |(f, a), i|
    if f
      if i > 1
        [false, a]
      else
        [f, a << i]
      end
    else
      [f, a]
    end
  end
puts r.last.inspect

Compare with:

r = []
[0,1,2,1,0].each do |i|
  break if i > 1
  r << i
end
puts r.inspect

Tail recursion is out of the question for Ruby, this is how things are done in true functional languages.

Breaking map doesn't work for me, result is nil.

Added: As @dogenpunk pointed out, there is take_while (and drop_while in fact), which is probably a better alternative, only it always creates temporary array which may or may not be the a problem.

Comments