Infinite Loop with my binary search implementation in JavaScript - javascript

this is my first question on stack overflow so please bear with me.
I wrote this binarySearch function and for some reason it hangs - I'm assuming because of an infinite loop. Can anyone find the faults with my code?
let binarySearch = (array, value) => {
let target = value,
start = 0,
end = array.length - 1,
middle = Math.floor( (end + start)/2 )
while (start <= end){
if ( array[middle] === target ){
return true
}else if (array[middle] < target){
start = middle + 1
}else if (array[middle] > target){
end = middle - 1
}
}
return false
}

The most obvious bug is that you need to calculate middle as the first operation inside the loop, not outside of it.
Without that change, you're always examining whichever element was "middle" when the function was first called, and never partitioning your search space.
With that fix in place, my tests indicate that the code works as required, although as suggested in the comments you should be returning the index of the found element, not simply a flag to say whether the element was found or not.

An infinite loop occurs because the implementation shown is an iterative binary search, but the middle value is not recalculated upon each iteration, as others have mentioned. Thus, the middle variable will never change beyond the first iteration.
Below is an implementation which returns the index of value, or null, if value is not found.
function binarySearch(array, value) {
let middle, start = 0,
end = array.length - 1;
while (start <= end) {
middle = Math.floor((start + end) / 2);
if (array[middle] > value) {
end = middle - 1;
} else if (array[middle] < value) {
start = middle + 1;
} else {
return middle;
}
}
return null;
}

Related

Each digit differs from the next one by 1

Script has to work this way:
isJumping(9) === 'JUMPING'
This is a one digit number
isJumping(79) === 'NOT JUMPING'
Neighboring digits do not differ by 1
isJumping(23454) === 'JUMPING'
Neighboring digits differ by 1
I have:
function isJumping(number) {
let str = number.toString();
for (let i = 1; i < str.length; i++) {
if (Math.abs(str[i+1]) - Math.abs(str[i]) == 1){
return 'JUMPING';
}
}
return 'NOT JUMPING';
}
console.log(isJumping(345));
Help please, where is mistake?
Loop over the characters and early return with "NOT JUMPING" if the condition is violated & if the condition is never violated return "JUMPING".
function isJumping(num) {
const strNum = String(Math.abs(num));
for (let i = 0; i < strNum.length - 1; i++) {
if (Math.abs(strNum[i] - strNum[i + 1]) > 1) {
// replace `> 1` with `!== 1`, if diff 0 is not valid!
return "NOT JUMPING";
}
}
return "JUMPING";
}
console.log(isJumping(9));
console.log(isJumping(79));
console.log(isJumping(23454));
There are a couple of issues:
You're not handling single digits.
You're returning too early. You're returning the first time you see a difference of 1 between digits, but you don't know that subsequent differences will also be 1.
You're not checking the difference between the first and second digits, and you're going past the end of the string.
You're using Math.abs as a means of converting digits to numbers.
Instead (see comments):
function isJumping(number) {
let str = number.toString();
for (let i = 1; i < str.length; i++) {
// Convert to number, do the difference, then
// use Math.abs to make -1 into 1 if necessary
if (Math.abs(+str[i] - str[i-1]) !== 1) {
// Found a difference != 1, so we're not jumping
return "NOT JUMPING";
}
}
// Never found a difference != 1, so we're jumping
return "JUMPING";
}
console.log(isJumping(345)); // JUMPING
console.log(isJumping(9)); // JUMPING
console.log(isJumping(79)); // NOT JUMPING
console.log(isJumping(23454)); // JUMPING
In that, I use +str[i] to convert str[i] to number and implicitly convert str[i-1] to number via the - operator, but there are lots of ways to convert strings to numbers (I list them here), pick the one that makes sense for your use case.
You might also need to allow for negative numbers (isJumping(-23)).
A clumsy way would be if (Math.abs(Math.abs(str[i+1]) - Math.abs(str[i])) == 1). Right now you are using Math.abs() to convert digits to numbers. Also, indexing is off, you start from 1, which is good, but then you should compare [i] and [i-1]. And the usual mismatch: you can say "JUMPING", only at the end. So you should check for !==1, and return "NOT JUMPING" inside the loop, and "JUMPING" after. That would handle the 1-digit case too.
It's a more readable practice to use parseInt() for making a number from a digit, otherwise the implementation of the comment:
function isJumping(number) {
let str = number.toString();
for (let i = 1; i < str.length; i++) {
if (Math.abs(parseInt(str[i-1]) - parseInt(str[i])) !== 1){
return 'NOT JUMPING';
}
}
return 'JUMPING';
}
console.log(isJumping(345));
console.log(isJumping(3));
console.log(isJumping(79));
You just need to check your single digit case, and then see if all the digits vary by just 1
function isJumping(number) {
let str = number.toString();
if(str.length == 1)
return 'JUMPING'
const allByOne = str.substring(1).split('').every( (x,i) => {
var prev = str[i];
return Math.abs( +x - +prev) == 1
})
return allByOne ? 'JUMPING' : 'NOT JUMPING';
}
console.log(isJumping(9));
console.log(isJumping(79));
console.log(isJumping(23454));
A vaguely functional approach... The find gets position of the first character pair where the gap is more than one. The .filter deals with negatives (and other extraneous characters) by ignoring them.
// default b to a, so that last digit case, where b===undefined, gives true
const gapIsMoreThanOne = (a,b=a) => ( Math.abs(a-b)>1);
const isDigit = n => /[0-9]/.test(n);
const isJumping = n => n.toString()
.split("")
.filter(isDigit)
.find((x,i,arr)=>gapIsMoreThanOne(x,arr[i+1]))
=== undefined
? "JUMPING" : "NOT JUMPING"
;
console.log(isJumping(1)); // JUMPING
console.log(isJumping(12)); // JUMPING
console.log(isJumping(13)); // NOT JUMPING
console.log(isJumping(21)); // JUMPING
console.log(isJumping(21234565)); // JUPING
console.log(isJumping(-21234568)); // NOT JUMPING
console.log(isJumping("313ADVD")); // NOT JUMPING
PS: To me "JUMPING" implies that there is a gap greater than one, not that there isn't: but I've gone with how it is in the question.

