How does this recursive code find original index? - ruby

I don't understand how it's returning the correct answer
def bsearch(array, target)
return nil if array.length == 0
midpoint = array.length / 2
case target <=> array[midpoint]
when -1
bsearch(array.take(midpoint), target)
when 0
midpoint
when 1
index = bsearch(array.drop(midpoint + 1), target)
(index.nil?) ? nil : (midpoint + 1) + index
end
end
p bsearch([1, 3, 4, 5, 9], 5) # => 3
p bsearch([1, 2, 3, 4, 5, 6], 6) # => 5
Somehow it jumps from line 10 (where it should return midpoint ) to 13 where it somehow has the original array again?!
Here was my attempt but it fails by returning the midpoint of the last recursive version of the array
def bsearch(array, target)
return nil if array.length == 0
aim = array.length / 2
return nil if array.length == 1 && array[0] != target
case target <=> array[aim]
when 0
aim
when -1
bsearch(array[0...aim], target)
when 1
bsearch(array[aim..-1], target)
end
end

The major difference between the code in question and your attempt is this line:
(index.nil?) ? nil : (midpoint + 1) + index
in the last branch. And it actually makes the difference.
The reason is that the algorithm "shrinks" the array on each miss, so the array you're looking in becomes smaller. But you still need the target index in the larger, original array. So, each time you take the "right" subarray and drop the "left" one you need to adjust the index somehow. And this is exactly what that line does.

Related

Quick Sort - How to apply recursive and get output

I am working on a school exercise on Quick Sort.
I have succeded to do the first exercise which is
Challenge
Given an array 'array' and a number 'p' in the first cell in the
array, can you partition the array so that all elements greater than
'p' is to the right of it and all the numbers smaller than 'p' are to
it's left? For example, if given the following as input:
4 5 3 9 1 The first number 4 is the pivot, so you should put the
smaller numbers to the left, and the larger to the right, and output:
3 1 4 5 9 The array should otherwise remain in the same order.
Can you write code to partition an array?
Example p partition([4, 5, 3, 9, 1])
=> [3, 1, 4, 5, 9]
My code for the above in ruby is
def partition(array)
# write your code here
pivot = array.shift()
base = [pivot]
left = []
right = []
array.each { |e| if e < pivot
left.push(e)
else
right.push(e)
end
}
left + base + right
end
p partition([4, 5, 3, 9, 1])
# => [3, 1, 4, 5, 9]
The Challenge for which I am raising this Question is
The function should output like this
p some_function_name([5, 8, 1, 3, 7, 10, 2])
# => 2 3
# 1 2 3
# 7 8 10
# 1 2 3 5 7 8 10
I am trying for the last 36hrs how to apply the partition code above recursively on this challenge. During my 36hrs of research on the Quick Sort algorithm, I can make the code to give the result of a sorted array, but this challenge is asking to provide prints at certain conditions which I am not able to achieve.
Any help is much appreciated.
This one tried for pivot at end
def partition(array)
# write your code here
pivot = array[-1]
i = -1
j = 0
while j < array.length-1
if array[j] < pivot
i += 1
array[i], array[j] = array[j], array[i]
end
j += 1
end
array.insert(i+1, array.pop)
puts index = i+1
puts (array.take index).join(' ')
puts (array.drop index+1).join(' ')
end
partition([5, 8, 1, 3, 7, 10, 2])
this one, I am not able to find a condition for terminating recursive function
def partition(array)
# write your code here
pivot = array.shift()
base = [pivot]
left = []
right = []
array.each { |e| if e < pivot
left.push(e)
else
right.push(e)
end
}
left + base + right
if left.length < 2
return
end
partition(left)
end
p partition([5, 8, 1, 3, 7, 10, 2])
p partition([1, 3, 2])
p partition([8, 7, 10])
It's not clear to my why you want partition to be recursive. There is no real natural way to make it recursive in a simple way. You can introduce a recursive helper method, but I don't see that as an improvement. partition really doesn't need to be more complicated than this:
def partition(array, pivot)
return [], [] if array.empty?
array.partition(&pivot.method(:>))
end
If you absolutely must, you can make it recursive like this:
def partition(...) = partition_rec(...)
private def partition_rec(array, pivot, left = [], right = [])
return left, right if array.empty?
first = array.first
rest = array.drop(1)
if first < pivot
partition_rec(rest, pivot, left + [first], right)
else
partition_rec(rest, pivot, left, right + [first])
end
end
With this partition in place, we can easily write our quicksort:
def quicksort(array)
return array if array.length < 2
pivot = array.first
left, right = partition(array.drop(1), pivot)
quicksort(left) + [pivot] + quicksort(right)
end
Now, all we need to do is to also print the result at each recursive call. A simple way to do that would be with Kernel#p, which returns its argument, so we can just insert it without changing the return value:
def quicksort(array)
return array if array.length < 2
pivot = array.first
left, right = partition(array.drop(1), pivot)
p quicksort(left) + [pivot] + quicksort(right)
end
If we need to replicate the exact format of the string as given in the question, then we should use Kernel#puts instead:
def quicksort(array)
return array if array.length < 2
pivot = array.first
left, right = partition(array.drop(1), pivot)
(quicksort(left) + [pivot] + quicksort(right)).tap do |result|
puts result.join(' ')
end
end
Note that there is a big no-no in your code. Here, you are modifying the argument passed into partition:
array.shift()
Same here:
array.insert(i+1, array.pop)
You must not, ever, mutate an argument. In fact, you should avoid mutation at all, as much as possible. The only thing you are allowed to mutate is yourself, i.e. self. But even then, you should be careful.

