What is the key point in deciding where to insert an element using binary search ?
Using binary search when the element is present returns its index.
function arr() {
this.arr = [5, 6, 8];
this.insert = function(element) {
var low = 0;
var high = this.arr.length - 1;
while (low <= high) {
var mid = parseInt((low + high) / 2);
if (element == this.arr[mid]) {
return mid;
} else if (element > this.arr[mid]) {
low = mid + 1;
} else {
high = mid - 1
}
}
return -1
}
}
var vector = new arr();
var x = vector.insert(6); // return 1
alert(x)
Here I can use splice to insert element on index 1, but what if do
var x = vector.insert(7);
7 isn't present in the array but should be inserted on 2th index.
How could I determine that ?
Try with something like this:
function arr() {
this.arr = [5, 6, 8];
this.insert = function(element) {
var low = 0;
var high = this.arr.length - 1;
while (low <= high) {
var mid = parseInt((low + high) / 2);
if (element == this.arr[mid]) {
return mid;
} else if (element > this.arr[mid]) {
low = mid + 1;
} else {
high = mid - 1
}
}
return mid;
}
}
You probably want to insert the element if it's not found inside your array, using splice(). Also I made small edits to your code.
The index of insertion is determined by your mid variable.
function arr() {
this.arr = [5, 6, 8];
this.insert = function(element) {
var low = 0;
var high = this.arr.length;
while (low <= high) {
var mid = Math.floor((low + high) / 2);
if (element == this.arr[mid]) {
return mid;
} else if (element > this.arr[mid]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
this.arr.splice(mid, 0, element);
return mid;
}
}
var vector = new arr();
var x = vector.insert(6);
console.log(x); // 1
var x = vector.insert(7);
console.log(x); // 2
var x = vector.insert(9);
console.log(x); // 4
console.log(vector.arr); // Array [ 5, 6, 7, 8, 9 ]
document.write('<p>Check your console :-)</p>');
Some binary search implementations return the one's complement of the insertion spot when the item isn't found. In your case, the item would be inserted at index 2 if it were found. But since it's not found, you return -3. The caller sees that the return value is less than 0, does a one's complement, and then inserts at that position.
Example:
result = find(7) // find the value in the array
if (result < 0) // if not found
{
index = ~result // complement the result
insert(7, index) // and insert (probably using splice)
}
In your code, rather than return -1, do a return ~mid
The reason for using the one's complement rather than the negative index is that if the item you're looking for is smaller than the smallest item in the array, it would have to be inserted at index 0. But if you returned -0, you'd get ... 0. So it's impossible to tell the difference between the item being found at zero and the item needing to be inserted at index 0. One's complement solves that problem because the one's complement of 0 is -1.
Related
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)
);
Challenge: https://www.codewars.com/kata/57c7930dfa9fc5f0e30009eb/train/javascript
Hi I have been trying this problem for many hours but unfortunately my code is taking too long to pass:
function closestPower(num) {
num = Math.floor(num);
if (num < 4) return 4;
// check if input is perfect power
let base = 2;
while (base < 10) {
let exponent = Math.trunc(getBaseLog(base , num));
if ( Math.pow(base, exponent) === num ) {
return num;
}
base++;
}
// check for upper and lower
base = 2;
const verifyObj = {upper:null, lower:null}; // verify
let upperPower = num + 1;
let lowerPower = num - 1;
while (!verifyObj.upper || !verifyObj.lower)
{
// no perfect power
if (lowerPower <= 2 ) verifyObj.lower = "Not found";
if (upperPower === Infinity ) verifyObj.upper = "Not found";
// up til base 9
if (base === 10) {
if (!verifyObj.upper) upperPower++;
if (!verifyObj.lower) lowerPower--;
base = 2;
}
// upper
if (!verifyObj.upper) {
let exponent = Math.trunc(getBaseLog(base , upperPower));
if ( Math.pow(base, exponent) === upperPower ) {
verifyObj.upper = upperPower;
}
}
// lower
if (!verifyObj.lower) {
let exponent = Math.trunc(getBaseLog(base , lowerPower));
if ( Math.pow(base, exponent) === lowerPower ) {
verifyObj.lower = lowerPower;
}
}
base++;
}
console.log(verifyObj) // {upper:64, lower: 49}
// nearest power
if ((upperPower - num) < (num - lowerPower)) {
return upperPower;
}
else return lowerPower;
}
closestPower(56.5); // 49
function getBaseLog(x, y) {
return Math.log(y) / Math.log(x);
}
I realized that my code is redundant as all i need to know if a “base” and “exponent” are more than 1 to determine a perfect power. Any formulas or ideas?
Some issues:
There is no reason why base should not be allowed to be 10 or more
Trying with upperPower at each increment is taking too many iterations. The distance to the next power might be rather big.
I would suggest the following algorithm:
Let the exponent to try with start at 2, and then increment by 1. Calculate which could be the corresponding base. The real base can be found by raising n to the inverse exponent (i.e. 1/exp). Then there are only 2 interesting integer bases to consider: by rounding downwards and upwards.
Here is an implementation:
function closestPower(n) {
if (n <= 6) return 4;
let result = -1;
let closest = n;
for (let factor, exp = 2; (factor = n ** (1 / exp)) > 1.9; ++exp) {
let above = Math.ceil(factor);
for (let intfactor = Math.floor(factor); intfactor <= above; intfactor++) {
let power = intfactor ** exp;
let diff = Math.abs(power - n);
if (diff == 0) return n;
if (diff < closest || diff == closest && power < n) {
closest = diff;
result = power;
}
}
}
return result;
}
// Some tests:
const tests = [
[0, 4], [9, 9], [30, 32], [34, 32], [56.5, 49],
[123321456654, 123321773584]
];
for (let [n, expected] of tests) {
let result = closestPower(n);
if (result === expected) continue;
console.log(`closestPower(${n}) returned ${result}, but expected ${expected}`);
}
console.log("all tests done");
Here's my algorithm
first i will get the exponent from base that less than of the n then I added the current base of the loop with the n then get the base log.
function closestPower(n) {
if(n < 4) return 4
let closest = []
let base = 2
while(base < n) {
const exponent = Math.floor(Math.log(n + base) / Math.log(base))
const power = Math.pow(base,exponent)
if(exponent === 1) break
if(power === n) return n
closest.push(power)
base++
}
return closest.reduce((prev, curr) => (Math.abs(curr - n) < Math.abs(prev - n) ? curr : prev))
}
console.log(closestPower(0))
console.log(closestPower(9))
console.log(closestPower(30))
console.log(closestPower(34))
console.log(closestPower(56.5))
console.log(closestPower(123321456654))
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;
}
i am working on binary search, and this is first thing i came up with:
function letsGoBinary(firstArray,array,search){
const middle = Math.floor(array.length / 2);
if(search === array[middle]) {
const rv = firstArray.indexOf(array[middle]);
return rv
}else if(search < array[middle]){
var lowerArray = []
for(var i = 0; i < middle; i++){
lowerArray.push(array[i])
}
letsGoBinary(firstArray,lowerArray, search)
}else if(search > array[middle]){
var forwardArray = []
for(var i = middle + 1; i < array.length; i++){
forwardArray.push(array[i]);
}
letsGoBinary(firstArray,forwardArray,search)
}else {
return -1
}
}
console.log(letsGoBinary([1,4,7,14,16],[1,4,7,14,16], 4))
and this works if I add console.log() in first if statement (search === array[middle]) and log the rv it logs exact value, and same happens if I log not found in else statement, it logs but while logging letsGoBinary its value is undefined. how can i fix that?
beside your question please check how slice method works, loop is not nesessery to get a part of array
also this code is bit meaningless, if you use
const rv = firstArray.indexOf(array[middle])
then why just in the very beginning not to use
const rv = firstArray.indexOf(search)
this line of code makes meaningless all your binary search as searches elements one by one
There are very simple solution for it
function letsGoBinary(array, search){
let start = 0
let end = array.length - 1
while (start <= end) {
const middle = Math.floor((start + end) / 2)
if(search === array[middle]) {
return middle
} else if (search < array[middle]) {
end = middle - 1
} else {
start = middle + 1
}
}
return -1
}
In the cases where you make a recursive call, you need to return the result.
return letsGoBinary(firstArray,lowerArray, search);
When dealing with recursive function, you should have the basic cases and then when you want to like perfom a recursive call you are not only calling the function again but you should return the function as response.
For instance if the search number is present into the lowerArray is means that you should return letsGoBinary(firstArray,lowerArray, search) as answer.
I updated your code so that is it:
Note: Look at the line 11 and 17
function letsGoBinary(firstArray,array,search){
const middle = Math.floor(array.length / 2);
if(search === array[middle]) {
const rv = firstArray.indexOf(array[middle]);
return rv
}else if(search < array[middle]){
var lowerArray = []
for(var i = 0; i < middle; i++){
lowerArray.push(array[i])
}
return letsGoBinary(firstArray,lowerArray, search)
}else if(search > array[middle]){
var forwardArray = []
for(var i = middle + 1; i < array.length; i++){
forwardArray.push(array[i]);
}
return letsGoBinary(firstArray,forwardArray,search)
}else {
return -1
}
}
console.log(letsGoBinary([1,4,7,14,16],[1,4,7,14,16], 4))
It is happening because after the first execution when search === array[middle], it is completely returning from letsGoBinary function, hence you have to add another return statement, please find below code snippet:
function letsGoBinary(firstArray,array,search){
const middle = Math.floor(array.length / 2);
if(search === array[middle]) {
const rv = firstArray.indexOf(array[middle]);
return rv
}else if(search < array[middle]){
var lowerArray = []
for(var i = 0; i < middle; i++){
lowerArray.push(array[i])
}
return letsGoBinary(firstArray,lowerArray, search)
}else if(search > array[middle]){
var forwardArray = []
for(var i = middle + 1; i < array.length; i++){
forwardArray.push(array[i]);
}
return letsGoBinary(firstArray,forwardArray,search)
}else {
return -1
}
}
console.log(letsGoBinary([1,4,7,14,16],[1,4,7,14,16], 4))
The answer from Dmitry Reutov is great. If you'd prefer a recursive version, this is a similar approach, but using recursion rather than a while loop:
const letsGoBinary = (
sortedArray,
value,
start = 0,
end = sortedArray .length - 1,
middle = Math.floor ((end + start) / 2)
) =>
start > end
? -1
: sortedArray [middle] == value
? middle
: sortedArray [middle] < value
? letsGoBinary (sortedArray, value, middle + 1, end)
: letsGoBinary (sortedArray, value, start, middle - 1)
console .log (
letsGoBinary ([1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233], 34)
)
Both these solutions use only a single array, relying on start, end, and middle indices to track the current search position.
This version defaults start and end on the first call, and then passes them on subsequent searches. middle is calculated on each call as the closest integer midpoint between start and end.
For this example, the first call uses start and end of 0 and 12 making middle 6, and the value we're testing would be sortedArray[6], which is 13. This is less than the search value of 34, so we call again with 7 and 12, which makes middle into 9 and the test value 55. That is larger than 34 so we call with 7 and 8, middle of 7, test value 21. That one is less than our value, and we call one more time with start and end both 8, which gives us a middle of 8 and a test value of 34. Since that equals our value, we return 8. If we had missed -- perhaps we were searching for 35 instead -- then we would call again with start of 9 and end of 8, and would return -1, because start > end. Or if we had been searching for 33 instead, we would have start of 8 and end of 7, with the same -1 result.
How can I get the nth value of a generator?
function *index() {
let x = 0;
while(true)
yield x++;
}
// the 1st value
let a = index();
console.log(a.next().value); // 0
// the 3rd value
let b = index();
b.next();
b.next();
console.log(b.next().value); // 2
// the nth value?
let c = index();
let n = 10;
console.log(...); // 9
You can define an enumeration method like in python:
function *enumerate(it, start) {
start = start || 0;
for(let x of it)
yield [start++, x];
}
and then:
for(let [n, x] of enumerate(index()))
if(n == 6) {
console.log(x);
break;
}
http://www.es6fiddle.net/ia0rkxut/
Along the same lines, one can also reimplement pythonic range and islice:
function *range(start, stop, step) {
while(start < stop) {
yield start;
start += step;
}
}
function *islice(it, start, stop, step) {
let r = range(start || 0, stop || Number.MAX_SAFE_INTEGER, step || 1);
let i = r.next().value;
for(var [n, x] of enumerate(it)) {
if(n === i) {
yield x;
i = r.next().value;
}
}
}
and then:
console.log(islice(index(), 6, 7).next().value);
http://www.es6fiddle.net/ia0s6amd/
A real-world implementation would require a bit more work, but you got the idea.
As T.J. Crowder pointed out, there is no way to get to the nth element directly, as the values are generated on demand and only the immediate value can be retrieved with the next function. So, we need to explicitly keep track of the number of items consumed.
The only solution is using a loop and I prefer iterating it with for..of.
We can create a function like this
function elementAt(generator, n) {
"use strict";
let i = 0;
if (n < 0) {
throw new Error("Invalid index");
}
for (let value of generator) {
if (i++ == n) {
return value;
}
}
throw new Error("Generator has fewer than " + n + " elements");
}
and then invoke it like this
console.log(elementAt(index(), 10));
// 10
Another useful function might be, take, which would allow you to take first n elements from a generator, like this
function take(generator, n) {
"use strict";
let i = 1,
result = [];
if (n <= 0) {
throw new Error("Invalid index");
}
for (let value of generator) {
result.push(value);
if (i++ == n) {
return result;
}
}
throw new Error("Generator has fewer than " + n + " elements");
}
console.log(take(index(), 10))
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
A simple loop will do:
let n = 10,
iter = index();
while (--n > 0) iter.next();
console.log(iter.next().value); // 9
You could create an array of size n, and use Array.from and its second argument to fetch the values you need. So assuming iter is the iterator from generator gen:
var iter = gen();
Then the first n values can be fetched as follows:
var values = Array.from(Array(n), iter.next, iter).map(o => o.value)
...and when you are only interested in the nth value, you could skip the map part, and do:
var value = Array.from(Array(n), iter.next, iter).pop().value
Or:
var value = [...Array(n)].reduce(iter.next.bind(iter), 1).value
The downside is that you still (temporarily) allocate an array of size n.
I wanted to avoid unnecessary creation of arrays or other intermediate values. Here's what my implementation of nth ended up like -
function nth (iter, n)
{ for (const v of iter)
if (--n < 0)
return v
}
Following the examples in the original question -
// the 1st value
console.log(nth(index(), 0))
// the 3rd value
console.log(nth(index(), 2))
// the 10th value
console.log(nth(index(), 9))
0
2
9
For finite generators, if the index is out of bounds the result will be undefined -
function* foo ()
{ yield 1
yield 2
yield 3
}
console.log(nth(foo(), 99))
undefined
Expand the snippet below to verify the results in your browser -
function *index ()
{ let x = 0
while (true)
yield x++
}
function* foo ()
{ yield 1
yield 2
yield 3
}
function nth (iter, n) {
for (const v of iter)
if (--n < 0)
return v
}
// the 1st value
console.log(nth(index(), 0))
// the 3rd value
console.log(nth(index(), 2))
// the 10th value?
console.log(nth(index(), 9))
// out-of-bounds?
console.log(nth(foo(), 99))