# Ruby array search, returning either a result with a good score or the last result

I have an array of hashes, each of which has two attributes: "title" and "Zscore". I want to return either the result of find or the last element. This can be done with:

a = array.select{|i| i["title"] == "some title"}
b = a.find{|i| i["Zscore"].to_f >= 5} || a.last


Is there an elegant way to put the conditional inside the block, something like the following?

array.select{|i| i["title"] == "some title"}.find{|i| i["Zscore"].to_f >= 5 || last}


Any built-in method? Any ideas of one-liner that is nicer than the following?

array.select{|i| i["title"] == "some title"}.find{|i| i["Zscore"].to_f >= 5} ||
array.select{|i| i["title"] == "some title"}.last

• Why? Why would you want a built-in method, and why would you think it would be better than the two characters ||? What you have right now is already elegant. I can't imagine what you think you'll accomplish by moving the conditional inside the block, except to make the code less readable and less elegant. Jun 26 '16 at 14:35
• @meagar The OP's intention is not clearly expressed, but I suspect the real intention is to avoid intermediate variable assignment a = and doing it in a chain style.
– sawa
Jun 26 '16 at 14:39
• @sawa That moves the question "Why?", but it doesn't answer it. Jun 26 '16 at 14:40
• @meagar Yeah. I don't think it is worth doing it.
– sawa
Jun 26 '16 at 14:43
• @meagar Why? because ruby provides many elegant one-liners and having all of array.select{|i| i["title"] == "some title"}.find{|i| i["Zscore"].to_f >= 5} || array.select{|i| i["title"] == "some title"}.last is long and unreadable. As for the original tag of ruby-on-rails, it is within a Rails app. Rails provides methods such as first_or_create and I have wondered about the existence of a method of such kind that might come in handy. Jun 26 '16 at 17:06

You can always define your own custom method to refactor your code in the Array class:

class Array
def find_or_last(&block)
find(&block) || last
end
end


And use it like this:

array.select { |i| i['title'] == 'some title' }.find_or_last { |i| i['Zscore'].to_f >= 5 }


You can also use procs or lambdas to achieve this.

• Any developer on my team who tried to introduce this code to a project would immediately have a his pull request denied and be given a stern talking to about monkey-patching core classes. Jun 26 '16 at 14:42
• @meagar Unless Spätzle is in your team, this comment has nothing to do with a correct robust answer, provided by Wikiti. Jun 26 '16 at 14:49
• @meagar Then I believe it worth it to mention best practices rather than your personal leadership preferences in first place. Jun 26 '16 at 14:53
• @meagar I don't like extending core classes either, but the OP asked for an elegant or one-liner solution. If he's working on a personal or basic project, this solution should work for him. Jun 26 '16 at 15:05
• I am NOT working on a personal / basic project and monkey-patching core classes would make my boss, just as @meagar said, reject my work. The whole purpose was having a nice one-liner (which eventually reduces db queries) W/O patching. If patching was legitimate for this, I wouldn't even have bothered asking this question. Jun 30 '16 at 4:45

I have assumed a is not needed for some unspecified purpose.

This is a way of achieving the desired result with a single pass through the array.

last = nil
array.find do |h|
next unless h["title"] == "some title"
last = h
h["Zscore"].to_f >= 5
end || last


Here is one way to do this using Enumerable#detect:

array = [{"title"=>"Foo", "Zscore" => "2.0"},
{"title"=>"Bar", "Zscore" => "5.0"},
{"title"=>"some title", "Zscore" => "2.0"}]

result = (t = array.select {|i| i["title"] == "some title"})
.detect (-> {t.last} ) {|i| i["Zscore"].to_f >= 5}

#=> {"title"=>"some title", "Zscore"=>"2.0"}


The parameter passed to detect is the ifnone method that will be evaluated and returned if detect could not find any matching element.

• You are close. But this is not correct. It will give you a wrong answer when the last element of array does not satisfy the first condition, and the default has to be used.
– sawa
Jun 26 '16 at 14:28
• The goal is not to return array.last, it is to return array.selet { |a| a['title'] == 'some_title' }.last Jun 26 '16 at 14:49
• @meagar Okay, I was working on the solution as sawa already pointed out the problem. Have fixed it now.
– Wand Maker
Jun 26 '16 at 14:50
• You can simply give up doing it with method chain, but use the default. That would still be an improvement over the OP's code.
– sawa
Jun 26 '16 at 14:58
• You haven't eliminated the intermediate variable, you've just wrapped it in parenthesis. Jun 26 '16 at 15:22

Variation with Enumerator#each_with_object (O(n)):

array.each_with_object({}) { |el, hsh| el['title'] == 'some title' && hsh.replace(el) && el['Zscore'].to_f >= 5.0 && (break el) }


Some kind of "functional" style. Maybe, somewhat too verbose but readable. Can't assess its elegance yet.

There is nothing wrong with using a local variable, but you can always transform variables to chains with a let abstraction. Ruby has no such abstraction, but you can create it it yourself (it has a myriad of names, I like as):

class Object
def as
yield self
end
end

result = xs.select { |x| x["title"] == "some title" }.
as { |ys| ys.find { |y| y["Zscore"].to_f >= 5 } || ys.last }