Group multiple overlapping timeblocks that may not have a direct overlap - javascript

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);

Related

How to calculate the sum and product of each element of multiple arrays?

Please bear with me this is difficult to explain. I will first explain how to do it successfully with only one set of data.
First, say I have an array like so yValuesMinusMean = [-5, -4, -1, 10]
I have another array like so xValuesMinusMean = [ 2.75,3.75,6.75,5.75 ]
Both of the above arrays can have numerous values. However, the length of both arrays is the same. So if the first one has 4, then the second one will definitely have 4.
I want to calculate the sum and product of the arrays. This is what I mean:
var sumOfXTimesYValues = this.calculateProductAndSum(yValuesMinusMean, xValuesMinusMean);
calculateProductAndSum(yValuesMinusMean = [], xValuesMinusMean = []) {
let total = 0;
for(let i = 0; i < yValuesMinusMean.length; i++) {
let product = (yValuesMinusMean[i] * xValuesMinusMean[i]);
total += product;
};
return total;
},
The result of this: console.log('sumOfXTimesYValues', sumOfXTimesYValues); is
17
LOGIC : (-5 * 2.75) + (-4 * 3.75) + (-1 * 6.75) + (10 * 5.25) = 17
So far, everything works. However, I want to make it so that instead of xValuesMinusMean being a single array with multiple numerical values, it will be a single array containing multiple arrays, with each array having the same number of elements as in yValuesMinusMean. Like so:
xValuesMinusMean = [ [ 2.75,3.75,6.75,5.75 ], [-2,-1,2,1]. .... ]
END GOAL: sumOfXTimesYValues = [17, 22, ...]
Logic for the second array item: (-5 * -2) + (-4 * -1) + (-1 * 2) + (10 * 1) = 22
Essentially, you're multiplying each value in each array in xValuesMinusMean with a value in yValuesMinusMean in the same order. So, -5 is the 0th item in the yValuesMinusMean array, and -2 is the 0th item in the array within xValuesMinusMean. So -5 * -2.
My next steps would be to do something like this:
xValuesMinusMean.forEach(element => {
for(let i = 0; i < xValuesMinusMean.length; i++) {
let product = (newCategoryArray[i] * xValuesMinusMean[i]);
total += product;
};
});
However, it yields the following: sumOfXTimesYValues = 352, which isn't correct. How would I be able to achieve the end goal?
END GOAL: sumOfXTimesYValues = [17, 22, ...]
Create a generic function for computing a scalar product (you've already got it):
function scalarProduct(a, b) {
let res = 0;
for(let i = 0; i < a.length; i++) {
res += a[i] * b[i];
}
return res;
}
and then map it over your matrix (array of vectors):
result = xValuesMinusMean.map(vec => scalarProduct(vec, yValuesMinusMean))
You can have one reduce function where you will take product of arrays and store it in accumulator.
const val =[ [ 2.75,3.75,6.75,5.25 ], [-2,-1,2,1]];
const yvalues = [-5, -4, -1, 10];
console.log(val.map(o=>o.reduce((a,e,i)=>a+=e*yvalues[i],0)));
Looks like your calculation is not correct first set of arrays will also return 22.
Live Demo :
const yValuesMinusMean = [-5, -4, -1, 10];
const xValuesMinusMean = [[2.75, 3.75, 6.75, 5.75], [-2, -1, 2, 1]];
const finalArr = [];
xValuesMinusMean.forEach(arr => {
let cal = 0;
arr.forEach((item, index) => {
cal += item * yValuesMinusMean[index]
});
finalArr.push(cal);
});
console.log(finalArr); // [22, 22]

Order a array with distance between items

I have an array with a lot of items. For clarification, I will use an array of numbers:
[1, 2, 3, 4, 5, 6, 7, 8]
I also have an array with pair of items which should be distant from each other:
[[1, 3], [6, 8], [2, 5]]
I need to random this array, but ensure that this pair of items will have a distance of arr.length - pairs.length (3 on this case) between them.
On this case, some of the correct orders should be:
[1, 6, 4, 2, 3, 8, 7, 5]
[3, 4, 5, 8, 1, 7, 2, 6]
All pair items have a distance of 3+. I need a function to sort the array but follow this rule.
Can someone help me?
I created an algorithm but it isn't working and the loop is not stopping, even with the last value being valid according to the rules
const arr = [1, 2, 3, 4, 5, 6, 7, 8]
const pairs = [[1, 3], [6, 8], [2, 5]]
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
const distance = arr.length - pairs.length
const getDistance = (arr, a, b) => {
return Math.abs(arr.indexOf(a) - arr.indexOf(b))
}
const isValid = (arr) => {
for (const pair of pairs){
if (getDistance(arr, pair[0], pair[1]) < distance){
return false
}
}
return true
}
const similars = {}
for (const pair of pairs){
similars[pair[0]] = pair[1]
similars[pair[1]] = pair[0]
}
shuffleArray(arr)
let index = 0;
while (!isValid(arr)){
const item = arr[index]
if (getDistance(arr, item, similars[item]) < distance){
arr.push(item);
arr.splice(index, 1)
}
else {
index++
}
console.log(arr)
}
console.log(arr)
OK, this is a complicated one. The approach that I give here will top out somewhere between 20 and 30 pairs.
First, let's discuss conceptually what we want to do when choosing an element.
We'll have a freeValues list of values you can use at this point, a freePairs list of pairs we can use, and an upcoming that says when a particular value graduates from upcoming to available (namely after we've reached the right distance). So the idea is:
if decide to use free:
use a random freeValue
else:
pick a random pair
use one of its values, stick the other in upcoming
update upcoming
Using a random value is straightforward, the challenge is the decision.
But we can record the decisions in a simple string. To take your [1, 6, 4, 2, 3, 8, 7, 5] example above, the decisions were ppfpffff.
For the decisions we need to know how many ways there were of completing our permutation if we choose a free choice vs pair. Then we can make random decisions.
Count of how many ways to do a thing strongly suggests using dynamic programming. But actually we need not just counts, we also need some more information. Still we'll use that.
Dynamic programing comes in two flavors, top down and bottom up. Top down is always easier to write - you just write a recursive function and add caching logic to "memoize" it. So we'll do that.
Now what we will actually do is develop an analysis that looks like:
[count of options, further analysis, state]
Our count is just a count. Our further analysis is just a dictionary mapping choices (free/pair) to its own analysis (or undefined at the very end). Our state will be (countFree, countPairs, toFree) where the counts are just counts of freeValues and freePairs, and toFree is a bitpattern of when the other half of pairs come free. So, for example, if toFree was 5 that would represent a value coming free after this entry, none the entry after, and then another the entry after that.
An important note, the state is the state AFTER we make the choice that gets us there, And not before.
Now let's write code for that analysis.
function analyzePattern (values, pairs) {
let countPairs = pairs.length;
let countFree = values.length - 2 * countPairs;
return _analyzePattern(countFree, countPairs, countPairs, 0, {});
}
function _analyzePattern(countFree, countPairs, minDistance, toFree, cache) {
const key = `${countFree} ${countPairs}, ${toFree}`;
const state = [countFree, countPairs, toFree];
if (! cache[key]) {
if (0 == Math.max(countFree, countPairs)) {
if (toFree) {
cache[key] = [0, undefined, state];
}
else {
cache[key] = [1, undefined, state];
}
}
else {
const answer = {};
let total = 0;
if (toFree % 2) {
// We will be adding a free after this.
// Analyze using a free value here.
let result = _analyzePattern(countFree, countPairs, minDistance, toFree>>1, cache);
let countUseFree = result[0] * countFree;
total = countUseFree;
answer['free'] = [countUseFree, result[1], result[2]];
// Analyze using the first of a pair here.
if (countPairs) {
// Mark that there is an upcoming value to free.
toFree += 2**minDistance;
result = _analyzePattern(countFree+1, countPairs-1, minDistance, toFree>>1, cache);
let countUsePair = result[0] * 2 * countPairs;
total += countUsePair;
answer['pair'] = [countUsePair, result[1], result[2]];
}
}
else {
// We will not be adding a free after this.
if (countFree) {
let result = _analyzePattern(countFree-1, countPairs, minDistance, toFree>>1, cache);
let countUseFree = result[0] * countFree;
total = countUseFree;
answer['free'] = [countUseFree, result[1], result[2]];
}
// Analyze using the first of a pair here.
if (countPairs) {
// Mark that there is an upcoming value to free.
toFree += 2**minDistance;
let result = _analyzePattern(countFree, countPairs-1, minDistance, toFree>>1, cache);
let countUsePair = result[0] * 2 * countPairs;
total += countUsePair;
answer['pair'] = [countUsePair, result[1], result[2]];
}
}
cache[key] = [total, answer, state];
}
}
return cache[key];
}
That will produce our analysis. The next tricky bit is turning the analysis into a random pattern of f's and p's making each permutation equally likely. Note that we have to be careful to use the previous state to make the right random choices.
function randomPattern (analysis, state) {
if (!analysis) {
return '';
}
if (! state) {
state = analysis[2];
}
const total = analysis[0];
const remaining = analysis[1];
const nextState = analysis[2];
if (remaining) {
if (remaining['free']) {
const odds = remaining['free'][0] * state[0] / total;
if (Math.random() < odds) {
return 'f' + randomPattern(remaining['free'], state);
}
else {
return 'p' + randomPattern(remaining['pair'], state);
}
}
else {
return 'p' + randomPattern(remaining['pair'], state);
}
}
else {
return '';
}
}
And now we can finish.
function randomPermutation(values, pairs) {
let inPairs = {};
for (p of pairs) {
inPairs[p[0]] = 1;
inPairs[p[1]] = 1;
}
let freeValues = [];
for (v of values) {
if (! inPairs[v]) {
freeValues.push(v);
}
}
let freePairs = Array.from(pairs);
let distance = pairs.length;
let upcoming = {};
const pattern = randomPattern(analyzePattern(values, pairs));
let answer = []
for (c of pattern) {
if (c == 'f') {
let i = Math.floor(freeValues.length * Math.random());
let tmp = freeValues[i];
freeValues[i] = freeValues[freeValues.length-1];
freeValues[freeValues.length-1] = tmp;
answer.push(freeValues.pop())
}
else {
let i = Math.floor(freePairs.length * Math.random());
let tmp = freePairs[i];
freePairs[i] = freePairs[freePairs.length-1];
freePairs[freePairs.length-1] = tmp;
let pair = freePairs.pop();
if (0.5 < Math.random()) {
answer.push(pair[0]);
upcoming[pair[1]] = distance;
}
else {
answer.push(pair[1]);
upcoming[pair[0]] = distance;
}
}
// Now adjust upcoming.
nextUpcoming = {};
for (const [key, value] of Object.entries(upcoming)) {
if (value <= 1) {
freeValues.push(key-0);
}
else {
nextUpcoming[key] = value-1;
}
}
upcoming = nextUpcoming;
}
return answer;
}
And some test code to try it.
const values = [1, 2, 3, 4, 5, 6, 7, 8];
const pairs = [[1, 3], [6, 8], [2, 5]];
for (let i = 0; i < 10; i++) {
console.log(randomPermutation(values, pairs));
}

