I don't understand why bsearch method returns nil, when array has 2 values.
arr = [1, 2]
arr.bsearch { |el| el < 2 }
bsearch
doc
You just need to read the doc for Array#bsearch carefully. It states that are two forms or modes of bsearch
: "find-minimum mode and find-any mode. In either case, the elements of the array must be monotone (or sorted) with respect to the block." With find-minimum mode the block returns true
or false
; with find-any mode a value of the same class as the elements of the array is returned by the block. You are therefore using the find-minimum mode.
Also from the doc, in find-minimum mode "there must be an index i, 0 <= i <= ary.size
so that:
false
for any element whose index is less than i
; andtrue
for any element whose index is greater than or equal to i
.This method returns the i-th element. If i
is equal to ary.size
, it returns nil."
Examining your code
Now lets examine your statement, modified slightly:
arr = [1, 2, 3]
arr.bsearch { |el| el < 2 } #=> nil
We wish to find an index 0
, 1
or 2
that satisfies the requirements imposed by the use of bsearch
.
Suppose the index of interest were 0
. As there are no indices less than zero, we test whether arr[1] < 2
and arr[2] < 2
(2 < 2
and 3 < 2
) are both true
. In fact both are false
. The index 0
is therefore removed from consideration.
Now suppose the index referred to in the doc were 1
. We require that arr[0] < 2
be false
, but it is true
. So we can rule out index 1
.
For the index to equal 2
to we require both arr[0] < 2
and arr[1] < 2
to be false
but in fact both are true
.
As there is no index that satisfies the requirement for using besearch
nil
is returned.
Correcting your code
It is evident that, for arr = [1, 2, 3]
, the expression must be written as follows.
arr.bsearch { |el| el >= 2 } #=> 2
arr.bsearch { |el| el > 2 } #=> 3
arr.bsearch { |el| el > 0 } #=> 1
arr.bsearch { |el| el > 3 } #=> nil
Other examples
Here are some more examples where the array contains integers.
ary = [2, 4, 6]
ary.bsearch { |el| el > -99 } #=> 2
ary.bsearch { |el| el > 1 } #=> 2
ary.bsearch { |el| el > 2 } #=> 4
ary.bsearch { |el| el >= 2 } #=> 2
ary.bsearch { |el| el >= 6 } #=> 6
ary.bsearch { |el| el > 6 } #=> nil
The array can be non-increasing
Note that the array need not be non-deceasing, only monotone, which means non-deceasing or non-increasing. Here is an example where the array is non-increasing (it is in fact strictly decreasing). The block calculation must of course be adjusting accordingly.
ary = [6, 4, 2]
ary.bsearch { |el| el <= 7 } #=> 6
ary.bsearch { |el| el <= 99} #=> 6
ary.bsearch { |el| el < 5 } #=> 4
ary.bsearch { |el| el <= 4 } #=> 4
ary.bsearch { |el| el < 4 } #=> 2
ary.bsearch { |el| el <= 2 } #=> 2
ary.bsearch { |el| el < 2 } #=> nil
ary.bsearch { |el| el <= 1 } #=> nil
The array may contain non-numeric values
The only requirement is that pairs of elements can be compared with the spaceship operator, <=>
. a <=> b
returns -1
if a < b
, returns 0
if a == b
and returns 1
if a > b
.
In the case of strings, for example, String#<=> is used by bsearch
. Here are some examples.
ary = ['brush', 'keep', 'task']
ary.bsearch { |s| s >= 'hi' } #=> "keep"
ary.bsearch { |s| s >= 'align' } #=> "brush"
ary.bsearch { |s| s >= 'mama' } #=> "task"
ary.bsearch { |s| s >= 'tar' } #=> "task"
ary.bsearch { |s| s >= 'tax' } #=> "nil"