I'd use:
n = 10
[1, 5, 8, 12, 16, 17].select { |i| i <= n }.last # => 8
Thinking about it, the fastest way is to use bsearch_index
since the input is already sorted.
n = 11
ary = [1, 5, 8, 12, 16, 17]
ary[ary.bsearch_index { |i| n < i } - 1] # => 8
bsearch would be slower if the hit occurs early in ary
but in big arrays it'll quickly pull ahead compared to looking at each element.
require 'fruity'
n = 11
ary = [1, 5, 8, 12, 16, 17]
compare do
last { ary.select { |i| i <= n }.last }
bsearch_index { ary[ary.bsearch_index { |i| n < i } - 1] }
Bustikiller { ary.reverse.find { |i| i <= n } }
engineerDave1 { ary.take_while {|x| x <= n}[-1] }
engineerDave2 { ary.reduce(nil) {|a,x| a = x if x <= n; a} }
end
# >> Running each test 8192 times. Test will take about 1 second.
# >> bsearch_index is faster than engineerDave1 by 10.000000000000009% ± 10.0%
# >> engineerDave1 is faster than last by 2x ± 0.1
# >> last is similar to engineerDave2
# >> engineerDave2 is similar to Bustikiller
Increasing the size of the array:
require 'fruity'
n = 999
ary = (0..1000).to_a
compare do
last { ary.select { |i| i <= n }.last }
bsearch_index { ary[ary.bsearch_index { |i| n < i } - 1] }
Bustikiller { ary.reverse.find { |i| i <= n } }
engineerDave1 { ary.take_while {|x| x <= n}[-1] }
engineerDave2 { ary.reduce(nil) {|a,x| a = x if x <= n; a} }
end
# >> Running each test 4096 times. Test will take about 17 seconds.
# >> bsearch_index is faster than Bustikiller by 3x ± 1.0
# >> Bustikiller is faster than engineerDave1 by 21x ± 1.0
# >> engineerDave1 is faster than last by 30.000000000000004% ± 10.0%
# >> last is faster than engineerDave2 by 10.000000000000009% ± 10.0%
The problems with the simple use of bsearch_index
are noted in the comments below. I think it'd be worth looking into using it but wrapping it with some logic to fix the problems mentioned. I'll make this a community answer so anyone coming up with the rest of the code can add it.