Reputation: 34023
I have a collection of five objects.
The have a number attribute day
with a unique value ranging 0-6.
If compared to a value, let's say 5 or 0, how could I find the object with equal attribute value OR the highest value in collection but not exceeding the given?
collection.map(&:day)
=> [0, 2, 3, 4, 6]
The desired return when comparing collection
with value 5 should return the object with day
value 4.
The desired return when comparing collection
with value 0 should return the object with day
value 0.
Upvotes: 1
Views: 99
Reputation: 110685
Let's see how the methods compare in performance. I only compare methods that do not require the array to be pre-sorted. I will edit to add other methods that are suggested.
Methods compared
module Methods
def awendt(collection, value)
collection_sorted_desc = collection.sort_by{|num| -num}
collection_sorted_desc.detect{|num| num <= value }
end
def awendt_rev(collection, value)
collection_sorted_desc = collection.sort.reverse
collection_sorted_desc.detect{|num| num <= value }
end
def cary1(a,v)
a.select { |e| e <= v }.max
end
def cary2(a,v)
enum = a.each
mx = - Float::INFINITY
loop do
x = enum.next
mx = x if x <= v && x > mx
end
mx
end
def cary3(a,v)
a.sort.take_while { |e| e <= v }.last
end
end
include Methods
methods = Methods.instance_methods
#=> [:awendt, :cary1, :cary2, :cary3]
Test data
def test_data(n,m)
[[*(0..n)].shuffle, m.times.with_object([]) { |_,a| a << rand(n) }]
end
arr, cutoffs = test_data(8,3)
#=> [[1, 5, 2, 0, 3, 8, 4, 6, 7], [5, 1, 7]]
Confirm methods return the same values
arr, cutoffs = test_data(1000,100)
results = cutoffs.each_with_object([]) { |c,a|
a << send(methods.first, arr, c) }
puts methods[1..-1].all? { |m|
cutoffs.zip(results).all? { |c,r| r == send(m, arr, c) } }
#=> true
Benchmark
require 'benchmark'
arr, cutoffs = test_data(200_000, 20)
Benchmark.bm(methods.map { |m| m.to_s.size }.max) do |bm|
methods.each do |m|
bm.report m.to_s do
cutoffs.each { |c| send(m, arr, c) }
end
end
end
user system total real
awendt 5.140000 0.040000 5.180000 ( 5.188094)
awendt_rev 1.030000 0.010000 1.040000 ( 1.050535)
cary1 0.490000 0.010000 0.500000 ( 0.489371)
cary2 6.740000 0.010000 6.750000 ( 6.782410)
cary3 0.910000 0.010000 0.920000 ( 0.919538)
Edit: I added a method :awendt_rev
that replaces collection.sort_by{|num| -num}
in :awendt
with collection.sort.reverse
.
Upvotes: 3
Reputation: 110685
Here are three ways you could do it. The benchmark I added below shows that #1 and #3 are more efficient than #2.
#1
a = [0,2,3,4,6]
def not_exceed(a,v)
a.select { |e| e <= v }.max
end
not_exceed(a,5) #=> 4
not_exceed(a,0) #=> 0
#2
def not_exceed(a,v)
enum = a.each
mx = - Float::INFINITY
loop do
x = enum.next
mx = x if x <= v && x > mx
end
mx
end
not_exceed(a,5) #=> 4
not_exceed(a,0) #=> 0
#3
def not_exceed(a,v)
a.sort.take_while { |e| e <= v }.last
end
not_exceed(a,5) #=> 4
not_exceed(a,0) #=> 0
Upvotes: 2
Reputation: 42799
Assuming there's only sorted numbers like you mentioned in your comment, and that all numbers are integers, you can easily use the detect function without any other needs
[1,2,3,4,5,6].detect {|x| x >= minimum}
This will return the first number that is either equal or greater than the minimum number.
EDIT: ok i just noticed you need the opposite, in this case you'll need to reverse the array
[1,2,3,4,5].reverse.detect{ |x| x <= maximum }
Upvotes: 1
Reputation: 13673
If the input is not sorted, you could use this:
def highest_or_equal(value, collection)
collection_sorted_desc = collection.sort_by{|num| -num}
collection_sorted_desc.detect{|num| num <= value }
end
And here's the test for it:
require 'minitest'
require 'minitest/autorun'
class Test < Minitest::Test
def test_returns_lower
assert_equal highest_or_equal(5, [0, 2, 3, 4, 6]), 4
end
def test_returns_exact_match
assert_equal highest_or_equal(0, [0, 2, 3, 4, 6]), 0
end
end
Upvotes: 2