NodeJS combining object array range properties - javascript

the title may be a bit confusing but I'll explain it in detail. I have a table in UI and user can choose date ranges from there like;
monday - {in:"13:00:00",out:"13:59:59"}
tuesday - [{in:"13:00:00",out:"13:59:59"},{in:"14:00:00",out:"14:59:59"}]
user can only choose multiple hour intervals for one day. I already made the grouping the intervals according to their date and combining the intervals like
tuesday- [{in:"13:00:00",out:"14:59:59"},{in:"14:00:00",out:"14:59:59"}]
in the first iteration. But I couldn't figure out how to make it for more than 4 or 5 hour intervals.FYI I'm using lodash for sorting and grouping and moment for converting hours to int.
If user enters 5 intervals for tuesday like [{in:"13:00:00",out:"13:59:59"},{in:"14:00:00",out:"14:59:59"},{in:"15:00:00",out:"15:59:59"},{in:"18:00:00",out:"18:59:59"},{in:"19:00:00",out:"19:59:59"}]
I want ranges to be combined like ;
[{in:"13:00:00",out:"15:59:59"},{in:"18:00:00",out:"19:59:59"}]
Any help or suggestion will be appreciated.

Assuming that your input data is chronological then one way of implementing your reduced time table is this;
var timeSlices = [{in:"13:00:00",out:"13:59:59"},{in:"14:00:00",out:"14:59:59"},{in:"15:00:00",out:"15:59:59"},{in:"18:00:00",out:"18:59:59"},{in:"19:00:00",out:"19:59:59"}],
ts = new Date(),
te = new Date(),
reduced = timeSlices.reduce((p,c) => {p.length ? (ts.setHours(...p[p.length-1].out.split(":")),
te.setHours(...c.in.split(":")),
te-ts <= 1000 ? p[p.length-1].out = c.out
: p.push(c))
: p.push(c);
return p;},[]);
console.log(reduced);
However if the objects with in and out times are located arbitrary in the array then a more conceptual approach like first sorting them according to their in times would be essential. That wouldn't be a big deal though.

Assuming ranges are composed of Moment instances and you wanted to combine any two ranges where the end of one range either overlapped another range or was less than or equal to one second behind the start of another range, this function should be able to combine the ranges
function combineRanges (ranges) {
if (ranges.length <= 1) {
return ranges
}
ranges = ranges.sort(byStart)
var current = ranges[0]
var combined = [current]
for (var i = 1; i < ranges.length; i++) {
var next = ranges[i]
if (current.out.diff(next.in, 'seconds') > 1) {
combined.push(next)
current = next
} else if (current.out.isBefore(next.out)) {
current.out = next.out
}
}
return combined
}
function byStart (a, b) {
return a.in - b.in
}

Related

What can I use to add new value to previous value and repeat this a certain amount of times? (Javascript)

I'm trying to get an array of numbers based on a calculation that keeps adding a set amount to the previous amount until this have repeated 20 times. The initial number is a negative number because the client pays an initial amount of money for a solar power system and then the calculation should subtract an amount each month based on how much the client saves by not having to pay for electricity. It needs to be an array (I think) because it needs to go into a chart. Here's a google worksheet that might make what I'm trying to more clear. The part of the sheet that is relevant to my question is in columns T and U in pink.
I did tonne of reading on loops, different array types (reduce and map). I'm new to this so it didn't seem any of those types of arrays will do what I need to be done. I found the below code somewhere and it seemed like this is the closest to what I need to happen but I could be completely off track (my adjusted version is further down):
// program to generate fibonacci series up to n terms
// take input from the user
const number = parseInt(prompt('Enter the number of terms: '));
let n1 = 0, n2 = 1, nextTerm;
console.log('Fibonacci Series:');
for (let i = 1; i <= number; i++) {
console.log(n1);
nextTerm = n1 + n2;
n1 = n2;
n2 = nextTerm;
}
I tried to adjust it to try get it to do what I need it to do but in console it shows one number and then the rest is NAN. I know this means not a number but I don't know why or how to fix it:
function runningNetProfit(n) {
var profitSequence = [0];
var nextYear = (monthlyEstimatedSavings * 12);
for (var i = negSystemCost; i < n - 1; i++) {
profitSequence.push(nextYear);
nextYear = nextYear + profitSequence[i];
}
return profitSequence;
}
console.log(runningNetProfit(20))
I added what I did (all of the code) to a codepen as well, maybe it can make my question more clear, that can be found here The javascript relevant to this question is right at the bottom from line 145. Any advice would be much appreciated.
See if this code works for you:
It takes a given installation cost, the price they pay for electricity, and the number of months, then spits out an array with these numbers.
const installCost = 250000;
const electricityCost = 45000;
const numMonths = 20
const newArray = Array.from({length: numMonths})
const updatedArray = newArray.map((_, index) => index * electricityCost - installCost);
console.log(updatedArray) // returns [-250000,-205000,-160000,-115000,-70000,-25000,20000,65000,110000,155000,200000,245000,290000,335000,380000,425000,470000,515000,560000,605000]
Here's the code sandbox for it: https://codesandbox.io/s/blue-pine-l5rjc8?file=/src/index.js

