How to fix an infinite loop? - javascript

I started learning JavaScript and as a part of a small project, I have an array of Date objects, and I am trying to search in it with binary search. I don't know why, but it became an infinite loop.
I know the problem is in the first if (if mid < right), because it works when the content of the if stands alone.
But if I put only the content, the function gets stuck when mid = right.
I don't see what in the if makes it an infinite loop or how to fix it.
I would greatly appreciate it if you could be of assistance.
**timeDiff is a function that returns which of the two dates are greater (later)
EDIT
I will try to describe the code better...
the function of the binary search receives an array of date objects (db = database), and one more date object (objDate), that the search should find.
I want the search function to return if the searched object is in the array (this is the true and false), and what is the index of it (or the index it should be in, if it is not on the array) - this is why I return also the mid.
I know it is not the best thing to return two values, but I need them two, so I put them in two variables when I call the function.
I add here the function timeDiff (that receives two Date objects and returns which of them is greater (later)) and an example for running.
function timeDiff(objDate, currDate) {
var _MS_PER_DAY = 86400000
var day1 = Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate(), objDate.getHours(), objDate.getMinutes());
var day2 = Date.UTC(currDate.getFullYear(), currDate.getMonth(), currDate.getDate(), currDate.getHours(), currDate.getMinutes());
return (day1 - day2) / _MS_PER_DAY ;
function binarySearch(db, objDate){
var left = 0, right = db.length ;
while (left <= right){
var mid = Math.trunc((left + right) / 2) ;
if (mid != right) {
var currDate = new Date(db[mid].date) ;
}
if (timeDiff(objDate, currDate) === 0){
return true, mid ;
}
if (timeDiff(objDate, currDate) < 0){
right = mid - 1 ;
}
if (timeDiff(objDate, currDate) > 0){
left = mid + 1 ;
}
}
(timeDiff(objDate, currDate) > 0) ? mid-- : mid = mid ;
return false, mid ;
}
a running example that should return "true, 0" (but actually does not finish the run) is:
var a = [{ "date": "2018-09-12", "appointments": [ { "subject": "Code review", "start": "09:00", "duration": 1.5 }, { "subject": "JavaScript objects", "start": "10:45", "duration": 2 } ] }] ;
var b = new Date(2018, 09, 12, 8, 0)
binarySearch(a, b)

It does end, it only loops twice.
function timeDiff(objDate, currDate) {
var _MS_PER_DAY = 86400000
var day1 = Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate(), objDate.getHours(), objDate.getMinutes());
var day2 = Date.UTC(currDate.getFullYear(), currDate.getMonth(), currDate.getDate(), currDate.getHours(), currDate.getMinutes());
return (day1 - day2) / _MS_PER_DAY ;
}
function binarySearch(db, objDate){
var left = 0, right = db.length ;
while (left <= right){
var mid = Math.trunc((left + right) / 2) ;
if (mid != right) {
var currDate = new Date(db[mid].date) ;
}
if (timeDiff(objDate, currDate) === 0){
return true, mid ;
}
if (timeDiff(objDate, currDate) < 0){
right = mid - 1 ;
}
if (timeDiff(objDate, currDate) > 0){
left = mid + 1 ;
}
console.log('looping');
}
(timeDiff(objDate, currDate) > 0) ? mid-- : mid = mid ;
return false, mid ;
}
var a = [{ "date": "2018-09-12", "appointments": [ { "subject": "Code review", "start": "09:00", "duration": 1.5 }, { "subject": "JavaScript objects", "start": "10:45", "duration": 2 } ] }] ;
var b = new Date(2018, 09, 12, 8, 0)
binarySearch(a, b)

You could use the find method of array
var a = [{ "date": "2018-09-12", "appointments": [ { "subject": "Code review", "start": "09:00", "duration": 1.5 }, { "subject": "JavaScript objects", "start": "10:45", "duration": 2 } ] }] ;
var b = new Date(2018, 09, 12, 8, 0)
console.log(a.find(item => item.date === `${b.getFullYear()}-${pad(b.getMonth())}-${pad(b.getDate())}`));
function pad(num) {
return num < 10 ? '0' + num : num;
}

