bsearch
in the form you are using (taking a block that yields integers) does not return the first of the identical values; it returns any that it stumbles upon, as it thinks they're identical. For example:
[1, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3, 4, 5].bsearch { |e| 2 - e.to_i }
# => 2.5
The returned value is somewhere in the middle of the 2.x
values, simply because bsearch
stumbled upon it first. If you want to return the first value, you have to use the alternate form, where the block yields booleans. From there, it is trivial to rework it for last. For example:
array = [1, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3, 4, 5]
indices = array.size.times.to_a
first_index = indices.bsearch { |i| array[i].to_i >= 2 }
last_index = indices.reverse.bsearch { |i| array[i].to_i <= 2 }
array[first_index..last_index]
# => [2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7]
If you don't need indices but the first and last element themselves, then this suffices:
first_element = array.bsearch { |e| e.to_i >= 2 }
last_element = array.reverse.bsearch { |e| e.to_i <= 2 }
For the hash part of the question though, your question is strange. For bsearch
form where the block yields a boolean (as you're using in that example), it is mandatory that the block yields false
for any element before the one you search, and true
thereafter. (Similarly, when block yields integers, it should return negative values followed by zeroes followed by positive values. In other words, the array is supposed to be sorted with respect to the predicate being searched for.) So by definition, the last element that fits your criterion (all elements without "test1"
are before all elements with "test1"
) must be hash.values.last
.