Ruby Enumerable: first truthy value of a block

ruby any
ruby find object in array
ruby hash any
ruby array find
ruby hash find
ruby select
ruby map

In ruby we can do something like:

stuff_in_trash.detect(&:eatable?)
=> :pack_of_peanuts

stuff_in_trash.detect(&:drinkable?)
=> nil

But what if we are interested in the value of the block the first time it is truthy, rather than the first item for which the block takes a truthy value?

That is convert the following code:

def try_to_make_artwork_from(enumerable)
  enumerable.each do |item|
    result = make_artwork_from item
    return result if result
 end
   nil
end

To something like:

def try_to_make_artwork_from(enumerable)
  enumerable.try_with { |item| make_artwork_from item }
end

What is desirable in the initial code is:

  1. It returns nil if the block never takes a truthy value;
  2. It returns the value of the block the first time it is truthy;
  3. It stops after finding the first match;
  4. It does not call make_artwork_from again (let us say it is not guaranteed to return the same result the next time you call it).

What is not so desirable, is that uses result three times, yet it has nothing to do with the story.

EDIT: Sorry, the initial implementation was incorrect, it needed to return nil in the case the block value was never true.

enumerable.lazy.map(&:block).detect(&:itself)

does the job, but is the simplest way? Is it efficient compare to simply using an each and caching the value?

How to iterate over an Enumerable, returning the first truthy result of , Ruby has Enumerable.find(&block) , which returns the first item in the collection You could first map the collection and then take the first truthy value, but this way Calling break with an argument will exit the whole block evaluation, returning� Passes each element of the collection to the given block. The method returns true if the block ever returns a value other than false or nil. If the block is not given, Ruby adds an implicit block of { |obj| obj } that will cause any? to return true if at least one of the collection members is not false or nil.

array.inject(nil) {|c,v| c or m(v)}

Ruby Enumerable: first truthy value of a block?, [1,2,3].lazy.select {|item| 42 if item == 2 }.first def detect_result(enumerable) enumerable.detect do |i| if result = yield(i) return result end end end # Usage:� The block of code above will output not truthy because line 2 evaluated the local variable a to falsey as a references the nil object.. Before we go back to our select method, we need to highlight

You can extend Enumerable with the desired iterator:

module Enumerable
  def detect_return
    self.detect do |i|
      r = yield(i) and return r
    end
  end
end

[1, 2, 3].detect_return do |i|
   if i + 1 >= 2
     puts "I will be printed only once"
     "ok, here I am"
   end
end
# I will be printed only once
# => "ok, here I am"

As far as we all agree that monkey patching is a bad stuff, let's provide less harming variant:

def detect_return(enumerable)
  enumerable.detect do |i|
    r = yield(i) and return r
  end
end

detect_return([1, 2, 3]) do |i|
  if i + 1 >= 2
    puts "I will be printed only once"
    "ok, here I am"
  end
end
# I will be printed only once
# => "ok, here I am"

detect_return([1, 2, 3]) do |i|
  if i + 1 >= 42
    puts "I will be printed only once"
    "ok, here I am"
  end
end
# => nil

Module: Enumerable (Ruby 2.7.1), The method returns true if the block ever returns a value other than false or nil . #cycle saves elements in an internal array so changes to enum after the first Returns a new array containing the truthy results (everything except false or nil )� Ruby has Enumerable.find(&block), which returns the first item in the collection for which the block evaluates to true. first_post_with_image = posts.find do |post| post.image end However, sometimes it's not the item you're interested in, but some value depening on it – e.g. the value the bloc

any? (Enumerable), The method returns true if the block ever returns a value other than false or nil. If the block is not given, Ruby adds an implicit block of { |obj| obj } that will cause� The reducer method receives an initial value as the accumulator (0 in this case), and for each value, it passes both the accumulator and the email to the block it was given. Finally, it returns the accumulator. You can do anything you want with those arguments inside the block. But this example counts the number of emails present in the emails

(from https://makandracards.com/makandra/35569-how-to-iterate-over-an-enumerable-returning-the-first-truthy-result-of-a-block-map-find)

You can simply use break:

# "pseudo" code
my_enum.find do |item|
  calculated_result = begin
    # some stuf
  end

  break calculated_result if my_condition_is_true
end


# Or from the given link exemple 
first_image_url = posts.find do |post|
  break post.image.url if post.image.present?
end


all? (Enumerable), If the block is not given, Ruby adds an implicit block of { |obj| obj } which will cause items, the first being the key, the second being the corresponding value. Select only keeps things that our block says are truthy. Remember though that everything that's not nil or false is truthy in Ruby. Let's take a look at how that'd be done using each: def select (old_array, &block) new_array = [] old_array.each { |n| new_array.push(n) if block.call(n) } new_array end

Block return values, Remember that we said a block returns a value just like methods do? It does this by calling the method collect on the original array, which calls the given block for each of the It first calls the block passing the number 1 . However, as soon as the block returns something truthy (something that is “equivalent to true”), the� If the block is not given, Ruby adds an the truthy results (everything the minimum and the maximum value in the enumerable. The first form assumes all

Ruby Enumerables: 'any', 'all', 'none', The any? method returns true if the block ever returns a value other than false or value| key == "b"} # returns an Array of the first match [key, value] that satisfies� The block that we pass in our call tomapis called one time for each element in the array. Each time the block is called, the value of the current array element is passed in as the block parameter. The results of running each block are stored as new elements in a new array. After this code is run, we have two separate arrays:

Ruby Enumerable: get the first which is not nil, but the result must be , How to iterate over an Enumerable, returning the first truthy result of , Ruby has The method returns true if the block ever returns a value other than false or nil. It takes a block as an argument and runs the code within the block on each element of the array. At each iteration, the value of n (which is passed to the block as an argument) corresponds to one item of the array. The code inside the block will print each array value as it is received from the each method. Instead of printing the array values

Comments
  • What do you mean by "value of the block", do you mean the argument passed to the block or what the block returns? Maybe I'm missing something but isn't this the purpose of Enumerable#find
  • Perhaps it is just me, but I don't understand your question. Wouldn't enumerable.detect { |item| make_artwork_from(item) } return exactly the desired output?
  • @spickermann OP wants the method to return the value of make_artwork_from(item). Your code above will return the value of item.
  • Yes, the lazy enumerable you included in your question is the correct, easiest and fastest way to accomplish this.
  • @EricDuminil Because I realized the question had the identical code snippet edited into it.
  • Downvoter : constructive criticism is always welcome!
  • What's confusing about it? It expands to nil or m(array[0]) or m(array[1]) or ...
  • The only difference in terms of behavior is the fact that array.inject(nil) {|c,v| c or m(v)} will keep iterating on the enumerable even though the block will never be called again. If one is only interested in never calling the block once it is no longer necessary, and not concerned with iterating over a huge enumerable, yours is arguably a much cleaner solution.
  • You can extend Enumerable, but patching core classes is pretty much never the answer.
  • @meagar True, monkey patching is generally a bad idea ;)