Related

undefined is returned from function

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.

When given N num of steps and M max possible steps return the total number possible sequences where the person can go (higher) from level 0 to level N

I feel like this is very similar to the staircase problem where you can use the fibonacci recursive call, but what's throwing me off is the second argument of M steps, I'm not sure how to apply it. Help?
I can solve this very similarly to the staircase problem which is just a fibonacci application then transitioning using that fib helper function.
I can't seem to get it at all to work
function numSteps(n, m) {
if (n === 0) return 1
let begin = 0
let end = 1
let result = 0
for (let i = 1; i <= n; i++) {
while (m < n) {
result = begin + end
begin = end
end = result
}
}
return result
};
console.log(numSteps(2, 6))
function steps(n, m) {
let table = new Array(n + 1).fill(false);
table[0] = true;
let total = 0
for (let i = 0; i < table.length; i++) {
if (table[i] === true) {
for (let j = 1; j <= m; j++) {
table[j] = true
total += 1
}
}
}
return total;
}
Generators are probably the best way to solve permutation and combination problems in JavaScript. You will learn a lot by tracing the evaluation step-by-step on paper or in a text editor -
const waysToClimb = function* (n, m, seq = [])
{ if (n > 0)
for (let i = 1; i <= m; i++)
if (i > n)
return
else
yield* waysToClimb (n - i, m, [ i, ...seq ])
else
yield seq
}
const result =
Array.from (waysToClimb (10, 3))
console.log (JSON.stringify (result))
// [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]
// , ...
// , [ 1, 3, 3, 3 ]
// ]
Here is a recursive solution in Ruby.
def steps(max_steps, steps_remaining)
(1..max_steps).sum do |n|
case n <=> steps_remaining
when -1
steps(max_steps, steps_remaining-n)
when 0
1
else
0
end
end
end
steps(2, 4)
#=> 5
steps(3, 10)
#=> 274
steps(5, 15)
#=> 13624
require 'time'
t = Time.now
puts steps(8, 25)
16_190_208
(Time.now-t).round
#=> 22 seconds
In the first example the 5 possible sequences are as follows:
[[1,1,1,1], [1,1,2], [1,2,1], [2,1,1], [2,2]]

Dividing a number to equal chunks

