How to get Index of element if found using Binary Search(Recursively) - javascript

function getMiddle({right,left}) {
return Math.floor((left + right) / 2);
}
function RecursivelyBinarySearch(arr, search) {
let left = 0;
let right = arr.length - 1;
let middle = getMiddle({left,right});
if (right >= left) {
if (arr[middle] === search) {
return middle;
} else if (arr[middle] < search) {
return RecursivelyBinarySearch(arr.slice(middle + 1, arr.length), search);
} else {
return RecursivelyBinarySearch(arr.slice(0, middle - 1), search)
}
}
return -1;
}
let result = RecursivelyBinarySearch([1,5,7,9,10,20], 20);
console.log(result);
// RecursivelyBinarySearch([1,5,7,9,10,20], 20)
RecursivelyBinarySearch returns 0, but I want it to be 5,
is it possible to do this without passing the orignal array?

Welcome to stack overflow! It's a strange request to not pass the original array. But I assume you have a good reason for it :)
Regarding your snippet, you have an error in your Binary Search function. For the last else case you need to pass arr.slice(0, middle) instead of middle - 1.
Now for your original question, we need to pass the first index of array every time, since this information is critical to find the original index (It is lost when you do arr.slice). You can modify your function like below to achieve the desired result.
function getMiddle({right,left}) {
return Math.floor((left + right) / 2);
}
function RecursivelyBinarySearch(arr, search, first_idx) {
let left = 0;
let right = arr.length - 1;
let middle = getMiddle({left,right});
if (right >= left) {
if (arr[middle] === search) {
return middle + first_idx;
} else if (arr[middle] < search) {
return RecursivelyBinarySearch(arr.slice(middle + 1, arr.length), search, middle + first_idx + 1);
} else {
return RecursivelyBinarySearch(arr.slice(0, middle), search, first_idx)
}
}
return -1;
}
let result = RecursivelyBinarySearch([1,5,7,9,10,20], 20, 0);
console.log(result);

Related

How to make the checking more robust and shorter?

I wrote this code, but I dont uderstand why it works this way, especially using the third and fourth examples as input. Why the 'middle' position remains so behind? -in the number 5 (or index 2) using the [1, 3, 5, 6] array and the number 7 as target??
And how to make it better??
I cant think of a shorter or better way to check the if/elses when the target value is not in the array, especially if the input is an array with only one value and the target to find is 0.
Maybe a better way to check the possible different scenarios.
Or how to better check the correct place of the target without so many if/elses.
For example, is this code good enough to a coding interview? What can I do better?
from LeetCode:
Search Insert Position
Given a sorted array of distinct integers and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
You must write an algorithm with O(log n) runtime complexity.
Example 1:
Input: nums = [1,3,5,6], target = 5
Output: 2
Example 2:
Input: nums = [1,3,5,6], target = 2
Output: 1
Example 3:
Input: nums = [1,3,5,6], target = 7
Output: 4
And especially this one:
Example 4:
Input: nums=[1], target= 0
Constraints:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums contains distinct values sorted in ascending order.
-104 <= target <= 104
this is my code:
/**
* #param {number[]} nums
* #param {number} target
* #return {number}
*/
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length -1;
let middle;
while(left <= right){
middle = nums.length>1 ? (Math.floor(left + (right - left)/2)) : 0;
if(nums[middle] === target){
return middle;
} else if(target < nums[middle]){
right = middle -1;
} else {
left = middle + 1;
}
}
console.log(`Middle: ${middle}`);
console.log(`Middle-1: ${nums[middle-1]}`);
if(nums.lenght === 1){
return 0;
} else {
if((target < nums[middle] && target > nums[middle-1] )|| (target < nums[middle] && nums[middle-1] === undefined)){ /*
No more items to the left ! */
return middle;
} else if(target<nums[middle] && target<nums[middle-1]){
return middle-1;
} else if(target > nums[middle] && target > nums[middle + 1]) {
return middle + 2; /* Why the 'middle' is so behind here? using the THIRD example as input?? */
} else {
return middle + 1;
}
}
};
Problem
The issue lies in the variable you are checking for after the while loop.
In a "classical" binary search algorithm, reaching beyond the while loop would indicate the needle isn't present in the haystack. In case of this problem, though, we simply need to return right + 1 in this place in the code (rather than checking the middle).
Your code adjusted for this:
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length -1;
let middle;
while(left <= right){
middle = nums.length>1 ? (Math.floor(left + (right - left)/2)) : 0;
if(nums[middle] === target){
return middle;
} else if(target < nums[middle]){
right = middle -1;
} else {
left = middle + 1;
}
}
return right + 1;
};
console.log(
searchInsert([1,3,5,6], 5),
searchInsert([1,3,5,6], 2),
searchInsert([1,3,5,6], 7),
searchInsert([1], 0)
);
Side note
Also, the below is redundant...
middle = nums.length>1 ? (Math.floor(left + (right - left)/2)) : 0;
...and can be shortened to:
middle = Math.floor((left + right) / 2);
Revised variant
const searchInsertProblem = (arr, n) => {
let start = 0;
let end = arr.length - 1;
while (start <= end) {
const middle = Math.floor((start + end) / 2);
if (arr[middle] === n) { return middle; } // on target
if (arr[middle] > n) { end = middle - 1; } // overshoot
else { start = middle + 1; } // undershoot
}
return end + 1;
};
console.log(
searchInsertProblem([1,3,5,6], 5),
searchInsertProblem([1,3,5,6], 2),
searchInsertProblem([1,3,5,6], 7),
searchInsertProblem([1], 0)
);

