Project

General

Profile

Actions

Feature #19075

closed

Binary searching for the last element

Added by kyanagi (Kouhei Yanagita) over 1 year ago. Updated 8 months ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:110473]

Description

My latest proposal is https://bugs.ruby-lang.org/issues/19075#note-6.
I will leave the initial proposal below.


PR: https://github.com/ruby/ruby/pull/6611

(I'm going to talk about Array here, but the same argument can be made for Range. If Array#bsearch_last is acceptable, I will work also for Range.)

Ruby's bsearch returns the first element which satisfies the given block.

# Search the first element greater than 18
array = [10, 15, 20, 25]
array.bsearch { |x| x > 18 } # => 20

If we want the last element, we need to invert the condition and step backward.

# Search the last element less than 18
array = [10, 15, 20, 25]
index = array.bsearch_index { |x| !(x < 18) }
array[index-1] # => 15

Of course, we need to consider nil and the boundary.

# Search the last element less than 100
index = array.bsearch_index { |x| !(x < 100) } # => nil
if index.nil?
  array.last # => 25
else
  array[index-1]
end
# Search the last element less than 0
index = array.bsearch_index { |x| !(x < 0) } # => 0
if index.nil?
  array.last
elsif index == 0
  nil
else
  array[index-1]
end

This is where mistakes can easily be made, so I propose Array#bsearch_last and Array#bsearch_last_index.

Array#bsearch_last returns the last element which satisfies the given block.

Array#bsearch requires that all false-evaluating elements precede all true-evaluating elements. As is clear from the meaning of the method, conversely to bsearch, bsearch_last requires that all true-evaluating elements precede all false-evaluating elements. (If bsearch_last is acceptable, the name "find-minimum mode" should be changed.)

array = [10, 15, 20, 25]
array.bsearch_last { |x| x < 18 }  # => 15
array.bsearch_last { |x| x < 100 } # => 25
array.bsearch_last { |x| x < 0 }   # => nil

There are several possible options for find-any mode.

(1) bsearch_last does not support find-any mode.

A block for bsearch_last must return true, false or nil.

[1, 2, 3].bsearch_last { 0 } # => TypeError

My pull request tentatively includes this implementation.

(2) bsearch_last supports find-any mode and it behaves like bsearch.

bsearch with find-any mode returns an element, for which the block returns zero.
If multiple elements satisfy the condition, it is not determined which of them will be returned.

It is conceivable that bsearch_last behaves in the same way as bsearch.

# current behavior
# It is not specified whether `:b`, `:c`, or `:d` is returned.
[[1,:a], [2, :b], [2, :c], [2, :d], [3, :e]].bsearch { |a, b| 2 <=> a } # => [2, :c]

(3) bsearch_last supports find-any mode and returns the last element. Make bsearch return the first element.

Change the behavior of bsearch to return the first element for which the block returns zero.
bsearch_last returns the last element for which the block returns zero.

# Change it like this:
[[1,:a], [2, :b], [2, :c], [2, :d], [3, :e]].bsearch { |a, b| 2 <=> a } # => [2, :b]
[[1,:a], [2, :b], [2, :c], [2, :d], [3, :e]].bsearch_last { |a, b| 2 <=> a } # => [2, :d]

(If this option is adopted, the name "find-any mode" should be renamed.)

Actions

Also available in: Atom PDF

Like1
Like0Like0Like0Like0Like0Like1Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0