Strange behavior with bsearch_index

tmp = [-3,3,5]
p "test: #{tmp.bsearch_index{|j| j == -3}}"
In the above code, I get response as nil. If I compare j against 3, or 5, it works. Why does bsearch_index does not consider very first element?
You need to write
tmp.bsearch_index{|n| n >= -3} #=> 0
This uses Array#bsearch_index's find minimum mode, which returns the smallest value in the array that satisfies the expression in the block. For example,
tmp.bsearch { |n| n >= 0 } #=> 3
tmp.bsearch_index { |n| n >= 0 } #=> 1
In this mode, to quote the doc for Array#bsearch, "the block must always return true or false, and there must be an index i (0 <= i <= ary.size) so that the block returns false for any element whose index is less than i, and the block returns true 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."
If the block were { |n| n == -3 } there would be no index i, 0 <= i <= tmp.size #=> 3 that has the property that tmp[j] == -3 is false for all j < i and true for all j >= 1.
If the block calculation were tmp[j] == 5 the requirement would be satisfied (for index 2) so the correct value would be returned. If the block calculation were tmp[j] == 3 the requirement would not be satisfied (tmp[2] == 3 #=> false); the fact that the correct index is returned is only due to the way the method has been implemented. If
tmp = [-3, 3, 5, 6]
then nil is returned for n == 3 as well as for n == -3:
tmp.bsearch_index { |n| n == 3 } #=> nil
bsearch has a second mode, find any mode. (See the doc for Array#bsearch for details.) In this case we might write one of the following:
tmp.bsearch_index { |n| -3 - n } #=> 0
tmp.bsearch_index { |n| 3 - n } #=> 1
tmp.bsearch_index { |n| 5 - n } #=> 2
tmp.bsearch_index { |n| 4 - n } #=> nil
This mode would be useful here if nil were to be returned when no element in the array evaluates to zero in the block. In other contexts it has a multitude of uses.

Using Ruby's array.bsearch_index(), is there an elegant way to look for the closest index in array so that array[i] is less than a number n?

I had to use something like
arr = [10, 20, 50, 80, 110]
(arr.bsearch_index{|a| a >= 50} || arr.length) - 1 # => 1
(arr.bsearch_index{|a| a >= 2000} || arr.length) - 1 # => 4
with the return value -1 meaning there is no such index. What if the numbers could be float, so you cannot look for 49 instead when n is 50. The code right now is a little bit messy. Is there a more elegant way to do it?
(Maybe it is just how bsearch_index() does it: to return nil when not found... so we just have to use bsearch(){ } || arr.length to convert it back to strictly numbers -- so that's just the way it is. bsearch_index has to either return only numbers or it can return nil as a design decision and it chose to return nil. But I am not sure if we just have to use the code above. Maybe the find-any mode of bsearch_index or some kind of way can do it and is more elegant.)
P.S. it might be interesting to use a reverse() operation or negating every element or something, but since those are O(n), it defeats the purpose of using a O(lg n) solution using binary search and we can just do a linear search.
In order to express "less than" directly (i.e. via <), you have to reverse the array:
rindex = arr.reverse.bsearch_index { |a| a < 50 }
#=> 4
To un-reverse the index:
arr.size - rindex - 1
#=> 1
In one line:
arr.reverse.bsearch_index { |a| a < 50 }.yield_self { |i| arr.size - i - 1 if i }
The the modifier-if handles i being nil, e.g. a < 10
Or simply use a descending array in the first place:
arr = [110, 80, 50, 20, 10]
arr.bsearch_index { |a| a < 50 } #=> 3
arr.bsearch_index { |a| a < 2000 } #=> 0
Not completely clear, but for a sorted Array I guess:
arr.bsearch_index{ |a| a >= n }.then { |i| i ? i - 1 : -1}
Or, since nil.to_i #=> 0
arr.bsearch_index{ |a| a >= n }.to_i - 1
# n = 49 #=> 1
# n = 50 #=> 1
# n = 51 #=> 2
# n = 111 #=> -1

Speeding up solution to algorithm

Working on the following algorithm:
Given an array of non-negative integers, you are initially positioned
at the first index of the array.
Each element in the array represents your maximum jump length at that
position.
Determine if you are able to reach the last index.
For example:
A = [2,3,1,1,4], return true.
A = [3,2,1,0,4], return false.
Below is my solution. It tries every single potential step, and then memoizes accordingly. So if the first element is three, the code takes three steps, two steps, and one step, and launches three separate functions from there. I then memoized with a hash. My issue is that the code works perfectly fine, but it's timing out for very large inputs. Memoizing helped, but only a little bit. Am I memoizing correctly or is backtracking the wrong approach here?
def can_jump(nums)
#memo = {}
avail?(nums, 0)
end
def avail?(nums, index)
return true if nums.nil? || nums.empty? || nums.length == 1 || index >= nums.length - 1
current = nums[index]
true_count = 0
until current == 0 #try every jump from the val to 1
#memo[index + current] ||= avail?(nums, index + current)
true_count +=1 if #memo[index + current] == true
current -= 1
end
true_count > 0
end
Here's a 𝑂(𝑛) algorithm:
Initialize 𝑚𝑎𝑥 to 0.
For each number 𝑛𝑖 in 𝑁:
If 𝑖 is greater than 𝑚𝑎𝑥, neither 𝑛𝑖 nor any subsequent number can be reached, so
return false.
If 𝑛𝑖+𝑖 is greater than 𝑚𝑎𝑥, set 𝑚𝑎𝑥 to 𝑛𝑖+𝑖.
If 𝑚𝑎𝑥 is greater than or equal to the last index in 𝑁
return true.
Otherwise return false.
Here's a Ruby implementation:
def can_jump(nums)
max_reach = 0
nums.each_with_index do |num, idx|
return false if idx > max_reach
max_reach = [idx+num, max_reach].max
end
max_reach >= nums.size - 1
end
p can_jump([2,3,1,1,4]) # => true
p can_jump([3,2,1,0,4]) # => false
See it on repl.it: https://repl.it/FvlV/1
Your code is O(n^2), but you can produce the result in O(n) time and O(1) space. The idea is to work backwards through the array keeping the minimum index found so far from which you can reach index n-1.
Something like this:
def can_jump(nums)
min_index = nums.length - 1
for i in (nums.length - 2).downto(0)
if nums[i] + i >= min_index
min_index = i
end
end
min_index == 0
end
print can_jump([2, 3, 1, 1, 4]), "\n"
print can_jump([3, 2, 1, 0, 4]), "\n"

Array iteration back to last position

I was trying to sort an array. I know I can use sort_by, but I was trying to implement it by myself. Here is the code:
def insertionSort(ar)
for i in 1..ar.length - 1
j = i
k = j - 1
while ar[j] < ar[k] && j != 0 do
temp = ar[k]
ar[k] = ar[j]
ar[j] = temp
k -= 1
j -= 1
end
puts ar.join(" ")
end
end
When the program reaches the first line and decrements, it came back to the last position, so the while doesn't break ever. I solved this with a condition in while to verify if is the first position.
Is this normal? Why does this happen? Is an array like a cycle list, and when I try to reach the -1 position, am I using the last position?
No, arrays in Ruby(like other languages) is NOT a cycle list.
It's syntax sugar. A negative index is relative to the end of the array. an index of -1 indicates the last element of the array, -2 is the next-to-last element in the array, and so on. For example:
arr = ['foo', 'bar', 42, :sym]
arr[-1] is the same as arr[3], arr[-2] is the same as arr[2], etc.
... it came back to the last position
When accessing elements, negative indices start counting from the end, i.e. ar[-1] is the last element.
... so the while doesn't break ever.
If Ruby did not allow negative indices, you would end up with an IndexError. Either way, you need j != 0 to ensure that your loop breaks when reaching the first element.
Here's another way to write it, using an upto and a downto loop:
def insertion_sort(a)
1.upto(a.size-1) do |i|
i.downto(1) do |j|
break unless a[j] < a[j-1]
a[j], a[j-1] = a[j-1], a[j]
end
end
end
array = [3, 7, 4, 9, 5, 2, 6, 1]
insertion_sort(array)
array #=> [1, 2, 3, 4, 5, 6, 7, 9]

Resources