Merge overlaping objects with interval properties in array with javascript - javascript

I found similar questions but approved answers do not work for my problem.
I have an input: array of range objects each containing:
start: Integer, start of range,
end: Integer, end of the range.
Output should be:
An array of non-overlapping range objects covering the same range(s) as input ordered from smallest start to largest start. Two ranges are not overlapping if:
range1.start <= range2.start, and
range1.end >= range2.start
Input:
[
{ start: 8, end: 10 },
{ start: 5, end: 7 },
{ start: 9, end: 12 },
{ start: 2, end: 6 },
]
output:
[
{ start: 2, end: 7 },
{ start: 8, end: 12 }
]
As I mentioned, I tried applying solutions on the web for Merging overlapping intervals but they do not do the job.
Thank you.

You could sort the array by start and end and iterate the sorted array with a check for the ranges if they overlap.
var data = [{ start: 8, end: 10 }, { start: 5, end: 7 }, { start: 9, end: 12 }, { start: 2, end: 6 }],
result = data
.sort(function (a, b) { return a.start - b.start || a.end - b.end; })
.reduce(function (r, a) {
var last = r[r.length - 1] || [];
if (last.start <= a.start && a.start <= last.end) {
if (last.end < a.end) {
last.end = a.end;
}
return r;
}
return r.concat(a);
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

var ranges = [
{ start: 8, end: 10 },
{ start: 5, end: 7 },
{ start: 9, end: 12 },
{ start: 2, end: 6 }
];
function merge(ranges) {
// first, sort the ranges
ranges.sort((a, b) => a.start - b.start);
// take two ranges, and merges them together
var mergeFn = (a, b) => ({start: Math.min(a.start, b.start), end: Math.max(a.end, b.end)});
// check if two ranges overlap
var overlapFn = (a, b) => (b.start <= a.end);
// make current the first item of the array (start the array from 1 to not check the first item against itself)
var current = ranges[0];
var result = [];
for(var i = 1; i < ranges.length; i++) {
if(overlapFn(current, ranges[i])) // if the current range overlapping with this range
current = mergeFn(current, ranges[i]); // merge them into the current range
else { // if not
result.push(current); // add the current accumulated range as result
current = ranges[i]; // start accumulating another one from this range
}
}
result.push(current); // add the last result
return result;
}
console.log(merge(ranges));

Related

How do I turn this recursion into tail recursion?

With given array on unique numbers which are always greater than 0 I need to find all possible unique combinations of those numbers that are equal to a certain number when summed.
For example, getNumberComponents([7, 4, 3, 2, 5, 6, 8, 1], 8) should return
[ [ 7, 1 ], [ 4, 3, 1 ], [ 3, 5 ], [ 2, 5, 1 ], [ 2, 6 ], [ 8 ] ] because sum of all numbers in every subarray equals 8.
My solution:
function getNumberComponents(numArray, number) {
const arrayLength = numArray.length;
const allVariants = [];
function findComponents(currentIndex = 0, currentVariant = []) {
while (currentIndex < arrayLength) {
const currentElement = numArray[currentIndex];
const currentSum = currentVariant.reduce((acc, cur) => acc + cur, 0);
const sumWithCurrent = currentSum + currentElement;
if (sumWithCurrent === number) {
allVariants.push([...currentVariant, currentElement]);
}
currentIndex++;
if (sumWithCurrent < number) {
findComponents(currentIndex, [...currentVariant, currentElement]);
}
}
}
findComponents();
return allVariants;
}
But I wonder if it's possible to use tail recursion for that? I have no idea how to turn my solution into tail recursion.
To make this tail recursive, you could:
Keep track of all indices that were selected to arrive at the current sum. That way you can easily replace a selected index with the successor index.
In each execution of the function get the "next" combination of indices. This could be done as follows:
If the sum has not been achieved yet, add the index the follows immediately after the most recently selected index, and adjust the sum
If the sum has achieved or exceeded, remove the most recently selected index, and then add the successor index instead, and adjust the sum
If there is no successor index, then forget about this index and replace the previous one in the list, again adjusting the sum
If there are no more entries in the list of indices, then all is done.
Instead of accumulating a sum, you could also decrease the number that you pass to recursion -- saving one variable.
Make the function return the array with all variants, so there is no need for an inner function, nor any action that follows the function call.
Here is an impementation:
function getNumberComponents(numArray, number, selectedIndices=[], allVariants=[]) {
let i = selectedIndices.at(-1)??-1;
if (number < 0) { // Sum is too large. There's no use to adding more
i = numArray.length; // Force the while-condition to be true
} else if (number == 0) { // Bingo
allVariants.push(selectedIndices.map(idx => numArray[idx]));
}
while (++i >= numArray.length) { // No more successor index available
if (selectedIndices.length == 0) return allVariants; // All done
i = selectedIndices.pop(); // Undo a previous selection
number += numArray[i]; // Remove from sum
}
selectedIndices.push(i); // Select index and recur:
return getNumberComponents(numArray, number - numArray[i], selectedIndices, allVariants);
}
console.log(getNumberComponents([7, 4, 3, 2, 5, 6, 8, 1], 8));
Here is my version of your function, but using tail recursion. This is still a complex subject for me, check if there are no mistakes
function getNumberComponents(numArray, number, currentIndex = 0, currentVariant = [], allVariants = new Set()) {
if (currentIndex >= numArray.length) {
const currentSum = currentVariant.reduce((acc, cur) => acc + cur, 0);
if (currentSum === number) {
allVariants.add(currentVariant);
}
return Array.from(allVariants);
}
const currentElement = numArray[currentIndex];
const currentSum = currentVariant.reduce((acc, cur) => acc + cur, 0);
const sumWithCurrent = currentSum + currentElement;
if (sumWithCurrent <= number) {
allVariants = new Set([...allVariants, ...getNumberComponents(numArray, number, currentIndex + 1, [...currentVariant, currentElement], allVariants), ...getNumberComponents(numArray, number, currentIndex + 1, currentVariant, new Set())]);
} else {
allVariants = new Set([...allVariants, ...getNumberComponents(numArray, number, currentIndex + 1, currentVariant, new Set())]);
}
return Array.from(allVariants);
}
console.log(getNumberComponents([7, 4, 3, 2, 5, 6, 8, 1], 8));

Dynamic Programming Bottoms up approach clarification [duplicate]

This question already has answers here:
What is the difference between bottom-up and top-down?
(9 answers)
Closed 1 year ago.
So I have been really trying to grasp Dynamic Programming. I can say that I really understand the memoization top down approach, but the bottoms up approach is really confusing to me. I was able to solve rods cutting top down, but I had to seek the solution for the bottoms up. I just don't understand when to use a 1D array or a 2D array. Then the for loop within the bottoms up is just confusing. Can anyone help me understand the differences in these two codes conceptually?
// Top Down Memoizaton:
const solveRodCuttingTop = function(lengths, prices, n) {
return solveRodCuttingHelper(0, lengths, prices, n);
};
function solveRodCuttingHelper(idx, span, prices, n, memo = []) {
// BASE CASES
if (idx === span.length || n <= 0 || prices.length !== span.length) {
return 0;
}
let included = 0, excluded = 0;
memo[idx] = memo[idx] || [];
if (memo[idx][n] !== undefined) return memo[idx][n];
if (span[idx] <= n) {
included = prices[idx] + solveRodCuttingHelper(idx, span, prices, n - span[idx], memo);
}
excluded = solveRodCuttingHelper(idx + 1, span, prices, n, memo);
memo[idx][n] = Math.max(included, excluded);
return memo[idx][n];
}
// Bottoms up
const solveRodCuttingBottom = function(lengths, prices, n) {
const rods = Array.from({length: n + 1});
rods[0] = 0;
let maxRevenue = - Infinity;
for (let i = 1; i < rods.length; i++) {
for (let j = 1; j <= i; j++) {
maxRevenue = Math.max(maxRevenue, prices[j - 1] + rods[i - j])
}
rods[i] = maxRevenue
}
return rods[prices.length];
};
const lengths = [1, 2, 3, 4, 5];
const prices = [2, 6, 7, 10, 13];
This is an interesting problem. Maybe I'm over-simplifying it, but if you first calculate each price per length, you can determine the solution by selling as much as possible at the highest rate. If the remaining rod is too short to sell at the best rate, move onto the next best rate and continue.
To solve using this technique, we first implement a createMarket function which takes lenghts and prices as input, and calculates a price-per-length rate. Finally the market is sorted by rate in descending order -
const createMarket = (lengths, prices) =>
lengths.map((l, i) => ({
length: l, // length
price: prices[i], // price
rate: prices[i] / l // price per length
}))
.sort((a, b) => b.rate - a.rate) // sort by price desc
const lengths = [1, 2, 3, 4, 5]
const prices = [2, 6, 7, 10, 13]
console.log(createMarket(lengths, prices))
[
{ length: 2, price: 6, rate: 3 },
{ length: 5, price: 13, rate: 2.6 },
{ length: 4, price: 10, rate: 2.5 },
{ length: 3, price: 7, rate: 2.3333333333333335 },
{ length: 1, price: 2, rate: 2 }
]
Next we write recursive solve to accept a market, [m, ...more], and a rod to cut and sell. The solution, sln, defaults to [] -
const solve = ([m, ...more], rod, sln = []) =>
m == null
? sln
: m.length > rod
? solve(more, rod, sln)
: solve([m, ...more], rod - m.length, [m, ...sln])
const result =
solve(createMarket(lengths, prices), 11)
console.log(result)
[
{ length: 1, price: 2, rate: 2 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 },
{ length: 2, price: 6, rate: 3 }
]
Above, solve returns the rod lengths that sum to the maximum price. If you want the total price, we can reduce the result and sum by price -
const bestPrice =
solve(createMarket(lengths, prices), 11)
.reduce((sum, r) => sum + r.price, 0)
console.log(bestPrice)
32

How do i multiply every number in the array from position tabProsent[I] to the left?

How do i multiply every number in the array from position tabProsent[I] to the left? It is not working for me to write tabProsent[i].reduceRight(getSum).
function getSum(total, num) {
return total * num;
}
// Update the current slider year (each time you drag the slider handle)
slider.oninput = function() {
output.innerHTML = this.value;
var i;
for (i = 0; i < tabAar.length; i++) {
if (slider.value == tabAar[i]) {
output2.innerHTML=Math.round(startVerdi * tabProsent[i].reduceRight(getSum));
}
}
}
This is a possible solution.
Note: I don't know what the result should be if the index is negative, actually then you get an result of 1. If index is greater then array-length the I reduce pos to the array-length.
function getMult(arr, pos) {
if (pos>=arr.length)
pos = arr.length-1;
let res =1;
for (let i=0; i<= pos; i++) {
res *= arr[i];
}
return res;
}
console.log(getMult([1,2,3,4,5], 3));
A functional approach could look like this:
let multToLeft = (arr, num, m) => {
if (num < 0 || num > arr.length) throw new Error(`Invalid num ${num}`);
return [ ...arr.slice(0, num).map(v => v * m), ...arr.slice(num) ];
};
let examples = [
[ [ 1, 2, 3, 4, 5 ], 2, 10 ],
[ [ 1, 2, 3, 4, 5 ], 3, 20 ],
[ [ 1, 2, 3, 4, 5 ], 5, 11 ]
];
for (let [ arr, num, m ] of examples) {
console.log(`multToLeft(${JSON.stringify(arr)}, ${num}, ${m}) -> ${JSON.stringify(multToLeft(arr, num, m))}`);
}
Note that I am working with num instead of index. I've chosen num to indicate the actual number of elements to the left of the array that get multiplied. This makes more sense than working with index, as it becomes messy to apply the multiplication to zero elements in the array (since supplying 0 would still mean to apply the multiplication to the first array item).

Group multiple overlapping timeblocks that may not have a direct overlap

I am trying to group timeslots by overlap but I can't figure out how to do it exactly.
I have a pretty simple array in the form of [{start_at: Date, end_at: Date, etc.etc. }]
And I lay them out in my view like this
<---slot1----><----slot5----><--slot6-->
<--slot2-><--slot4---> <--slot7-->
<----slot3---->
Finding directly overlapping slots isn't that hard, I just compare a slot with the next one with (StartA <= EndB) and (EndA >= StartB)
from here.
Now I want to group my overlapping slots (slot 1, 2, 3, 4 and 5) but not include slot 6 and 7, and put those two in their own group. into something like [[Slot (has 1 through 5)][Slot (has 6 and 7)]]
I am kind of lost with this problem right now and I hope anybody here can help me.
I'd suggest creating a Slot object that holds:
an array of items in the slot,
the earliest start_at date of those items,
the latest end_at of those items.
By keeping an up to date slot-range, you don't have to compare a new item to each of the slot's items. You'll only have to compare to the slot itself.
Now, you'll have to sort your items by start_at. You can then reduce the array by:
Create a Slot for the first item
Set the Slot's start_at and end_at to mimic those of the first item
Go to the second item, check for overlap with the first Slot
If it overlaps,
push the second item to the Slot's items array, and
Set start_at to the minimum of Slot.start_at and item2.start_at
Do the same (max) for end_at
If it does not overlap,
Create a new Slot for the second item, repeat with this Slot and item3 (et cetera)
A sample implementation (I'd advice you to rewrite it based on your personal preferences. I didn't make any neat classes/prototypes/etc., nor did I test it thoroughly)
function createSlot(initialItem) {
var slot = {
items: [initialItem],
start: initialItem.start,
end: initialItem.end
};
slot.addItem = function(item) {
slot.items.push(item);
slot.start = Math.min(slot.start, item.start);
slot.end = Math.max(slot.end, item.end);
}
return slot;
};
function itemsOverlap(item1, item2) {
return item1.start <= item2.end &&
item1.end >= item2.start;
};
var slots = [];
var items = randomItems(10);
items.slice(1).reduce(function(currentSlot, item) {
if (itemsOverlap(currentSlot, item)) {
currentSlot.addItem(item);
return currentSlot;
}
slots.push(currentSlot);
return createSlot(item);
}, createSlot(items[0]));
console.log(
slots.map(function(slot) { return slot.items.length; }));
// Create random data
function randomItems(n) {
var arr = [];
for (var i = 0; i < n; i += 1) {
arr.push(generateRandomItem());
}
return arr.sort(function(a, b) { return a.start - b.start; });
};
function randomHourTimespan() {
return Math.random() * 60 * 60 * 1000;
};
function randomHalfDayTimespan() {
return randomHourTimespan() * 12;
};
function generateRandomItem() {
var start = Date.now() + randomHalfDayTimespan();
var end = start + randomHourTimespan();
return { start: new Date(start), end: new Date(end) };
}
I implemented a simple algorithm to group the slots regarding to the start and end values.
Here is a working fiddle https://jsfiddle.net/LeoAref/gg6q0mby/, and you will find a visual presentation for the grouping.
var timeSlots = [
{start: 0, end: 3},
{start: 1, end: 2},
{start: 2, end: 4},
{start: 4, end: 6},
{start: 4, end: 8},
{start: 5, end: 6}
];
timeSlots.forEach((slot, index) => {
var slotElem = document.createElement('div');
slotElem.classList.add('slot');
slotElem.style.top = index * 25 + 'px';
slotElem.style.left = slot.start * 30 + 'px';
slotElem.style.width = (slot.end - slot.start) * 30 + 'px';
document.body.appendChild(slotElem);
});
var groups = [];
timeSlots.forEach(slot => {
added = false;
if (groups.length) {
var index = 0;
do {
group = groups[index];
if (slot.start >= group.start && slot.start < group.end ||
slot.end <= group.end && slot.end > group.start
) {
group.slots.push(slot);
group.start = Math.min(slot.start, group.start);
group.end = Math.max(slot.end, group.end);
added = true;
}
} while (!added && ++index < groups.length);
if (!added) {
groups.push({start: slot.start, end: slot.end, slots: [slot]});
}
} else {
groups.push({start: slot.start, end: slot.end, slots: [slot]});
}
})
groups.forEach(group => {
var groupElem = document.createElement('div');
groupElem.classList.add('group');
groupElem.style.left = group.start * 30 + 'px';
groupElem.style.width = (group.end - group.start) * 30 - 2 + 'px';
document.body.appendChild(groupElem);
})
#user3297291's description/algorithm of a time interval grouping function is really good. Here's a function that was created/posted on GitHub by the user 'blaston' from several years ago that follows the algorithm. I'm posting it here in case the content/link disappears. I started with blaston's function for its simplicity to follow and swapped array groups in blaston's function for slot objects from #user3297291's post.
// Group all overlaping intervals
// * * * * * * *
// This is an approach to a problem the engineers at Google Calandar/ Outlook probably faced.
// You have events that may overlap and you want to display them in such a way that
// they don't overlap with each other. One approach is to distribute them into columns.
// Each column has events that don't overlap with each other.
// Cost: O(n*log n) if the interval aren't sorted by the starting time,
// O(n) otherwise.
// Sample run: groupOverlapingIntervals([ [2, 5], [5, 6],[3, 4] ])
// Output: [ [ [2, 5], [3, 4], [5, 6] ] ]
function groupOverlapingIntervals(intervals) {
intervals.sort(function(a, b) {
return a[0] - b[0];
});
var groups = [
[intervals[0]]
];
var j = 0;
var end = intervals[0][1];
for (var i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= end) {
if (intervals[i][1] > end) {
end = intervals[i][1];
}
groups[j].push(intervals[i]);
} else {
groups.push([intervals[i]]);
j++;
end = intervals[i][1];
}
}
return groups;
}
var intervals = [
[2, 5],
[5, 6],
[3, 4],
[7, 8],
[6.5, 9],
[10, 11.5]
];
var groups = groupOverlapingIntervals(intervals);
console.log(groups);

How to find all overlapping ranges and partition them into chunks?

I have an array of ranges and I want to be able to find all the overlapping ranges:
For example:
var examples = [
// Group 1
{start: 9, end: 10.50}, // Range 1
{start: 10, end: 10.50}, // Range 5
// Group 2
{start: 11, end: 13}, // Range 2
{start: 13.5, end: 14.5}, // Range 3
{start: 11.5, end: 14} // Range 4
]
Range 2 overlaps with Range 4
Range 3 overlaps with Range 4
Although Range 2 does not overlap with Range 3, because they both overlap with Range 4. They will be put into the same group
Range 1 and Range 5 only overlap with each other and so they will be in their own group
JSFiddle here:
http://jsfiddle.net/jukufon7/2/
Here's my take:
jsfiddle
var examples = [
{start: 17, end: 20},
{start: 9, end: 10.50},
{start: 15, end: 17},
{start: 11, end: 12},
{start: 18, end: 19.5},
{start: 19.5, end: 22},
{start: 11.5, end: 12.5},
{start: 11.5, end: 13},
{start: 17.5, end: 18.5},
{start: 19, end: 19.5},
{start: 22, end: 25}
]
function partitionIntoOverlappingRanges(array) {
array.sort(function (a,b) {
if (a.start < b.start)
return -1;
if (a.start > b.start)
return 1;
return 0;
});
var getMaxEnd = function(array) {
if (array.length==0) return false;
array.sort(function (a,b) {
if (a.end < b.end)
return 1;
if (a.end > b.end)
return -1;
return 0;
});
return array[0].end;
};
var rarray=[];
var g=0;
rarray[g]=[array[0]];
for (var i=1,l=array.length;i<l;i++) {
if ( (array[i].start>=array[i-1].start)
&&
(array[i].start<getMaxEnd(rarray[g]))
) {
rarray[g].push(array[i]);
} else {
g++;
rarray[g]=[array[i]];
}
}
return rarray;
} // end partitionIntoOverlappingRanges
Results from examples above:
Here's a simple scanning algorithm. It executes in O(n log n) because of the necessity to sort the ranges.
The basic idea is to just scan from left to right looking for both start and end points (which requires a sorted list of each of those). While scanning, keep track of the number of active ranges (that is, ranges whose start point has been encountered and whose endpoint has not yet been encountered). Every time you hit a start-point, the range needs to be added to the current group. The range count is maintained by incrementing it at each start point and decrementing it at each end point. Every time the count returns to 0, a complete group has been found.
If you want to compute the simplified set of ranges instead of the groups, you can simplify. Instead of keeping a set of ranges in a group, the start point of the current composed group is set when the active range count increments from 0 to 1, and the end point is set when the active range count decrements from 1 to 0. In this case, you only need a sorted list of start points and a sorted list of end points (in the algorithm as presented, the sorted start points are found by sorting the ranges themselves by start point. The group is needed so that the range can be added to the accumulated group.)
Sort the ranges by their start values.
Make a list of the end values, and sort it (it's not necessary to know which range belongs to an endpoint). Call this end_values.
Initialize current_group to an empty set, and active_range_count to 0. Initialize current_range and current_end to 0.
Loop until done:
If current_range is a valid index into ranges and ranges[current_range].start is less than end_values[current_end]:
Add ranges[current_range] to current_group, increment current_range and increment active_range_count.
Loop.
Otherwise, if current_end is a valid index into end_values:
Decrement active_range_count and increment current_end.
If active_range_count is 0, then current_group is complete; save it, and reinitialize current_group to an empty set.
Loop.
Otherwise, done.
Here are both versions in javascript:
/* Partition an array of ranges into non-overlapping groups */
/* Side-effect: sorts the array */
function partition(ranges) {
var end_values = ranges.map(function(r){return r.end}).sort(function(a, b){return a - b})
ranges.sort(function(a, b){return a.start - b.start})
var i = 0, j = 0, n = ranges.length, active = 0
var groups = [], cur = []
while (1) {
if (i < n && ranges[i].start < end_values[j]) {
cur.push(ranges[i++])
++active
} else if (j < n) {
++j
if (--active == 0) {
groups.push(cur)
cur = []
}
} else break
}
return groups
}
/* Given a array of possibly overlapping ranges, produces
* an array of non-overlapping ranges covering the same
* values.
*/
function compose_ranges(ranges) {
var starts = ranges.map(function(r){return r.start}).sort(function(a, b){return a - b})
var ends = ranges.map(function(r){return r.end}).sort(function(a, b){return a - b})
var i = 0, j = 0, n = ranges.length, active = 0
var combined = []
while (1) {
if (i < n && starts[i] < ends[j]) {
if (active++ == 0) combined.push({start: starts[i]})
++i
} else if (j < n) {
if (--active == 0) combined[combined.length - 1].end = ends[j]
++j
} else break;
}
return combined
}
The concept is simple: record the max range of every certain group.
Give a shot to the code below.
function contains(range, number) {
return number > range.start && number < range.end
}
function partitionIntoOverlappingRanges(array) {
var groups = [];
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < groups.length; j++) {
if (contains(groups[j], array[i].start) || contains(groups[j], array[i].end)) {
groups[j].arrays.push(array[i]);
if (groups[j].start > array[i].start) groups[j].start = array[i].start;
if (groups[j].end < array[i].end) groups[j].end = array[i].end;
break;
}
}
if (j == groups.length) {
groups.push({
start: array[i].start,
end: array[i].end,
arrays: [array[i]]
})
}
}
return groups
}

Categories