Binary Search for multiple items within a Range (Log time filter)

I have an Array of Log items, already sorted by their timestamp (number of milliseconds since 1970). Now I want to filter them by a specific time range, so I think of Binary Search, however this variant is different than all variants I knew before as I need to find a range within a range. Note that there may be none or multiple items at the value edges.
I came up with this to reduce one range requirement but still don't know how to get to the first/last edge items:
filterByTime(min: number, max: number): LogItem[] {
const items = this.items;
const len = items.length;
if (min === undefined && max === undefined) {
return items.slice(0, len - 1);
}
min = min || Number.MIN_VALUE;
max = max || Number.MAX_VALUE;
const fn = (a: LogItem) => {
if (a.time < min) {
return -1;
} else if (a.time > max) {
return 1;
} else {
return 0;
}
};
const lowerBound = this.binarySearchBound(fn, 0, len, true);
if (lowerBound == -1) { return []; }
const upperBound = this.binarySearchBound(fn, 0, len, false);
return items.slice(lowerBound, upperBound + 1);
}
binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
const items = this.items;
if (items.length == 0) {
return -1;
}
if (isLowerBound && compareFn(items[0]) == 0) {
return 0;
} else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
return items.length - 1;
}
while (left <= right) {
const mid = (left + right) / 2;
const item = this.items[mid];
const compare = compareFn(item);
if (compare < 0) {
left = mid + 1;
} else if (compare > 0) {
right = mid - 1;
} else {
// What to do now?
}
}
return -1;
}
Worst case scenario, I can just do a linear search from the edge since I can assume there are not that much items at the edge but surely there is a better way I didn't think of but then I may have to iterate through the whole result set if mid falls in the middle of the result set.
EDIT for adding a note: It's possible for min or max is undefined (and could be both, in which case I can just set an if and return the copy of the whole array). Is it better to just substitute it with MIN_VALUE and MAX_VALUE if they are undefined, or is there a better way to handle that case?
I would suggest the following:
Write two binary search functions, as the execution time is then not hampered by passing and checking the isLowerBound boolean.
Make the returned upperBound to mean the next index after the potential last index that belongs to the range. This corresponds with how arguments work with native functions like slice.
Don't use -1 as a special index. If coded well, an empty range will come out of the two binary searches any way and give an empty array as result
Make the compare function to work with 2 parameters, so you can actually search for either the min or the max value.
Yes, I would use MIN_VALUE and MAX_VALUE as defaults and not test for boundary cases. If boundary cases happen often, it might be worth to include those checks, but in general be aware that these checks will then be executed for every filter, which may bring down the average execution time.
Here is the suggested implementation with integer data (instead of objects) to keep it simple. In order to have it run in a snippet I also removed the type references:
function filterByTime(min=Number.MIN_VALUE, max=Number.MAX_VALUE) {
const fn = (a, b) => a - b; // simplified (should be a.time - b.time)
const lowerBound = this.binarySearchLowerBound(fn, 0, this.items.length, min);
const upperBound = this.binarySearchUpperBound(fn, lowerBound, this.items.length, max);
return this.items.slice(lowerBound, upperBound);
}
function binarySearchLowerBound(compareFn, left, right, target) {
while (left < right) {
const mid = (left + right) >> 1;
if (compareFn(this.items[mid], target) < 0) {
left = mid + 1;
} else { // Also when equal...
right = mid;
}
}
return left;
}
function binarySearchUpperBound(compareFn, left, right, target) {
while (left < right) {
const mid = (left + right) >> 1;
if (compareFn(this.items[mid], target) <= 0) { // Also when equal...
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
// Demo with simplified data (instead of objects with time property)
this.items = [1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8, 8];
console.log(this.filterByTime(2, 4));
console.log(this.filterByTime(4, 5));
Combined the variants on this article, I merged first and last code into a single function:
filterByTime(items: LogItem[], min: number, max: number): LogItem[] {
const len = items.length;
if (len == 0) {
return [];
}
if (min === undefined && max === undefined) {
return items.slice(0, len - 1);
}
min = min || Number.MIN_VALUE;
max = max || Number.MAX_VALUE;
const fn = (a: LogItem) => {
if (a.time < min) {
return -1;
} else if (a.time > max) {
return 1;
} else {
return 0;
}
};
const lowerBound = this.binarySearchBound(fn, 0, len, true);
if (lowerBound == -1) { return []; }
const upperBound = this.binarySearchBound(fn, 0, len, false);
return items.slice(lowerBound, upperBound + 1);
}
binarySearchBound(compareFn: (a: LogItem) => number, left: number, right: number, isLowerBound: boolean): number {
const items = this.items;
if (items.length == 0) {
return -1;
}
if (isLowerBound && compareFn(items[0]) == 0) {
return 0;
} else if (!isLowerBound && compareFn(items[items.length - 1]) == 0) {
return items.length - 1;
}
let result = -1;
while (left <= right) {
const mid = (left + right) / 2;
const item = this.items[mid];
const compare = compareFn(item);
if (compare < 0) {
left = mid + 1;
} else if (compare > 0) {
right = mid - 1;
} else {
result = mid;
if (isLowerBound) {
right = mid - 1;
} else {
left = mid + 1;
}
}
}
return result;
}

Binary search using recursion results in correct number but wrong index in Javascript

I am a newbie to algorithm, I tried using recursion in binary search, I do get the result and it is correct, but the index of the result is wrong, I've tried over and over but just cannot the problem causing this. Here is my code:
function binarySearch(arr, num, pivot) {
const center = Math.round(arr.length / 2);
if (arr.length == 1 && arr[0] == num) {
return `num is :${arr} pivot is : ${pivot}`;
} else if (arr.length == 1 && arr[0] !== num) {
return -1;
}
if (pivot == undefined) {
pivot = center;
}
// console.log(`center is :${center}, pivot is ${pivot}`);
if (arr[center] == num) {
return `num is :${arr[center]} pivot is : ${[pivot - 1]}`;
} else if (arr[center] < num) {
return binarySearch(arr.slice(center, arr.length), num, pivot + 1);
} else {
return binarySearch(arr.slice(0, center), num, pivot - 1);
}
}
thank you for the response in advance!

How to count number of ways to parenthesize boolean expression string to evaluate to desired result

Straight out of CTCI, 8.14: Given a boolean expression consisting of the symbols 0 (false), 1 (true), & (AND), | (OR), and ^(XOR), and a desired boolean result value result, implement a function to count the number of ways of parenthesizing the expression such that it evaluates to result.
I'm attempting a brute force approach that calculates every single possible combo, if matches desired result, add it to an array(combos) and return that result length. It seems to work for most expressions, but not the 2nd example given. What do I seem to be missing?
function countEval(s, goalBool, combos = []) {
// on first call make s into array since theyre easier to work with
if (!(s instanceof Array)) {
// and turn 1s and 0s into their bool equivalent
s = s.split('').map((item) => {
if (item === '1') {
return true;
} else if (item === '0'){
return false;
} else {
return item;
}
});
}
if (s.length === 1 && s[0] === goalBool) {
combos.push(s[0]); // can be anything really
} else {
for (let i = 0; i < s.length - 2; i = i + 2) {
// splice out the next 3 items
const args = s.splice(i, 3);
// pass them to see what they evaluate too
const result = evalHelper(args[0], args[1], args[2]);
// splice that result back in s array
s.splice(i, 0, result);
// pass that array to recurse
countEval(s, goalBool, combos);
// remove said item that was just put in
s.splice(i, 1);
// and reset array for next iteration
s.splice(i, 0, ...args);
}
}
return combos.length;
}
function evalHelper(a, op, b) {
if (op === '|') {
return a || b;
} else if (op === '&') {
return a && b;
} else if (op === '^') {
return a !== b;
}
}
With the 2 examples given it works for the first one, but not the second...
console.log(countEval('1^0|0|1', false)); // 2, correct
console.log(countEval('0&0&0&1^1|0', true)); // 30, should be 10!?!?!
The Bug
Your program is not taking into account overlap.
Example
Consider your program when s = '1|1|1|1'.
In one of the depth-first search iterations, your algorithm will make the reduction s = (1|1)|1|1. Then in a deeper recursive level in the same search, your algorithm will make the reduction s = (1|1)|(1|1). Now s is fully reduced, so you increment the length of combos.
In a different depth-first search iteration, your algorithm will first make the reduction s = 1|1|(1|1). Then in a deeper recursive level in the same search, your algorithm will make the reduction s = (1|1)|(1|1). Now s is fully reduced, so you increment the length of combos.
Notice that for both cases, s was parenthesized the same way, thus your program does not take into account overlap.
A Better Solution
A lot of times, when a problem is asking the number of ways something can be done, this is usually a big indicator that dynamic programming could be a potential solution. The recurrence relation to this problem is a bit tricky.
We just need to pick a "principle" operator, then determine the number of ways the left and right side could evaluate to true or false. Then, based on the "principle" operator and the goal boolean, we can derive a formula for the number of ways the expression could evaluate to the goal boolean given that the operator we picked was the "principle" operator.
Code
function ways(expr, res, i, j, cache, spaces) {
if (i == j) {
return parseInt(expr[i]) == res ? 1 : 0;
} else if (!([i, j, res] in cache)) {
var ans = 0;
for (var k = i + 1; k < j; k += 2) {
var op = expr[k];
var leftTrue = ways(expr, 1, i, k - 1, cache);
var leftFalse = ways(expr, 0, i, k - 1, cache);
var rightTrue = ways(expr, 1, k + 1, j, cache);
var rightFalse = ways(expr, 0, k + 1, j, cache);
if (op == '|') {
if (res) {
ans += leftTrue * rightTrue + leftTrue * rightFalse + leftFalse * rightTrue;
} else {
ans += leftFalse * rightFalse;
}
} else if (op == '^') {
if (res) {
ans += leftTrue * rightFalse + leftFalse * rightTrue;
} else {
ans += leftTrue * rightTrue + leftFalse * rightFalse;
}
} else if (op == '&') {
if (res) {
ans += leftTrue * rightTrue;
} else {
ans += leftFalse * rightFalse + leftTrue * rightFalse + leftFalse * rightTrue;
}
}
}
cache[[i, j, res]] = ans;
}
return cache[[i, j, res]];
}
function countEval(expr, res) {
return ways(expr, res ? 1 : 0, 0, expr.length - 1, {});
}

JSLint: how not to make THIS function within a loop

In my angular application, I am using a loop to find in an object the nearest value to a given number and return its key.
For example, I want the closest values to 0.5:
for (var j in nums) {
if (0.5 > nums[j]) var prev = nums[j];
else if (0.5 <= nums[j]) {
// If the current number is equal to 0.5, or immediately higher, stores that number
// and stops the for each() loop
var next = nums[j];
// Get the value
var percentage = (Math.abs(0.5 - prev) < Math.abs(next - 0.5)) ? prev : next;
// Get the key from the value
$scope.seventyfive = parseInt('0' + Object.keys(nums).filter(function(key) {return nums[key] === percentage;})[0], 10);
break;
}
}
JSLint is pointing out that I shouldn't make functions within a loop, so I am trying to avoid that with:
filterPct = function (nums, pct) {
return function () {
return nums[key] === pct;
};
}
and
for (var j in nums) {
if (0.5 > nums[j]) var prev = nums[j];
else if (0.5 <= nums[j]) {
// If the current number is equal to 0.5, or immediately higher, stores that number
// and stops the for each() loop
var next = nums[j];
// Get the value
var percentage = (Math.abs(0.5 - prev) < Math.abs(next - 0.5)) ? prev : next;
// Get the key from the value
$scope.seventyfive = parseInt('0' + Object.keys(nums).filter(filterPct(nums, percentage))[0], 10);
break;
}
}
But this is returning 0 instead of the right value. I am positive I am missing something obvious, but I obviously need another pair of eyes...
UPDATE: Thanks to the support I received, this is the error-proof version of the code above:
filterPct = function (nums, pct) {
return function (key) {
return nums[key] === pct;
};
};
// Store the value with 50% Confidence
for (i in nums) {
if (nums.hasOwnProperty(i)) {
if (0.5 > nums[i]) {
prev = nums[i];
} else if (0.5 <= nums[i]) {
// If the current number is equal to 0.5, or immediately higher, stores that number
// and stops the for each() loop
next = nums[i];
// Get the value
percentage = (Math.abs(0.5 - prev) < Math.abs(next - 0.5)) ? prev : next;
// Get the key from the value
$scope.fifty = parseInt('0' + Object.keys(nums).filter(filterPct(nums, percentage))[0], 10);
break;
}
}
}
filterPct = function (nums, pct) {
return function () {
return nums[key] === pct;
};
}
You forgot to define key (it should be the first argument of the inner function).

Categories