ChuckE
ChuckE

Reputation: 5688

Ruby Enumerable: get the first which is not nil, but the result must be what's inside the block

The Enumerable#find method works by evaluating until the it finds an element which matches the condition in the block. Is there something similar for returning the first time that the block is not evaluated to nil? imagining one would have a collection of hashes:

value = nil
options.each do |o|                     
  break if value = o[:desired]
end                                     
value ||= DEFAULT

isn't there a method which already accomplishes this?

No point in making a lot of transformations to the collection, i'd like to minimize the number of allocations, so any solution which allocates a new Array will not be good for me.

Upvotes: 4

Views: 1099

Answers (5)

guest
guest

Reputation: 1

How about

options.reduce{ |_,o|
   break o if o[:desired]
   DEFAULT
}

or

catch do |tag|
   options.each{ |_,o| o[:desired] and throw tag, o }
   DEFAULT
end

The latter allows for recursion.

Upvotes: 0

stujo
stujo

Reputation: 2109

This came up for me today: I think we can use break with a value from reduce

treasure = [1,2,3].reduce(nil) do |memo, value|
  break memo if memo
  foo(value)
end

Upvotes: 0

Ryan Lue
Ryan Lue

Reputation: 987

As of Ruby 2.0, this can be accomplished by combining #map and #find with lazy enumerables:

value = options.lazy.map { |o| o[:desired] }.find { |x| !x.nil? } # or find(&:present?) with ActiveSupport
value ||= DEFAULT

Upvotes: 1

lorefnon
lorefnon

Reputation: 13105

You can use reduce:

value = options.reduce(nil){|memo, entry|  memo || entry[:desired]  } || DEFAULT

Upvotes: 0

Wand Maker
Wand Maker

Reputation: 18762

find method will work for finding first element which has :desired key with minimum iterations.

I think you wish to get the value of desired key from the block instead of element itself - there is no method in Enumerable that behaves like a mixture of find and map - you will have to use the outer variable to which value is assigned inside the block as shown below.

options = [{foo: 1}, {desired: 2}, {bar: 3}]

value = nil
options.find do |o|
  value = o[:desired]
  break if value
end

p value
#=> 2

It more or less looks like your code, which should also work just fine.

Below is one way which you can use if you want to use Enumerable methods, but it will iterate over all elements.

p value = options.map { |o| value = o[:desired] }.compact.first

Upvotes: 3

Related Questions