Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ruby

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? 
like image 826
Hakan Ensari Avatar asked Jul 12 '11 16:07

Hakan Ensari


People also ask

What is the difference between collect and map?

Collection- A collection represents a group of objects, known as its elements. Map- A map cannot contain duplicate keys. Each key can map to at most one value.

Is collect the same as map Ruby?

#collect is actually an alias for #map . That means the two methods can be used interchangeably, and effect the same behavior.

How do you break in Ruby?

In Ruby, we use a break statement to break the execution of the loop in the program. It is mostly used in while loop, where value is printed till the condition, is true, then break statement terminates the loop. In examples, break statement used with if statement. By using break statement the execution will be stopped.


2 Answers

Sure seems the only way to do this in Ruby is a filter type method then passing results to map. I'm not sure if this works in 1.8, but in 1.9 you could:

[0,1,2,1,0].take_while {|val| val < 2}.map(&:some_function) 

Or in the times example

3.times.take_while {|count| count <= 1 } #=> [0,1] 
like image 63
dogenpunk Avatar answered Sep 23 '22 21:09

dogenpunk


Instead of using map directly, build up your own collection and then use the fact that break returns a value to abort early:

result =    [0, 1, 2, 1, 0].each_with_object([]) { |val, accumulator|     if val < 2       accumulator << val     else       break accumulator     end   } result  # => [0, 1] 

If we did just break (instead of break accumulator) then nil would be implicitly returned and result would just be set to nil.

This solution has the advantage of only allocating a single accumulator Array and only having to loop once.

like image 38
pdobb Avatar answered Sep 23 '22 21:09

pdobb