Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find first n elements from array that match a condition

Tags:

ruby

I want to select the first 10 elements of an array that match a certain condition without having to loop through the whole array. I know that find gets me the first element. So for example, the code below gives me the first prime that is larger than 100:

require 'prime'

puts Prime.find {|p| p > 100 } #=> 101

Is there a way to get the first 10 primes that are bigger then 100?

like image 411
Erwin Rooijakkers Avatar asked May 10 '14 12:05

Erwin Rooijakkers


2 Answers

In Ruby 2.0+ you can write:

require 'prime'

Prime.lazy.select{|p| p > 100 }.take(10).to_a #=> [101, 103, 107, 109, 113, 127, 131, 137, 139, 149]
like image 159
hirolau Avatar answered Sep 28 '22 00:09

hirolau


You can do it more manually like

def check_for_primes(start_number, desired_size)
  result = []
  suspect = start_number
  while result.size < desired_size do
    result << suspect if suspect.prime?
    suspect += 1
  end
  result
end

check_for_primes 100, 10

#=> [101, 103, 107, 109, 113, 127, 131, 137, 139, 149]

with a simple ruby iteration.

Which works with all ruby versions.

instead of the (indisputably non ruby like) while loop, we can add @cary-swoveland 's variation, which has quite some ruby goodness in it.

check_enum_text(start_number, desired_size)
  (start_number..1.0/0).each_with_object([]) do |n,arr|
    if n.prime?
      arr << n;
      return arr if arr.size == desired_size
    end
  end
end

***********UPDATE***********

and some benchmarks for performance

require 'benchmark'
a_gazillion = 10000000000

Benchmark.bm do |x|
  x.report("lazy_select") { Prime.lazy.select{|p| p > (a_gazillion / 1000) }.take(10).to_a }
  x.report("prime_each") { arr = []; Prime.each{|p| arr << p if p > a_gazillion / 1000; break if arr.count == 10 } }
  x.report("while_loop") { check_for_primes a_gazillion, 10 }
  x.report("enum_text") { check_enum_text a_gazillion, 10 }
end

            user       system     total     real
lazy_select 75.360000   0.240000  75.600000 (84.259781)
prime_each  6.100000    0.040000   6.140000 ( 6.730646)
while_loop  0.620000    0.000000   0.620000 ( 0.655504)
enum_text   0.610000    0.000000   0.610000 ( 0.770726)

from what we see the two latest solutions are the ones that perform the best. From some extra benchmarking (by tweaking the desired_size) I can not conclude to which one is better

def bench(start, length)
  Benchmark.bm do |x|
    x.report("enum_text") { check_enum_text start, length }
    x.report("while_loop") { check_for_primes start, length}
  end
end

bench a_gazillion, 100
             user       system    total     real
enum_text    6.350000   0.000000   6.350000 (  6.974557)
while_loop   6.530000   0.000000   6.530000 (  7.330884)

bench a_gazillion, 500
             user        system     total        real
enum_text    31.880000   0.110000  31.990000 ( 36.723209)
while_loop   32.850000   0.060000  32.910000 ( 38.569744)

Performance is similar (actually @cary-swoveland's solution performs slightly better), so I will have to go with that solution since it is more ruby like!!

like image 29
xlembouras Avatar answered Sep 27 '22 22:09

xlembouras