using built-in Array#bsearch for collection bisect_left; bisect_right
404pnf opened this issue · comments
please ignore the following function definiton. They are wrong!
Array#bsearch return nil in find minimum mode. And one has to compare x with a[0] and a[-1] to decide what to return as the result. Not worth it.
The original solution is correct and simpler.
def my_bisect_left(a, x, opts={})
return nil if a.nil?
return 0 if a.empty?
lo = (opts[:lo] || opts[:low]).to_i
hi = opts[:hi] || opts[:high] || a.length
idx = if block_given?
a[lo..hi].bsearch { |e| yield(x) <= yield(e) }
else
a[lo..hi].bsearch { |e| x <= e }
end
# if non is found, bsearch return nil
if idx
idx - 1
else
0 # insert at the beginning since all elements are bigger than us
end
end
# ==> nil
my_bisect_left (1..10000000).to_a, 56789
# ==> 56788
Benchmark.bmbm do |x|
x.report('orig') { bisect_left (1..10000000).to_a, 56789 }
x.report('new') { my_bisect_left (1..10000000).to_a, 56789 }
end
# =Rehearsal ----------------------------------------
# =orig 0.450000 0.030000 0.480000 ( 0.488831)
# =new 0.460000 0.030000 0.490000 ( 0.492856)
# =------------------------------- total: 0.970000sec
# = user system total real
# =orig 0.460000 0.030000 0.490000 ( 0.488632)
# =new 0.460000 0.030000 0.490000 ( 0.493830)
# ==> [#<Benchmark::Tms:0x007f8c1a80f428 @label="orig", @real=0.488632, @cstime=0.0, @cutime=0.0, @stime=0.03, @utime=0.4600000000000002, @total=0.4900000000000002>, #<Benchmark::Tms:0x007f8c1a80fe50 @label="new", @real=0.49383, @cstime=0.0, @cutime=0.0, @stime=0.03, @utime=0.45999999999999996, @total=0.49>]
Similiarly, bisect_right
if idx
idx + 1
else
-1
end
The doc says:
- (Object) bisect_left(a, x, opts = {})
Return the index where to insert item x in list a, assuming a is sorted.
It would be more appropriate to say "assuming a is in monotonically non-descending order."
We could check the argument a before call the method, or we could get wrong result
bisect_left (1..10000000).to_a, 56789
# ==> 56788
bisect_left (1..10000000).to_a.reverse, 1
# ==> 0
## THIS IS WRONG, BEAUSE THE COLLECTION GIVEN IS IN DESCENDING ORDER
Therefore I propose 4 predicts: ascending?, descending?, non_descending? non_ascending?
Then we can check input
def my_bisect_left(a, x, opts={})
return nil if a.nil?
return 0 if a.empty?
rasie ArgumentError unless non_descending?(a)
....
end
Please feel free to create a Pull Request. I'm happy to have the help.
bisect_left and similar functions all take a opt arguments: (a, x, opts={}). I am afraid by using it casually will cause the result collection not sorted.
e.g
col = [ 1, 2, 3, 4, 5, 6]
bisect_left col, 1, { low: 3 }
=> 3
Then, insort_left! will insert 1 at index 3.
[1, 2, 3, 1, 4, 5, 6] # now col isn't sorted
I don't know if I understand it correctly. If so, I would suggest removing opt={} from bisect__, insort__ functions.
I'm comfortable with your suggestion. I have a bad habit of adding options hashes to method signatures then not using them. Please feel free to remove any you see.
Also, I've added you as a contributor to this repo. Feel free to merge your changes directly. Later this evening I'll start a new Issue where we discuss a roadmap for the next release. There are a few updates I'd like to make.
Thank you again for your help!
I am still not confident to merge anything to master without you green light it. I will publish to dev branch and send PR instead.
Thank you for your trust.