2

I have this array

[1, 2, 3, 4, 5, 6]

I would like to get the first 2 elements that are bigger than 3.

I can do:

elements = []

[1, 2, 3, 4, 5, 6].each do |element|
  elements << element if element > 3
  break if elements.size == 2
end

puts elements

Is there a more elegant way to do this?

Is there something in the Ruby core like Array.select(num_elements, &block)?

3
  • Why did not you choose arr.select { |z| z > 3 }[2] ? Commented May 20, 2019 at 6:26
  • @ray because in my production scenario the filter computation is very expensive and the number of elements can be thousands. So I want to break the loop as soon I have achieve the minimum desirable amount Commented May 30, 2019 at 9:21
  • I got your point Commented May 30, 2019 at 11:28

3 Answers 3

3

You were nearly there. Just use break with a parameter:

[1, 2, 3, 4, 5, 6].each_with_object([]) do |element, acc|
  acc << element if element > 3
  break acc if acc.size >= 2
end

Another way to accomplish it, would be to use Enumerator::Lazy with array.lazy.select, or an explicit Enumerator instance with Enumerable#take (here it’s a definite overkill, posting mostly for educational purposes.)

enum =
  Enumerator.new do |y|
    i = [1, 2, 3, 4, 5, 6].each
    loop { i.next.tap { |e| y << e if e > 3 } }
  end
enum.take(2)
#⇒ [4, 5]

Sidenote: both examples above would stop traversing the input as soon as two elements are found.

Sign up to request clarification or add additional context in comments.

4 Comments

Why don't you use to_enum method instead of each?
each without a block calls to_enum under the hood.
Yes, but here your intention is to convert so why calling each rather than to_enum? That would explicitly tells us as well.
the part of stop traversing the input as soon as two elements are found is the key here.
3
a = [1, 2, 3, 4, 5, 6]

p a.filter {|x| x > 3}.first(2)

Or

p a.select{|x| x > 3}.first(2)

output

[4, 5]

As Cary suggest, the given below code wouldn't be a performance hit if array is bigger, it would stop executing further if 2 elements are found

a.lazy.select{|x| x > 3}.first(2)

4 Comments

...or a.lazy.select{|x| x > 3}.first(2) to bail out as soon as the first two have been found.
@CarySwoveland That's exactly something I am thinking now. Thanks. Will update the answer now.
I prefer that any acknowledgement of readers' contributions to answers be confined to comments (and that such acknowledgements are generally not needed), in order to keep answers focused on the question. In the same way you don't see books and articles littered with acknowledgements of which readers of drafts suggested which changes.
Okay sure. Thanks
1

Just for having a couple of options more..

ary.each_with_object([]) { |e, res| res << e if e > 3 && res.size < 2 }

or

ary.partition { |e| e > 3 }.first.first(2)

4 Comments

Wow super. Amazing idea.
This traverses the whole array, which might be a bit underperformant for [1, 1, *1..Float::INFINITY].
@AlekseiMatiushkin, I'm aware. But I'm unsure about partition. I've not been able to find how to break when res.size < 2, and break if was returning nil. But I'm learning it right now from your answer: { |e, res| res << e if e > 3; break res if res.size == 2 } :).
If you are unsure about partition, there is a simple way to ensure: (1..10).partition { |e| puts e; e > 3 }.first.first(2). To escape sooner you need a lazy enumerator.

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.