Get a list of overlapping time ranges from a set of appointments

Title isn't great, and I'm open to suggestions.
Here's my basic problem:
I have a set of appointments, each with a start time and end time.
Given that set, what I want is a new set of ranges [ start_time, end_time ] for all periods where there are n overlapping appointments.
So, for example, given the set (timestamps simplified to small numbers for readability)
[
[ 1, 3 ],
[ 2, 4 ],
[ 2, 4 ],
[ 5, 7 ],
[ 6, 8 ],
[ 7, 8 ]
]
...and assuming I want all ranges that have at least 3 different appointments occurring within them, the result should be
[
[ 2, 3 ],
[ 6, 7 ]
]
To make this a bit less abstract...
Imagine I run a 24-hour window-tinting service with 3 installers on staff at all times. On my website, I want to show all available installation times. So I need to hide any time ranges where I've already got 3 appointments scheduled.
Not necessarily asking anyone to write code – but if there's a well-known algorithm for this class of problem that someone could point me to, I'd appreciate it.
Thanks.
[EDIT] added javascript tag because i'll be implementing this in Node, but answers don't need to be in JS.
[EDIT 2] I'm looking for a pretty general solution, so let's say that appointments can start at any time (not normalized to hour or 30 minute chunks) and can be of any duration
I think it works to create a histogram from the input ranges, then iterate through the histogram locating ranges where the height is greater than or equal to your target overlap, in this case 3.
BTW, I don't think [6,7] is a valid range given your inputs - I believe it should be [7,7]. At least that's what my code produces :)
Here's some Java code to illustrate:
public static void main(String[] args)
{
int[][] ranges = {{1,3},{2,4},{2,4},{5,7},{6,8},{7,8}};
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for(int[] range : ranges)
{
min = Math.min(min, range[0]);
max = Math.max(max, range[1]);
}
int[] hist = new int[1+max-min];
for(int[] range : ranges)
for(int i=range[0]; i<=range[1]; i++) hist[i-min]++;
int overlap = 3;
for(int i=0; i<hist.length; i++)
{
int j = i;
while(i<hist.length && hist[i] >= overlap) {i++;}
if(i>j)
System.out.println((j+min) + " : " + (i+min-1));
}
}
Output:
2 : 3
7 : 7
EDIT
I was dissatisfied with histogram approach since it relies on integer ranges and would be inefficient for long ranges. It occurred to me that you could instead sort the range endpoints, keeping track of whether they were at the start or end of the range, then walk through the endpoints keeping a counter of active ranges (increment when you encounter a start, decrement when you encounter an end). When the counter first rose above or fell below your threshold, in your case 3, you'd output the range.
I now see that MBo suggested this same approach.
Here's some more code to illustrate:
static class RangeEnd
{
int time;
int delta;
public RangeEnd(int pos, int delta)
{
this.time = pos;
this.delta = delta;
}
}
public static void main(String[] args)
{
int[][] ranges = {{ 1,3},{2,4},{2,4},{5,7},{6,8},{7,8}};
RangeEnd[] ends = new RangeEnd[2*ranges.length];
int i=0;
for(int[] range : ranges)
{
ends[i++] = new RangeEnd(range[0], 1);
ends[i++] = new RangeEnd(range[1], -1);
}
Arrays.sort(ends, new Comparator<RangeEnd>()
{
#Override
public int compare(RangeEnd e1, RangeEnd e2)
{
if(e1.time < e2.time) return -1;
else if(e1.time > e2.time) return 1;
else if (e1.delta > e2.delta) return -1;
else return 1;
}
});
int overlap = 3;
int count = 0;
boolean active = false;
int start = 0;
for(RangeEnd end : ends)
{
count += end.delta;
if(count >= overlap)
{
if(!active)
{
start = end.time;
active = true;
}
}
else if(active)
{
System.out.println(start + " : " + end.time);
active = false;
}
}
}
Make array/list of pairs: {time, flag = +1 for start of interval, -1 for the end of interval}
Sort list by time key. In case of tie account for start/end flag (end before start if intervals like [1,2] and [2,3] should not intersect)
Make Counter = 0
Traverse list, for every pair add flag to Counter. When Counter changes from n-1 to n - output range begins, when Counter changes from n to n-1 - output range ends
Assuming you can place things, say, on 30 minute intervals, then just start at first interval with #appts=0, and at every interval point, increment for every appt starting now and decrement for every appt ending now. #appts will always track how many appts are active at the current interval.
If you want to be really crazy efficient, you can "bucket sort" your starting end ending times into buckets for each interval, then the whole process will be linear. But unless you have a super large number of appointments, it will also work to just look them up as you go along.
What do your time stamps and ranges look like exactly? Are they daily/ hour / half hour / minute specific?
Here's a possible solution:
Let's say your time stamps are hour specific.
Declare a dictionary to hold a string key and int value. Key will represent time stamp to the hour e.g. "08-30-17 23". Value will be the count of how many appointments will be/are taking place in that hour.
Now Loop through your set of ranges. For each range, use another loop to go through the hours between the start and end time. For each of those hours, increment the count by 1 in the dictionary for that time stamp (with hour granularity).
At the end you should have the number of appointments taking place for every hour found in your data. If you have three appointments between 5 and 6pm and also 6 and 7pm of a given day then you'll need some more logic to transform that into a 5 to 7pm range.

