1

Short question: when calling a method of an object "a" and passing it a block, can I access "a" from within that block?

Example: Let's say I have an array "words" and I first want to remove all the "bad" words from it (determined using a function) and then find the word that is most frequent among those that remained. I can do it in two lines:

temp_words = words.reject{|w| word_is_bad(w)}
puts temp_words.max{|w| temp_words.count(w)}

However, I wish to avoid having to create a new variable called "temp_words" and do everything in one line, like this:

words.reject{|w| word_is_bad(w)}.max{|w| self.count(w)}

even though I believe my intention is clear, the code fails since "self" does not refer to the temp array generated after reject is called, but to the program's main object.

3
  • The short answer is no, not without using instance_eval or something similar. You don't really need to chain the blocks though. Could always write it like words.max_by {|w| word_is_bad(w) ? -1 : words.count(w) } (side note, I think you probably want max_by rather than max) Commented Jul 3, 2014 at 14:01
  • @numbers1311407 That would be an excellent answer, Why not add it as one? Commented Jul 3, 2014 at 14:02
  • @MarkThomas good point, will do Commented Jul 3, 2014 at 14:04

2 Answers 2

3

If you want to stick to your way of doing it, you can write:

words.reject{|w| word_is_bad(w)}.instance_eval{max_by{|w| count(w)}}

but a better and more popular way to do it is:

words.reject{|w| word_is_bad(w)}.group_by{|w| w}.max_by{|_, a| a.length}.first
Sign up to request clarification or add additional context in comments.

Comments

3

The short answer is no, not without using instance_eval or something similar.

You don't really need to chain the blocks though. Could always write it like:

 words.max_by {|w| word_is_bad(w) ? -1 : words.count(w) }

(side note, I think you probably want max_by rather than max)

If you find yourself having to get clever with one liners to express your logic, it might make sense to encapsulate what you need in a class. Consider this simple example:

class Words < Array
  def self.is_bad(word)
    foo
  end

  def good
    reject {|w| Word.is_bad(w) }
  end
end

Which would allow you to write:

words = Words.new(["foo", "bar", "baz"])
words.good.max_by {|word| words.count(word) }

Much more expressive and sidesteps the problem.

A step further:

def max_good
  good.max_by {|word| count(word) }
end

And you could write simply words.max_good

4 Comments

This is a good solution, but only for the example. In my "real" problem, the temp array I'm working on is a result of a much more complicated process.
When all words are bad words, this code silently returns the first word without telling it is bad.
You may want to extend Array or otherwise write a custom enumerable Words class to encapsulate the logic you need.
@sawa That's true, so rejecting first does make more sense if this were to be a one liner. Given that the OP has admitted the "real" problem is more complicated it probably makes sense to circumvent tricky one liners all together and encapsulate the logic in a class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.