Merge overlaping objects with interval properties in array with 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));

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
}

Create array of all integers between two numbers, inclusive, in Javascript/jQuery

Say I have the following checkbox:
<input type="checkbox" value="1-25" />
To get the two numbers that define the boundaries of range I'm looking for, I use the following jQuery:
var value = $(this).val();
var lowEnd = Number(value.split('-')[0]);
var highEnd = Number(value.split('-')[1]);
How do I then create an array that contains all integers between lowEnd and highEnd, including lowEnd and highEnd themselves? For this specific example, obviously, the resulting array would be:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
var list = [];
for (var i = lowEnd; i <= highEnd; i++) {
list.push(i);
}
ES6 :
Use Array.from (docs here):
console.log(
Array.from({length:5},(v,k)=>k+1)
)
In JavaScript ES6:
function range(start, end) {
return Array(end - start + 1).fill().map((_, idx) => start + idx)
}
var result = range(9, 18); // [9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
console.log(result);
For completeness, here it is with an optional step parameter.
function range(start, end, step = 1) {
const len = Math.floor((end - start) / step) + 1
return Array(len).fill().map((_, idx) => start + (idx * step))
}
var result = range(9, 18, 0.83);
console.log(result);
I would use range-inclusive from npm in an actual project. It even supports backwards steps, so that's cool.
I highly recommend underscore or lo-dash libraries:
http://underscorejs.org/#range
(Almost completely compatible, apparently lodash runs quicker but underscore has better doco IMHO)
_.range([start], stop, [step])
Both libraries have bunch of very useful utilities.
My version of the loop ;)
var lowEnd = 1;
var highEnd = 25;
var arr = [];
while(lowEnd <= highEnd){
arr.push(lowEnd++);
}
fastest way
while-- is faster on most browsers
direct setting a variable is faster than push
function:
var x=function(a,b,c,d){d=[];c=b-a+1;while(c--){d[c]=b--}return d},
theArray=x(lowEnd,highEnd);
or
var arr=[],c=highEnd-lowEnd+1;
while(c--){arr[c]=highEnd--}
EDIT
readable version
var arr = [],
c = highEnd - lowEnd + 1;
while ( c-- ) {
arr[c] = highEnd--
}
Demo
http://jsfiddle.net/W3CUn/
FOR THE DOWNVOTERS
performance
http://jsperf.com/for-push-while-set/2
faster in ie and 3x faster in firefox
only on aipad air the for loop is a little faster.
tested on win8, osx10.8, ubuntu14.04, ipad, ipad air, ipod;
with chrome,ff,ie,safari,mobile safari.
i would like to see the performance on older ie browsers where the for loop isn't that optimized!
function range(j, k) {
return Array
.apply(null, Array((k - j) + 1))
.map(function(_, n){ return n + j; });
}
this is roughly equivalent to
function range(j, k) {
var targetLength = (k - j) + 1;
var a = Array(targetLength);
var b = Array.apply(null, a);
var c = b.map(function(_, n){ return n + j; });
return c;
}
breaking it down:
var targetLength = (k - j) + 1;
var a = Array(targetLength);
this creates a sparse matrix of the correct nominal length. Now the problem with a sparse matrix is that although it has the correct nominal length, it has no actual elements, so, for
j = 7, k = 13
console.log(a);
gives us
Array [ <7 empty slots> ]
Then
var b = Array.apply(null, a);
passes the sparse matrix as an argument list to the Array constructor, which produces a dense matrix of (actual) length targetLength, where all elements have undefined value. The first argument is the 'this' value for the the array constructor function execution context, and plays no role here, and so is null.
So now,
console.log(b);
yields
Array [ undefined, undefined, undefined, undefined, undefined, undefined, undefined ]
finally
var c = b.map(function(_, n){ return n + j; });
makes use of the fact that the Array.map function passes: 1. the value of the current element and 2. the index of the current element, to the map delegate/callback. The first argument is discarded, while the second can then be used to set the correct sequence value, after adjusting for the start offset.
So then
console.log(c);
yields
Array [ 7, 8, 9, 10, 11, 12, 13 ]
My five cents:
Both direction array of integers function.
When range(0, 5) become [0, 1, 2, 3, 4, 5].
And range(5, 0) become [5, 4, 3, 2, 1, 0].
Based on this answer.
function range(start, end) {
const isReverse = (start > end);
const targetLength = isReverse ? (start - end) + 1 : (end - start ) + 1;
const arr = new Array(targetLength);
const b = Array.apply(null, arr);
const result = b.map((discard, n) => {
return (isReverse) ? n + end : n + start;
});
return (isReverse) ? result.reverse() : result;
}
P.S. For use in real life you should also check args for isFinite() and isNaN().
function createNumberArray(lowEnd, highEnd) {
var start = lowEnd;
var array = [start];
while (start < highEnd) {
array.push(start);
start++;
}
}
If the start is always less than the end, we can do:
function range(start, end) {
var myArray = [];
for (var i = start; i <= end; i += 1) {
myArray.push(i);
}
return myArray;
};
console.log(range(4, 12)); // → [4, 5, 6, 7, 8, 9, 10, 11, 12]
If we want to be able to take a third argument to be able to modify the step used to build the array, and to make it work even though the start is greater than the end:
function otherRange(start, end, step) {
otherArray = [];
if (step == undefined) {
step = 1;
};
if (step > 0) {
for (var i = start; i <= end; i += step) {
otherArray.push(i);
}
} else {
for (var i = start; i >= end; i += step) {
otherArray.push(i);
}
};
return otherArray;
};
console.log(otherRange(10, 0, -2)); // → [10, 8, 6, 4, 2, 0]
console.log(otherRange(10, 15)); // → [10, 11, 12, 13, 14, 15]
console.log(otherRange(10, 20, 2)); // → [10, 12, 14, 16, 18, 20]
This way the function accepts positive and negative steps and if no step is given, it defaults to 1.
Solution with pure ES6
Inspired by m59's answer above, but without the dependency on fill:
const range = (start, stop) => Array.from({ length: stop - start + 1 }, (_, i) => start + i)
So you can use it like:
range(3,5)
=> [3, 4, 5]
var values = $(this).val().split('-'),
i = +values[0],
l = +values[1],
range = [];
while (i < l) {
range[range.length] = i;
i += 1;
}
range[range.length] = l;
There's probably a DRYer way to do the loop, but that's the basic idea.
You can design a range method that increments a 'from' number by a desired amount until it reaches a 'to' number.
This example will 'count' up or down, depending on whether from is larger or smaller than to.
Array.range= function(from, to, step){
if(typeof from== 'number'){
var A= [from];
step= typeof step== 'number'? Math.abs(step):1;
if(from> to){
while((from -= step)>= to) A.push(from);
}
else{
while((from += step)<= to) A.push(from);
}
return A;
}
}
If you ever want to step by a decimal amount : Array.range(0,1,.01)
you will need to truncate the values of any floating point imprecision.
Otherwise you will return numbers like
0.060000000000000005 instead of .06.
This adds a little overhead to the other version, but works correctly for integer or decimal steps.
Array.range= function(from, to, step, prec){
if(typeof from== 'number'){
var A= [from];
step= typeof step== 'number'? Math.abs(step):1;
if(!prec){
prec= (from+step)%1? String((from+step)%1).length+1:0;
}
if(from> to){
while(+(from -= step).toFixed(prec)>= to) A.push(+from.toFixed(prec));
}
else{
while(+(from += step).toFixed(prec)<= to) A.push(+from.toFixed(prec));
}
return A;
}
}
Adding http://minifiedjs.com/ to the list of answers :)
Code is similar to underscore and others:
var l123 = _.range(1, 4); // same as _(1, 2, 3)
var l0123 = _.range(3); // same as _(0, 1, 2)
var neg123 = _.range(-3, 0); // same as _(-3, -2, -1)
var empty = _.range(2,1); // same as _()
Docs here:
http://minifiedjs.com/api/range.html
I use minified.js because it solves all my problems with low footprint and easy to understand syntax. For me, it is a replacement for jQuery, MustacheJS and Underscore/SugarJS in one framework.
Of course, it is not that popular as underscore. This might be a concern for some.
Minified was made available by Tim Jansen using the CC-0 (public domain) license.
const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
_Array = (length) => Object.keys(Array.from({length}))
//_Array = [0, 1, 2, 3, 4]
const range = (start: number, end: number) => {
for (var i = start, list = []; i <= end; list.push(i), i++);
return list;
};
Hope the below method will help someone.
Here count variable can be used to mention the array length.
const generateRandomArryOfNumbers = (min = 1, max = 100, count = 31) => {
return Array.from(new Array(count), () =>
Math.floor(Math.random() * (max - min + 1) + min)
);
};
Here's 3 functions that should cover everything I could think of (including fixes for problems in some other answers): rangeInt(), range(), and between(). Both ascending and descending orders are accounted for in all cases.
Examples
rangeInt()
Includes endpoints and only deals with integers
rangeInt(1, 4) // [1, 2, 3, 4] Ascending order
rangeInt(5, 2) // [5, 4, 3, 2] Descending order
rangeInt(4, 4) // [4] Singleton set (i.e. not [4, 4])
rangeInt(-1, 1) // [-1, 0, 1] Mixing positive and negative
range()
Same as rangeInt() except
Not limited to integers
Allows for a specified number of points in a third parameter
range(0, 10, 2) // [0, 3.333, 6.666, 10] Gets endpoints and 2 points between
range(0, 1.5, 1) // [0, 0.75, 1.5] Accepts fractions
between()
Same as range() except
Endpoints are excluded
There are no singleton sets (an empty array will be returned instead)
between(0, 10, 2) // [3.333, 6.666]
between(-1, -1.5) // [-1.25]
between(4, 4, 99) // []
Source
/**
* Gets a set of integers that are evenly distributed along a closed interval
* #param {int} begin - Beginning endpoint (inclusive)
* #param {int} end - Ending endpoint (inclusive)
* #return {Array} Range of integers
*/
function rangeInt( begin, end ) {
if ( !Number.isInteger(begin) || !Number.isInteger(end) ) {
throw new Error('All arguments must be integers')
}
return range(begin, end, Math.abs(end - begin) - 1)
}
/**
* Gets a set of numbers that are evenly distributed along a closed interval
* #param {Number} begin - Beginning endpoint (inclusive)
* #param {Number} end - Ending endpoint (inclusive)
* #param {int} points - How many numbers to retrieve from the open interval
* #return {Array} Range of numbers
*/
function range( begin, end, points ) {
if ( begin !== end ) {
return [ begin, ...between(begin, end, points), end ]
}
else if ( Number.isFinite(begin) ) {
return [ begin ] // singleton set
}
else throw new Error('Endpoints must be finite')
}
/**
* Gets a subset of numbers that are evenly distributed along an open interval
* #param {Number} begin - Beginning endpoint (exclusive)
* #param {Number} end - Ending endpoint (exclusive)
* #param {int} points - How many numbers to retrieve from the interval
* #return {Array} Retrieved numbers
*/
function between( begin, end, points = 1 ) {
if ( !Number.isFinite(begin) || !Number.isFinite(end) || !Number.isFinite(points) ) {
throw new Error('All arguments must be finite')
}
const set = []
// Skip if an open interval does not exist
if ( begin !== end ) {
const step = (end - begin) / (points + 1)
for ( let i = 0; i < points; i++ ) {
set[i] = begin + (i + 1) * step
}
}
return set
}
Solving in underscore
data = [];
_.times( highEnd, function( n ){ data.push( lowEnd ++ ) } );
function getRange(a,b)
{
ar = new Array();
var y = a - b > 0 ? a - b : b - a;
for (i=1;i<y;i++)
{
ar.push(i+b);
}
return ar;
}

Categories