Search in Rotated Sorted Array( with algorithm )

I am trying to search for a target in rotated sorted ascending array in O(logn) time
For example:
Example 1:
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4
Example 2:
Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1
My idea is that, in the rotated array, the whole sequence is not said to be sorted anymore.
But I found that if I give a cut in the middle, either the first half or second half is still sorted.
So, I pick the middle one and compare the values on both ends to check which part is still sorted. Then I check if the target is within the sorted range. And I am doing it recursively
Here is my code:
var search = function(nums, target) {
let start = 0
let end = nums.lenght -1;
let mid = 0;
while (start<=end){
mid = (start + end) / 2
if (target == nums[mid]){
return mid
}
else{
if (nums[mid]>nums[start] && nums[start]<taget<nums[mid]){
end = mid;
}else {
start = mid
}
}
}
return -1;
};
But I still got an error for such input [4,5,6,7,0,1,2] , 0 ; but output -1 ; I dont get why algorithm doesnt work and see the lacking. Can anyone see my faults?
----- second update --- corrected misspelled and condition sytax error
var search = function(nums, target) {
let start = 0
let end = nums.length -1;
let mid = 0;
while (start<=end){
mid = (start + end) / 2
if (target == nums[mid]){
return mid
}
else{
if (nums[mid]>nums[start]){ //it means it is ascending and sorted...
if(nums[start]<target && target<nums[mid]){ // if target is within the range, then it could only be possible to go first half
end = mid;
}
// it means the second half is sorted one otherwise
}else {
start = mid
}
}
}
return -1;
};
This passed the first case;
[2,5,6,0,0,1,2]
0
but got time exceeded on this
[2,5,6,0,0,1,2], target = -1
------ third edit , never expected it is so hard; I come up with 3 case to check... but not finished yet. I dont know which part went. wrong
var search = function(nums, target) {
let start = 0
let end = nums.length -1;
let mid = 0;
while (start<=end){
mid = (start + end) / 2
if (target == nums[mid]){
return mid
}
else{
//case1 + (sorted first half) +(sorted second half)
if (nums[mid]>nums[start] && num[mid]<nums[end]){
if(nums[start]<target && target<nums[mid]){
end = mid;
}else{
start=mid;
}
}
//case2 + - (sorted first half) +(unsorted second half)
else if (nums[mid]>nums[start] && num[mid]>nums[end]){
if(nums[start]<target && target<nums[mid]){
end = mid;
}else{
start = mid
}
}
//case3 - + (unsorted first half) +(sorted second half)
else {
if(nums[end]<target && target>nums[mid]){
start = mid;
}else{
end = mid
}
}
}
return -1;
};
but I dont have any line 75; my line goes to til line 47 only
-------------forth edit
I looked again the pattern and come up with a clearer observation. This passed one of the case. but got Time Limit Exceeded in this case:
[2,5,6,0,0,1,2]
3
var search = function(nums, target) {
let left = 0
let right = nums.length -1;
let mid = 0;
while (left<=right){
mid = (left + right) / 2
if (target == nums[mid]){
return mid
}
else{
if (nums[mid] < nums[right]){
if(nums[mid]<target && target<nums[right]){
left = mid;
}else{
right=mid;
}
}else{
if(nums[mid]>target && target<nums[right]){
left = mid;
}else{
right=mid;
}
}
}
}
return -1;
};
Ok, you have corrected the misspelled and syntax errors, let's think about the cases you could encounter :
The list is in order.
The list is rotated.
If the list is in order, you can apply the classic algorithm. If the list is rotated, there is one point at which the number goes from highest to lowest. As this point is unique (think about why this is always true) it can be only in one half of the list, meaning one is in order, the other is a rotated list. (It could also be in between the 2 halves, meaning both of them are in order, but this is an easy case).
Thus you need to split the list in half, and determine in which half the target could be (could the target be in both halves ?) : to do that, first check which half is sorted, then check if the targer could be in that half (by using the first and last value). If it can't, it's in the rotated half.
Then, if the target is in the sorted half, you continue with the classic algorithm, else you search the target in the rotated half, which is like searching the target in a rotated list.
--Old answer for history--
Not sure if it's that, but you misspelled nums.length in the setup, which then set end to NaN (not a number), and thus the while loop is never entered(NaN comparaisons never hold true), and you return -1.
Also, there are multiples errors on line if (nums[mid]>nums[start] && nums[start]<taget<nums[mid]){ :
target is misspelled
You can't compare A < x < B in one go, you need to do A < x && x < B
If the target is in the first half but it's not sorted (mid < x < start), you will search in the second half. You probably need 4 different cases there.

Sum an array of numbers in Javascript using Recursion

Why isn't this working? I'm getting a stack too deep error:
var countRecursion = function(array) {
var sum = 0
var count = 0
sum += array[count]
count ++
if (count < array.length) {
countRecursion(array);
} else {
return sum
}
}
You made a mistake and reset sum and counter inside the recursive block. I simply moved them outside.
var countRecursion = function(array) {
sum += array[count]
count ++
if (count < array.length) {
countRecursion(array);
} else {
return sum
}
}
var sum = 0
var count = 0
countRecursion([1,2,3]);
alert(sum);
This code is not recursive but iterative. I am not 100% sure if that's what you really wanted. But since you mentioned it, some people down voted my answer since I only fixed your code, but didn't made it recursive I guess. For completeness, here is recursive version of your code:
var countRecursion = function(array, ind) {
if (ind < array.length) {
return array[ind] + countRecursion(array, ind + 1);
} else {
return 0;
}
}
var sum = 0
var count = 0
sum = sum + countRecursion([1,2,3, 5, 6, 7], count);
alert(sum);
For recursion: pass data up, return data down.
The original code has a different count variable, being a local variable defined in the function, that is initial set to 0. As such the base case is never reached and the function recurses until the exception is thrown.
In addition to using a variable from an outer scope (or another side-effect) this can also be addressed by by following the recommendation on how to handle recursion, eg.
var countRecursion = function(array, index) {
index = index || 0; // Default to 0 when not specified
if (index >= array.length) {
// Base case
return 0;
}
// Recurrence case - add the result to the sum of the current item.
// The recursive function is supplied the next index so it will eventually terminate.
return array[index] + countRecursion(array, index + 1);
}
I see what you're thinking.
The issue with your code is that everytime you call countRecursion, count goes back to 0 (since it's initialized to 0 within your function body). This is making countRecursion execute infinitely many times, as you're always coming back to count = 0 and checking the first term. You can solve this by either:
Initializing count outside the function body, that way when you do count++, it increases and doesn't get reset to 0.
Passing count along with array as a parameter. That way, the first time you call the function, you say countRecursion(array, 0) to initialize count for you.
Note that you have to do the same for sum, else that will also revert to zero always.
Finally, (and this doesn't have to do with the stack error) you have to actually call return countRecursion(array) to actually move up the stack (at least that's how it is in C++ and what not - pretty sure it applies to javascript too though).
Array sum using recursive method
var countRecursion = function(arr, current_index) {
if(arr.length === current_index) return 0;
current_index = current_index || 0;
return countRecursion(arr, current_index+1) + arr[current_index];
}
document.body.innerHTML = countRecursion([1,2,3,4,5, 6])

Function Returning Square Brackets When Using Array Methods

I've just started an exercise in Chapter 4 of a book called Eloquent JavaScript. The exercise asks me to do the following:
"Write a range function that takes two arguments, start and end, and
returns an array containing all the numbers from start up to (and
including) end."
However, when I write the following code:
function range(start, end) {
array = [];
for (var i = start; i <= end; i += 1) {
array.push(i);
}
return array;
}
console.log(range(5, 2));
It returns square brackets ( [] ). Does anyone know what the issue might be? Thanks a bunch!
You are passing the range indexes in the wrong order. You cant go from 5 to 2, you need to go from 2 to 5
range(2,5)
Returning square brackets indicates an empty list. This may or may not be the desired result.
To expand on your question a bit - you could use a range function that is more flexible and able to produce descending lists. See below for example, or in this fiddle.
function range(start, end) {
array = [];
// Determine order
var delta = (start < end)? 1 : -1;
for (var i = start; num_compare(i,end,delta); i += delta) {
array.push(i);
}
return array;
}
function num_compare(a,b, delta) {
if(delta == 0) { return false; } // No change? Avoid infinite loop.
if(delta < 0) { return a >= b; } // Descending. Continue while a>=b
if(delta > 0) { return a <= b; } // Asecending. Continue while a<=b
}
var answer = range(5,2);
console.log(answer);
document.getElementById("answer").innerHTML = answer.toString();
Now passing in range(5,2); will produce the (maybe desired) list of [5,4,3,2].
Related questions/answers of note include:
reversing order in for loop, comparison operators

Animate counter using Javascript

I have a couple of fairly simple javascript functions which animate the transition of a number, going up and down based on user actions. There are a number of sliders on the page which within their callback they call recalculateDiscount() which animates the number up or down based on their selection.
var animationTimeout;
// Recalculate discount
function recalculateDiscount() {
// Get the previous total from global variable
var previousDiscount = totalDiscount;
// Calculate new total
totalDiscount = calculateDiscount().toFixed(0);
// Calculate difference
var difference = previousDiscount - totalDiscount;
// If difference is negative, count up to new total
if (difference < 0) {
updateDiscount(true, totalDiscount);
}
// If difference is positive, count down to new total
else if (difference > 0) {
updateDiscount(false, totalDiscount);
}
}
function updateDiscount(countUp, newValue) {
// Clear previous timeouts
clearTimeout(animationTimeout);
// Get value of current count
var currentValue = parseInt($(".totalSavingsHeader").html().replace("$", ""));
// If we've reached desired value, end
if (currentValue === newValue) { return; }
// If counting up, increase value by one and recursively call with slight delay
if (countUp) {
$(".totalSavingsHeader").html("$" + (currentValue + 1));
animationTimeout = setTimeout("updateDiscount(" + countUp + "," + totalDiscount + ")", 1);
}
// Otherwise assume we're counting down, decrease value by one and recursively call with slight delay
else {
$(".totalSavingsHeader").html("$" + (currentValue - 1));
animationTimeout = setTimeout("updateDiscount(" + countUp + "," + totalDiscount + ")", 1);
}
}
The script works really well for the most part however there are a couple of problems. Firstly, older browsers animate more slowly (IE6 & 7) and get confused if the user moves the slider again whilst it is still within the animation.
Newer browsers work great EXCEPT for on some occasions, if the user moves the slider mid-animation, it seems that it starts progressing in the wrong direction. So for updateDiscount() gets called with a new value and a directive to count up instead of down. As a result the animation goes the wrong direction on an infinite loop as it will never reach the correct value when it's counting in the wrong direction.
I'm stumped as to why this happens, my setTimeout() experience is quite low which may be the problem. If I haven't provided enough info, just let me know.
Thank you :)
Here is how you use setTimeout efficiently
animationTimeout = setTimeout(function {
updateDiscount(countUp,totalDiscount);
},20);
passing an anonymous function help you avoid using eval.
Also: using 1 millisecond, which is too fast and will freeze older browsers sometimes. So using a higher which will not even be noticed by the user can work better.
Let me know if this works out for you
OK think it's fixed...
Refactored code a little bit, here's final product which looks to have resolved bug:
var animationTimeout;
function recalculateDiscount() {
var previousDiscount = parseInt(totalDiscount);
totalDiscount = parseInt(calculateDiscount());
if (($.browser.msie && parseFloat($.browser.version) < 9) || $.browser.opera) {
$(".totalSavingsHeader").html("$" + totalDiscount);
}
else {
if (previousDiscount != totalDiscount) {
clearTimeout(animationTimeout);
updateDiscount(totalDiscount);
}
}
}
function updateDiscount(newValue) {
var currentValue = parseInt($(".totalSavingsHeader").html().replace("$", ""));
if (parseInt(currentValue) === parseInt(newValue)) {
clearTimeout(animationTimeout);
return;
}
var direction = (currentValue < newValue) ? "up" : "down";
var htmlValue = direction === "up" ? (currentValue + 1) : (currentValue - 1);
$(".totalSavingsHeader").html("$" + htmlValue);
animationTimeout = setTimeout(function () { updateDiscount(newValue); }, 5);
}
Will give points to both Ibu & prodigitalson, thank you for your help :)

Categories

Resources