Time Complexity - Bad Recursion - British Change Combinations

I recently came up with a naive (+ poor) solution to the British Change Problem (i.e. how many combinations of coins can generate a given total). I have a better solution now, but was still interested in solving the time and space complexity of the two solutions below.
Worst Solution
This solution recursively tries combining every number against itself and every other number, resulting in a lot of duplicate work. I believe it's O(n^n) time and not sure how to measure space complexity (but it's huge, since we're storing every result). Thoughts?
var makeChange = function(total){ // in pence
var allSets = new Set();
var coins = [1,2,5,10,20,50,100,200];
var subroutine = (arr, total) => {
if(total < 0){ return; }
if(total === 0){
allSets.add(''+arr);
} else {
// increase each coin amount by one and decrease the recursive total by one
for(var i = 0; i<coins.length; i++){
if((total - coins[i]) >= 0){
subroutine(arr.slice(0,i).concat(arr[i]+1).concat(arr.slice(i+1)), (total - coins[i]))
}
}
}
};
var zeros = new Array(coins.length).fill(0);
subroutine(zeros, total);
return allSets.size;
};
Improved Solution
This solution still has massive space complexity but I believe the time complexity has -improved- to O(n!) since we're recursing on smaller subsets of coins each time.
var makeChange = function(total){ // in pence
var allSets = new Set();
var coins = [1,2,5,10,20,50,100,200];
var subroutine = (arr, total, start) => {
if(total < 0){ return; }
if(total === 0){
console.log(''+arr);
allSets.add(''+arr);
} else {
// only solve for coins above start, since lower coins already solved
for(var i = start; i<coins.length; i++){
if((total - coins[i]) >= 0){
subroutine(arr.slice(0,i).concat(arr[i]+1).concat(arr.slice(i+1)), (total - coins[i]), i);
}
}
}
};
var zeros = new Array(coins.length).fill(0);
for(let i = 0; i<coins.length; i++){
subroutine(zeros, total, i);
}
return allSets.size;
};
Please help me to understand if my time/space complexity estimates are correct, and how to better estimate future problems like these. Thanks!
The complexity of the first algorithm is not actually an O(n^n). N is a variable which represents your input. In this case, I will refer to the variable "total" as your input, so N is based on total. For your algorithm to be O(n^n), it's recurrence tree would have to have a depth of N and a branching factor of N. Here, your depth of your recurrence is based on the smallest variable in your coins array. There is one branch of your recursion tree where you simply subtract that value off every time and recurse until total is zero. Given that that value is constant, it is safe to say your depth is n. Your branching factor for your recursion tree is also based off of your coins array, or the number of values in it. For every function call, you generate C other function calls, where C is the size of your coins array. That means your function is actually O(n^c) not O(n^n). Your time and space complexities are both based off of the size of your coins array as well as your input number.
The space complexity for your function is O(n^c * c). Every time you call your function, you also pass it an array of a size based on your input. We already showed that there are O(n^c) calls, and each call incorporates an array of size c.
Remember when analyzing the complexity of functions to take into account all inputs.

