Ruby # select, but select only a specific number

What's the best way to do something in Ruby like my_array.select(n){ |elem| ... } my_array.select(n){ |elem| ... } , where n means "I only want to return the elements of n and stop evaluating after reaching this number"?

+4
source share
6 answers

It seems that you are not avoiding the traditional loop if you use stock 1.8.7 or 1.9.2 ...

 result = [] num_want = 4 i = 0 while (elem = my_array[i]) && my_array.length < num_want result << elem if elem.some_condition i += 1 end 
+1
source

This should do the trick:

my_array.select(n) { |elem| elem.meets_condition? }.take(n)

However, this will still evaluate all the elements.

If you have a lazy enumerator, you can do it more efficiently.

https://github.com/ruby/ruby/pull/100 shows an attempt to enable this feature.

+2
source

You can easily implement lazy_select :

 module Enumerable def lazy_select Enumerator.new do |yielder| each do |e| yielder.yield(e) if yield(e) end end end end 

Then things like

 (1..10000000000).to_enum.lazy_select{|e| e % 3 == 0}.take(3) # => [3, 6, 9] 

run instantly.

+2
source

You can create an Enumerable-like extension that has the desired selectn semantics:

 module SelectN def selectn(n) out = [] each do |e| break if n <= 0 if yield e out << e n -= 1 end end out end end a = (0..9).to_a a.select{ |e| e%3 == 0 } # [0, 3, 6, 9] a.extend SelectN a.selectn(1) { |e| e%3 == 0 } # [0] a.selectn(3) { |e| e%3 == 0 } # [0, 3, 6] # for convenience, you could inject this behavior into all Arrays # the usual caveats about monkey-patching std library behavior applies class Array; include SelectN; end (0..9).to_a.selectn(2) { |e| e%3 == 0 } # [0,3] (0..9).to_a.selectn(99) { |e| e%3 == 0 } # [0,3, 6, 9] 
+1
source

I think the broken loop can be done in the old-fashioned loop style with break or something like this:

 n = 5 [1,2,3,4,5,6,7].take_while { |e| n -= 1; n >= 0 && e < 7 } 

In a functional language, this will be recursion, but without TCO it does not make much sense in Ruby.

UPDATE

take_while was a dumb idea, as dbenhur pointed out, so I don't know anything better than a loop.

0
source

Why not flip this and do #take before #select:

 my_array.take(n).select { |elem| ... } 

This ensures that you only do calculations for n number of elements.

EDIT:

Enumerable :: Lazy, as you know, is slower, but if your calculation is more computationally expensive than lazy slowness, you can use the Ruby 2.0 function:

 my_array.lazy.select { |elem| ... }.take(n) 

See: http://blog.railsware.com/2012/03/13/ruby-2-0-enumerablelazy/

0
source

Source: https://habr.com/ru/post/1403751/


All Articles