I'm trying to write a function that returns an array of equally chunked up dates and number of days pertaining to those dates. Should there be a remainder of those days they get appended to the array as follow.
Expected outcome:
[{
'startDate' : 20160719 //dates generated from momentjs
'numOfDays': 5
},{
'startDate' : 20160724
'numOfDays': 5
},{
'startDate' : 20160729
'numOfDays': 3
}]
Below is the function I've written in which you can pass in a start date (momentjs), the total number of days (daysToDisplay) and number of days to be divided by (numOfDays).
function buildQueue(startDate, numOfDays, daysToDisplay) {
if (!startDate || !numOfDays || !daysToDisplay) {
throw new Error('All params required!');
}
var num = numOfDays > daysToDisplay ? daysToDisplay : numOfDays;
var div = Math.floor(daysToDisplay / num);
var count = daysToDisplay;
var rem = daysToDisplay % num;
var lastItem;
var i;
var arr = [];
for (i = 0; i <= daysToDisplay; i += num) {
arr.push({
startDate: moment(startDate, 'YYYYMMDD').add(i, 'days').format('YYYYMMDD'),
numOfDays: numOfDays,
count: i
})
if (rem === count) {
break;
}
count -= num;
}
if (count > 0) {
lastItem = arr[arr.length - 1];
var leftover = daysToDisplay - lastItem.count;
arr.push({
startDate: moment(lastItem.startDate, 'YYYYMMDD').add(num, 'days').format('YYYYMMDD'),
numOfDays: rem,
count: leftover + lastItem.count
});
}
return arr;
}
A working example is here (https://jsfiddle.net/zv5ghqpa/1/). The code appears to work in scenarios where daysToDisplay is dividable by more than 2.
When daysToDisplay is only dividable by one, we get an additional item in the returned array basically due to the zero index in the for loop. The expected outcome if I call buildQueue('20160719', 5, 8) should be:
[{
'startDate': 20160719
'numOfDays': 5
}, {
'startDate': 20160724
'numOfDays': 3
}]
Instead its returning:
[{
'startDate': 20160719
'numOfDays': 5
},{
'startDate': 20160724
'numOfDays': 5
}, {
'startDate': 20160729
'numOfDays': 3
}]
I hope i've given enough info... this is really doing my head in.
Thanks in advance!
I think this is code you're looking for:
function buildQueue(startDate, numOfDays, daysToDisplay) {
if (!startDate || !numOfDays || !daysToDisplay) {
throw new Error('All params required!');
}
var num = numOfDays > daysToDisplay ? daysToDisplay : numOfDays;
var div = Math.floor(daysToDisplay / num);
var count = daysToDisplay;
var rem = daysToDisplay % num;
var n = 0;
var i;
var arr = [];
for (i = 0; i <= daysToDisplay; i += num) {
arr.push({
startDate: moment(startDate, 'YYYYMMDD').add(i, 'days').format('YYYYMMDD'),
numOfDays: daysToDisplay % num,
count: i
})
console.log(rem + ' ' + count);
if (rem === count) {
count = 0;
break;
}
count -= num;
}
if (count > 0) {
var leftover = daysToDisplay - arr[arr.length - 1].count;
arr.push({
startDate: moment(arr[arr.length - 1].startDate, 'YYYYMMDD').add(num, 'days').format('YYYYMMDD'),
numOfDays: daysToDisplay % num,
count: leftover + arr[arr.length - 1].count
});
}
return arr;
}
//console.log(buildQueue(moment(), 80, 100));
console.log(buildQueue(moment(), 5, 8));
//console.log(buildQueue(moment(), 15, 100));
//console.log(buildQueue(moment(), 30, 100));
//console.log(buildQueue(moment(), 45, 100));
I think the 'Expected outcome' was correct before you edited the question. I also note the sample code produced a count property that you don't want in the output.
Perhaps this code does what you want?
function buildQueue(startDate, numOfDays, daysToDisplay) {
var arr = []
while (daysToDisplay > 0) {
arr.push({
startDate: moment(startDate, 'YYYYMMDD')
.add(numOfDays * arr.length, 'days')
.format('YYYYMMDD'),
numDays: Math.min(numOfDays, daysToDisplay)
})
daysToDisplay -= numOfDays
}
return arr
}

Check if more than two date ranges overlap

I have multiple date ranges. I want to check if they are overlapping in javascript. When there are only two it is easy, I use:
if(start_times1 <= end_times2 && end_times1 >= start_times2) {}
But what is the formula when there are more than 2 date ranges?
You can use nested for loops with arguments
function dateRangeOverlaps(a_start, a_end, b_start, b_end) {
if (a_start <= b_start && b_start <= a_end) return true; // b starts in a
if (a_start <= b_end && b_end <= a_end) return true; // b ends in a
if (b_start < a_start && a_end < b_end) return true; // a in b
return false;
}
function multipleDateRangeOverlaps() {
var i, j;
if (arguments.length % 2 !== 0)
throw new TypeError('Arguments length must be a multiple of 2');
for (i = 0; i < arguments.length - 2; i += 2) {
for (j = i + 2; j < arguments.length; j += 2) {
if (
dateRangeOverlaps(
arguments[i], arguments[i+1],
arguments[j], arguments[j+1]
)
) return true;
}
}
return false;
}
Here is refined version of what Paul posted:
Added filter and null check to allow any number of entries
Changed the logic so that it can be applied on an array. Eg: [{"from": value, "to": value}]
Adjusted overlap check to allow times having end and start as same
Script:
function dateRangeOverlaps(a_start, a_end, b_start, b_end) {
if (a_start < b_start && b_start < a_end) return true; // b starts in a
if (a_start < b_end && b_end < a_end) return true; // b ends in a
if (b_start < a_start && a_end < b_end) return true; // a in b
return false;
}
function multipleDateRangeOverlaps(timeEntries) {
let i = 0, j = 0;
let timeIntervals = timeEntries.filter(entry => entry.from != null && entry.to != null && entry.from.length === 8 && entry.to.length === 8);
if (timeIntervals != null && timeIntervals.length > 1)
for (i = 0; i < timeIntervals.length - 1; i += 1) {
for (j = i + 1; j < timeIntervals.length; j += 1) {
if (
dateRangeOverlaps(
timeIntervals[i].from.getTime(), timeIntervals[i].to.getTime(),
timeIntervals[j].from.getTime(), timeIntervals[j].to.getTime()
)
) return true;
}
}
return false;
}
Below code comes from my project, maybe it will help you:
function dateRangeOverlaps(startDateA, endDateA, startDateB, endDateB) {
if ((endDateA < startDateB) || (startDateA > endDateB)) {
return null
}
var obj = {};
obj.startDate = startDateA <= startDateB ? startDateB : startDateA;
obj.endDate = endDateA <= endDateB ? endDateA : endDateB;
return obj;
}
//storing existing dates for comparison
public multipleExistingDates=[
{startDate:'02/03/2020 05:00:00',endDate:'02/03/2020 05:30:00'},
{startDate:02/04/2020 05:00:00'',endDate:'02/05/2020 05:00:00'},]
/The date to be compared with existing dates to check if the new date is overlapping with existing dates/
public checkOverlappingDsates(startDate:Date, endDate:Date):boolean{
return this.multipleExistingDates.some((elem)=>{
return( !((moment(endDate).diff(moment(elem.startDate))) < 0 ||
(moment(startDate).diff(moment(elem.endDate))) > 0;})
Note: If the date is overlapping, the function return true else false. Also , you would need to install moment for date comparison.
Why don't we use moment and moment-range, is it not supported across all browsers? 🤔
window['moment-range'].extendMoment(moment);
const events1 = [{
"Date": "05/15/2021",
"EndTime": "17:00",
"StartTime": "16:00"
},
{
"Date": "05/15/2021",
"EndTime": "18:00",
"StartTime": "17:00"
},
{
"Date": "05/15/2021",
"EndTime": "18:45",
"StartTime": "17:45"
}
];
const events2 = [{
"Date": "05/15/2021",
"EndTime": "17:00",
"StartTime": "16:00"
},
{
"Date": "05/15/2021",
"EndTime": "18:00",
"StartTime": "17:00"
},
{
"Date": "05/15/2021",
"EndTime": "19:45",
"StartTime": "18:45"
}
];
function checkOverlap(timeSegments) {
var overlap = timeSegments
.map(r =>
timeSegments.filter(q => q != r).map(q =>
moment.range(
moment(q.Date + " " + q.StartTime),
moment(q.Date + " " + q.EndTime)
).overlaps(
moment.range(
moment(r.Date + " " + r.StartTime),
moment(r.Date + " " + r.EndTime)
)
)
)
);
console.log(overlap.map(x => x.includes(true)).includes(true));
}
checkOverlap(events1);
checkOverlap(events2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/4.0.2/moment-range.js"></script>
Simply use the areIntervalsOverlapping function from date-fns, the "modern JavaScript date utility library".
You just have to pass the two dates as arguments to the function, and it will return true or false depending if the two dates overlaps or not.
Example
Check this example from their documentation:
areIntervalsOverlapping(
{ start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) },
{ start: new Date(2014, 0, 17), end: new Date(2014, 0, 21) }
)
//=> true
This example above returned true because the two dates overlaps. Note that the 0 number (the second argument) in Date(2014, 0, 10) represents the month of January.
You can also use this areIntervalsOverlapping function to check if other time intervals (like hours in the same day) overlaps, because in JavaScript a Date object also considers hours.
Installation
If, for example, you are using Node.js (or any framework that uses it), you just have to install date-fns with
npm install date-fns --save
And then import the desired functions inside your JavaScript code like:
import { areIntervalsOverlapping } from "date-fns";
Of course date-fns is not limited to Node.js. You can use it inside any JavaScript project.
Wouldn't be too hard to do recursively. Make a method overlap which returns the overlapping daterange for two dates. Then in your hasOverlap(list dates) method, if the list is two items, it's simple, else, return hasoverlap(overlap(dates[0], dates[1]), rest of list)
No matter the language, the basic logic to see if two date ranges overlap is:
max(range_1_start, range_2_start) <= min(range_1_end, range_2_end)
In JavaScript syntax, that might look like this:
function doDatesOverlap(start_1,end_1,start_2,end_2){
return Math.max(start_1,start_2) <= Math.min(end_1,end_2);
}
var start_1 = new Date('01/01/2023');
var end_1 = new Date('01/31/2023');
var start_2 = new Date('01/15/2023');
var end_2 = new Date('02/15/2023');
if(doDatesOverlap(start_1,end_1,start_2,end_2)){
console.log('They overlap!');
}

Multiple 'If' statements?

Assume i have a function checkTime like the one below where i have to check for multiple condition simultaneously.
var result=0;
function checkTime(time1, time2) {
if (time1 >= 0 && time2 <= 0) {
result = 1;
}
else if (time1 >= 0 && time2 <= 1) {
result = 4;
}
else if (time1 >= 2 && time2 <= 3) {
result = 5;
}
else if (time1 >= 4 && time2 <= 6) {
result = 6;
}
else if (time1 >= 7 && time2 <= 9) {
result = 7;
}
else if (time1 >= 11 && time2 <= 12) {
result = 8;
}
else if (time1 >= 13 && time2 <= 15) {
result = 9;
}
else if (time1 >= 16 && time2 <= 17) {
result = 10;
}
else if (time1 >= 19 && time2 <= 20) {
result = 11;
}
return result;
}
(The above given example is hypothetical)
The function i have used totally works,but:
Is there a better method or procedure or formula to replace this?(where it doesnt have to be this lengthy or ugly)
Thanx!
You can use an array to represent all the combination:
tests = [
{ time1: 0, time2: 0, result: 1 },
{ time1: 0: time2: 1, result: 4 },
...
];
for (var i = 0; i < tests.length; i++) {
if (time1 >= tests[i].time1 && time2 <= tests[i].time2) {
return tests[i].result;
}
}
If the code is identical, and only the values change, you could do something like this:
function checkTime(time1, time2) {
[
[0, 0, 0],
[0, 1, 0]
].forEach(function (it) {
if (time1 >= it[0] && time2 <= it[1]) {
return it[2];
}
});
}
Well, first off you have the possibility of an undefined result, so that makes things ambiguous. Should result start at 0 instead? This is an important detail. Second, you seem to be working with boundaries, so it would help to change the <= to < to make this clearer. (If so, the 7-9/11-12 section has a bug.) Third, you have an implicit comparison of time1 and time2, so make that explicit.
var result = 0;
var diff = time2 - time1;
var bounds = [21, 19, 16, 13, 11, 7, 4, 2, 0];
if (diff <= 0) result = 0; // unexpected outcome
else
for (position = 1; position < bounds.length; ++position) {
if (time1 >= bounds[position]) {
if (time2 < bounds[position - 1]) {
result = 3 + (bounds.size - position);
}
break;
}
}
return result;
Other implementations are possible, but it's hard to tell based on your question exactly what problem you're solving.
follow-up
This section of code has a gap:
else if (time1 >= 7 && time2 <= 9) {
result = 7;
}
else if (time1 >= 11 && time2 <= 12) {
result = 8;
}
If time = 10 and time2 = 10, there is no match. It's easy to miss this type of error when you are repeating yourself. Specifying lower and upper bounds for each condition is unnecessary repetition. Since I couldn't see a pattern to the bounds (which could be delegated to a function), I just put the lower bounds into an array and made sure it was sorted descending so that the loop could stop after the first match.

Categories