Group nearby dates together

I have a list of variable dates that I would like to group together, preferably in javascript.
ie.
2014-08-12
2014-08-10
2014-07-28
2014-07-27
2014-01-27
2013-04-27
2003-02-12
This list of days can be completely dynamic, but here is an example resultset.
Can anyone think of an elegant way to group dates that are considered to be 'near' each other together, which in this case would be:
2014-08-12
2014-08-10
2014-07-28
2014-07-27
2014-01-27
2013-04-27
2003-02-12
A way to do this is:
Convert all date strings to numbers using .getTime()
Sort
Group
Convert back to date string
An example of this:
var dates = ['2014-08-12', '2014-08-10', '2014-07-28', '2014-07-27', '2014-01-27', '2013-04-27', '2003-02-12'],
groups = [],
last = 0
dates.map(function (each) {
return new Date(each).getTime() // 1.
}).sort(function (a, b) {
return b-a // 2.
}).forEach(function (each) {
if (Math.abs(each-last) > 1e10) { // 1e10 ms = 116 days
groups.push([]) // 3.
}
groups[groups.length-1].push(each)
last = each
})
groups.map(function (dates) {
return dates.map(function (each) {
return new Date(each).toISOString().substr(0, 10) // 4.
}).join('\n')
}).join('\n\n')
You can adjust the 1e10 value to whatever nearby dates means to your application

Find minimum and maximum dates

There is an array:
var a = new Array();
It contains date entries like this: '2012-09-12 09:20', etc
I need to find minimum and maximum dates using javascript. This code does not work with time values.
var minT = Math.min.apply(Math, a);
var maxT = Math.max.apply(Math, a);
How can I solve this problem in javascript? It seems to be quite complex as I'm not very experienced in this language.
If your array contains Date objects, then this should work. If it just contains strings like '2012-09-12 09:20', then you can sort them, and get the 1st and last elements.
a.sort(function(a, b){
return Date.parse(a) - Date.parse(b);
});
var maxT = a[a.length-1];
var minT = a[0];
Math.min/max only compares numbers, not strings. Don't use them to represent the dates, but use Date objects - they will be compared by their internal timestamp number. Still, the max/min will return that internal number, so you would need to convert it back to a Date (see Min/Max of dates in an array?):
However, if you want to use the strings or can't use the recreated Date, you will need to run manually through the array - either with a for-loop, or the ES5.1-only iterator method .reduce():
var min = datestrings.reduce(function(min, cur) {
return cur < min ? cur : min;
});
// is equivalent to
var min = datestrings[0];
for (var i=1; i<datestrings.length; i++)
if (datestrings[i] < min)
min = datestrings[i];
If your code does not need to be efficient, you also just can sort the array and get the first and last values. The default alphanumeric sorting will do it for your date format, so this is really simple:
datestrings.sort();
var min = datestrings[0],
max = datestrings[datestrings.lengh-1];
Try this:
var maxDate=new Date(Math.max.apply(null,dates));
var minDate=new Date(Math.min.apply(null,dates));
I found it on an earlier question
This should do it:
var maxT=new Date(Math.max.apply(null,a));
var minT=new Date(Math.min.apply(null,a));
If you must work with strings you could define a function:
function maxDate(data){
var max = '';
for(var i=0; i<data.length; i++)
if(data[i]>max)
max=data[i];
return max;
}
And then:
var maxT=maxDate(a);
DISCLAIMER: This second method will only work if all the date strings are in the same format, if you have different format dates in your array you will not be able to use this function.
Is the array filled with Date objects?
If so, compare them using them, and sort them using one of the many known algorithms.
If not, recreate the array with Date objects, one for each of them, and do as I said above, by ordering the array.

Categories