algorithm of counting Overlaps of ranges in javascript - javascript

input integer is always positive
input: [[1, 4], [3, 7], [6, 8], [10,15]]
output: [[1, 8], [10,15]]
input: [[3, 4], [1,3], [5, 9], [5, 12]]
output: [[1, 4], [5, 12]]
I read this stackoverflow, but wandering if there is a better way.

You could sort the data ascending and then check the predecessor if it fits into the last range. If not append the actual array to the result set.
function groupRanges(array) {
return array
.sort(function (a, b) { return a[0]- b[0] || a[1]- b[1]; })
.reduce(function (r, a) {
var last = r[r.length - 1] || [];
if (a[0] <= last[1]) {
if (last[1] < a[1]) {
last[1] = a[1];
}
return r;
}
return r.concat([a]);
}, []);
}
console.log(groupRanges([[1, 4], [3, 7], [6, 8], [10,15]]));
console.log(groupRanges([[3, 4], [1,3], [5, 9], [5, 12]]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

For every start and end of range make a pair containing value and +1/-1 for start and end
Sort these pairs by value, using +-1 as secondary key in compare function: (x,+1) before (the same x,-1)
Make ActiveRanges=0, walk through pair list/array, adding +-1 to ActiveRanges.
When ActiveRanges becomes nonzero, big range begins.
When ActiveRanges becomes zero, big range finishes.

Here's what I came up with, usint the MathJS library to generate ranges :
let input = [[1, 4], [3, 7], [6, 8], [10,15]]
let workArray = input.map( arr => math.range(arr.join(":"), true )._data )
workArray = [].concat.apply([], workArray) // Concatenating all values
workArray = [...new Set(workArray)] // Deduplicating, sorting
let output = [],
min = workArray[0],
max = min
for( let i=0, l=workArray.length; i<l ; i++){
if(max+1 != workArray[i+1]){
output.push([min,max])
min = workArray[i+1]
max=min
} else {
max++
}
}
console.log(output) // [ [1,8] , [10,15] ]
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.13.1/math.min.js"></script>

If the array is of length 4 (as per your example) and everything is constant as you described it, here is a simple function you can use:
function reGroup(arr) {
const x = [arr[0][0], arr[2][1]];
return [x, arr[3]];
}

Related

Line combination algorithm

array = [[1, 2], [13, 14], [4, 5], [80, 30], [12, 14], [10, 90], [3, 2], [6, 9], [1, 5], [4, 5], [5, 9], [4, 3], [13, 12]]
//expected
output = [[1, 2], [13, 14], [4, 5], [80, 30], [10, 90], [3, 2], [6, 9], [4, 5], [5, 9], [4, 3], [13, 12]]
You can consider the subarrays as lines, for example, [1,2] would be a line connected from point 1 to point 2. Therefore, [1,2],[3,2],[4,3],[4,5],[4,3] would correlate with several short lines that connect point 1 to point 5, because there is a line connected from point 1 to 2, 2 to 3, 3 to 4, and 4 to 5.
If the array contains a larger single line that is point 1 to point 5, it should be filtered out. This is to remove all longer lines that already have their points defined in more shorter lines. What algorithm could be used to solve this?
I have tried the code below at https://codepen.io/Maximusssssu/pen/jOYXrNd?editors=0012
The first part outputs all subarrays in ascending order for readability, whereas for the second part, I have tried the include() method to check whether a node is present in the subarray, and get its position.
array = [[1, 2], [13, 14], [4, 5], [80, 30], [12, 14], [10, 90], [3, 2], [6, 9], [1, 5], [4, 5], [5, 9], [4, 3], [13, 12]]
array_ascending_arr = [];
for (let i = 0; i < array.length; i++) {
let subarrayy = array[i].sort(function(a, b) {
return a - b
});
array_ascending_arr.push(subarrayy);
}
console.log(array_ascending_arr) // output all subarrays in ascending order back into array
for (let k = 1; k < 5; k++) {
for (let m = 0; m < array.length; m++) {
for (let i = 1; i < 2; i++) {
if (array_ascending_arr[m].includes(k) == true) {
console.log(m)
}
}
}
console.log(".......")
}
the main idea is to calculate the difference in number of intermediate elements and not in value
if we have [[1,2],[14,10],[4,6],[9,2] that makes a sequence = [1,2,4,6,9,10,14] (sorted)
return delta values:
[1,2] -> d:1
[9,2] -> d:3 // the 9 is three positions away from the 2
the principle is therefore to process starting from the least distant values towards those most distant
(the other sorting criteria are secondary and are mainly useful for debugging)
note: duplicate pairs are also eliminated
const
test1 = [[1,2],[13,14],[4,5],[80,30],[12,14],[10,90],[3,2],[6,9],[1,5],[4,5],[5,9],[4,3],[13,12]]
, test2 = [[1,2],[4,5],[30,80],[12,18],[10,90],[2,3],[6,9],[1,5],[4,6],[5,9],[3,4],[12,13],[12,14],[14,15],[15,18]]
;
console.log('test1:\n', JSON.stringify( combination( test1 )))
console.log('test2:\n', JSON.stringify( combination( test2 )))
function combination(arr)
{
let
nodes = arr.flat().sort((a,b)=>a-b).filter((c,i,{[i-1]:p})=>(c!==p))
, sets = nodes.map(g=>[g])
, bads = []
;
arr // i:index, s:start(min), e:end(max), d: delta
.map(([v0,v1],i) => ({i,s:Math.min(v0,v1),e:Math.max(v0,v1), d:Math.abs(nodes.indexOf(v0) - nodes.indexOf(v1))}))
.sort((a,b) => (a.d-b.d) || (a.s-b.s) || (a.e-b.e) || (a.i-b.i) )
.forEach(({i,s,e,d}) =>
{
let
gS = sets.find(n=>n.includes(s))
, gE = sets.find(n=>n.includes(e))
;
if (gS === gE) { bads.push(i) }
else { gS.push(...gE); gE.length=0 }
})
//console.log( sets.filter(a=>a.length).map(a=>JSON.stringify(a)).join(' - ') )
return arr.filter((x,i)=>!bads.includes(i))
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
.as-console-row::after { display: none !important; }
You could try for an adjacency list. My understanding of the algorithm is if two pairs share an edge they will combine to form a new edge until all sets are unique.
So from what I understand, you're trying to remove any set of numbers that encompasses a range greater than any that are smaller. The following code should do this:
array.filter(pair => {
// Get the smaller and larger numbers from the pair
const [small, big] = pair.sort((a,b) => a-b)
// Check if this pair is larger than any others
return !array.some(test => {
const [testSmall, testBig] = test.sort((a,b) => a-b)
return big > testBig && small < testSmall
})
})
Note that this won't remove duplicates.
If you don't mind your subarrays being reordered in the final array, you can simplify it a bit by sorting them all at the beginning:
array
.map(pair => pair.sort((a,b) => a-b))
.filter(([s, b], _, arr) => !arr.some(([ts, tb]) => b>tb && s<ts))

JS - How to remove items from Array that are present on other Array?

I have two array with the following structure:
Array A: [[1, 10], [2, 4], [5,22], [3, 10]]
Array B: [2, 5]
The result array should be:
Array C: [[1, 10], [3, 10]]
I'm trying to use includes() but it's very slow when I have big arrays. What is best solution to remove items on this case? Here is what I'm trying:
let activeCSs = css.filter(cs => !vacant_css.includes(cs[0]));
Assuming, you want only to check the first element of the inner arrays, you could take a Set for the check.
var arrayA = [[1, 10], [2, 4], [5, 22], [3, 10]],
arrayB = [2, 5],
setB = new Set(arrayB)
result = arrayA.filter(([v]) => !setB.has(v));
console.log(result);
Another way is using reduce and filter
var A = [
[1, 10],
[2, 4],
[5, 22],
[3, 10]
];
var B = [2, 5];
var result = B.reduce((carry, current) => carry.filter(list => !list.includes(current)), A);
console.log(result)
Assumption: base on the !vacant_css.includes(cs[0])) code in the question, the comparison is for each integer in ArrayB to the first integer in each array contained in arrayA. arrayA[x][y] === arrayB[z]. Coded to solve that problem.
Poster mentions in comments this is from an exercise, so pretty good bet they were looking for time complexity sensitive solution...
I am not a big O expert (yeah, I hear it), but I think my answer ends up something like: O(n log n) + O(n log n) + O(n). It is not immediately clear to me what the Set based answer would result in big O. O(n) * O(n) + whatever the Set(arrayB) costs I think?
let arrayA = [[1, 10], [2, 4], [5, 22], [3, 10]]
let arrayB = [2, 5]
arrayA.sort((a, b) => a[0] - b[0])
arrayB.sort((a, b) => a - b)
let i = 0
let j = 0
let arrayAnswer = []
while (i<arrayA.length&&j<arrayB.length) {
let d = arrayA[i][0]-arrayB[j];
console.log(`i ${i} j ${j} d ${d}`)
if (d === 0) {
i++
} else if (d<0) {
arrayAnswer.push(arrayA[i])
i++
} else {
j++
}
}
console.log(arrayAnswer)
Warning: This code is not tested for edge cases and is janky and old school.

Javascript: Group array into ascending-order

I have an array like this:
var my_array = [[6, 2], [7, 3], [9, 4], [9, 6], [3, 7]]
... and I'd like to sort the array in different groups like below:
var result = [ [6, 2], [7, 3], [9, 4] ],
[ [9, 6], [3, 7] ]
So as you can see the sort method should group all arrays their array[1] values are matching together (like an ascending row)
SO the values in the example above result[0][1] --> 2, result[1][1] --> 3, result[0][3] --> 4 are matching together.
Also the group result[1][0] --> 6 and result[1][1] --> 7 are matching together.
BTW: The my_array - array is already sorted, so that my_array[x][1] <= my_array[x+1][1].
I have no clue how to code this, so this is all what I got till now:
var my_array = [[6, 2], [7, 3], [9, 4], [9, 6], [3, 7]]
function sort_array(array) {
var group=[];
for (var i=array[0][1]; i<3; i++) {
if (array[i+1][1] == i) {
group.push(array[i+1])
}
else {
}
}
return group;
}
console.log(sort_array(my_array));
My understanding of your request;
Take a given array of two item arrays (already sorted by item 2)
group the two item arrays by the sequential item 2's (split them when you encounter a missing int).
return the result.
var my_array = [[6, 2], [7, 3], [9, 4], [9, 6], [3, 7]]
function sort_array(array) {
var results = [];
var tmp = [];
for(var i = 0; i < array.length; i++){
tmp.push(array[i]);
if(i== array.length -1 || array[i][1] != (array[i+1][1]-1)){
results.push(tmp);
tmp = [];
}
}
return results;
}
console.log(sort_array(my_array));
You could do the grouping using recursion, it basically compares the current and the next number, if the next number is consecutive it will continue to call itself, if it's not consecutive it will add a new group then call itself until there is no next value.
"use strict" // this will allow tail call recursion in modern browsers
const my_array = [[5, 0], [6, 2], [7, 3], [9, 4], [9, 6], [3, 7], [4, 10]]
function groupConsecutiveYs([curr, ...rest], acc = [[]]) {
const next = rest[0]
// add to current group
acc[acc.length - 1].push(curr)
if (typeof next === 'undefined') // we are done return the accumulator
return acc
// if next is not consecutive add another grouping
if (curr[1] !== next[1] - 1)
acc.push([])
// call recursive function again
return groupConsecutiveYs(rest, acc)
}
console.log(
groupConsecutiveYs(my_array)
)
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js?concise=true"></script>

How to get the last array that includes a certain element?

I have an array of arrays and I want to check if there is a tie between the second elements and then return the first element of the last array that makes a tie.
for example this should return 4. (the first element in the last array that has a second element that makes a tie)
var optionsArray = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]];
It is quite simple, you need to iterate over your source array, check if the given item matches the criteria, and save it to result if it does. Now if any other item does match the criteria, result's value will be overwritten with the new matching item.
var optionsArray = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]];
var result;
optionsArray.forEach(function(item) {
if(item[1] == 10) {
result = item;
}
});
console.log(result);
You can create a simple find function that iterates the array backwards, and returns as soon as a condition callback returns true.
var optionsArray = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]];
function find10(s) {
return s[1] === 10;
}
function findFromTheEnd(arr, cb) {
var l = arr.length;
while(l--) { // iterate backwards
if(cb(arr[l])){ // if the callback returns true
return arr[l]; // return the item
}
}
return null; // return null if none found
}
var result = findFromTheEnd(optionsArray, find10);
console.log(result);
You can use reduceRight() and return array.
var arr = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]];
var result = arr.reduceRight(function(r, e) {
if(e[1] == 10 && !r) r = e;
return r;
}, 0)
console.log(result)
You can also use for loop that starts from end and break on first match.
var arr = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]];
var result;
for (var i = arr.length - 1; i >= 0; i--) {
if (arr[i][1] == 10) {
result = arr[i]
break;
}
}
console.log(result)
A classic for in the reserve order with a break seems enough :
var optionsArray = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]];
var elementFound;
for (var i = optionsArray.length-1; i >=0; i--) {
if(optionsArray[i].item[1] == 10) {
elementFound = optionsArray[i].item[1];
break;
}
}
If elementFound is not undefined, it refers to the found array.
Rather than considering this as a multidimensional array problem, think of it as an array includes problem nested in an array search problem;
const aarr = [1, 2, 3, 4];
aarr.includes(3); // true
aarr.includes(10); // false
// and
const barr = ['hello', 'world'];
barr.find(item => item[0] === 'h'); // "hello"
barr.find(item => item[3] === 'l'); // "hello"
barr.find(item => item[1] === 'z'); // undefined
So to nest these,
const carr = [[1, 2, 3, 4], [4, 5, 6, 7]];
carr.find(arr => arr.includes(4)); // [1, 2, 3, 4]
carr.find(arr => arr.includes(6)); // [4, 5, 6, 7]
Next, we've reduced the whole problem down to "how to do this in reverse?"
You've a few options depending on how you want to implement it, but a simple way to do it is a shallow clone arr.slice() followed by a reverse arr.reverse() (we use the clone so there are no side-effects of reverse on the original array)
carr.slice().reverse().find(arr => arr.includes(4)); // [4, 5, 6, 7]
If you're working with an index, remember that you'll need to transform those too; -1 is fixed, otherwise transformed_index = arr.length - original_index - 1
Here is how you might implement the reverse of some of the Array methods
const optionsArray = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]];
// index 0 1 2 3 4
function arrLast(arr, comparator, method = 'find', transform = x => x) {
return transform(arr.slice().reverse()[method](comparator), arr);
}
const findLast = (arr, comparator) => arrLast(arr, comparator);
const findLastIndex = (arr, comparator) => arrLast(arr, comparator, 'findIndex', (i, arr) => i === -1 ? -1 : arr.length - i - 1);
arrLast(optionsArray, arr => arr.includes(10)); // [4, 10]
findLastIndex(optionsArray, arr => arr.includes(10)); // 3
If you have to make comparisons among array items and you need to cut short once you are satisfied a while loop is ideal. Accordingly you may do as follows;
var arr = [[1, 10], [2, 10], [3, 10], [4, 10], [6, 14]],
i = 0,
result;
while (arr[i][1] === arr[++i][1]);
result = arr[i-1][0]
console.log(result);

How to sum elements at the same index in array of arrays into a single array?

Let's say that I have an array of arrays, like so:
[
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
]
How do I generate a new array that sums all of the values at each position of the inner arrays in javascript? In this case, the result would be: [17, 10, 19]. I need to be able to have a solution that works regardless of the length of the inner arrays. I think that this is possible using some combination of map and for-of, or possibly reduce, but I can't quite wrap my head around it. I've searched but can't find any examples that quite match this one.
You can use Array.prototype.reduce() in combination with Array.prototype.forEach().
var array = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
],
result = array.reduce(function (r, a) {
a.forEach(function (b, i) {
r[i] = (r[i] || 0) + b;
});
return r;
}, []);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Update, a shorter approach by taking a map for reducing the array.
var array = [[0, 1, 3], [2, 4, 6], [5, 5, 7], [10, 0, 3]],
result = array.reduce((r, a) => a.map((b, i) => (r[i] || 0) + b), []);
console.log(result);
Using Lodash 4:
function sum_columns(data) {
return _.map(_.unzip(data), _.sum);
}
var result = sum_columns([
[1, 2],
[4, 8, 16],
[32]
]);
console.log(JSON.stringify(result));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
For older Lodash versions and some remarks
Lodash 4 has changed the way _.unzipWith works, now the iteratee gets all the values passed as spread arguments at once, so we cant use the reducer style _.add anymore. With Lodash 3 the following example works just fine:
function sum_columns(data) {
return _.unzipWith(data, _.add);
}
var result = sum_columns([
[1, 2],
[4, 8, 16],
[32],
]);
console.log(JSON.stringify(result));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
_.unzipWith will insert undefineds where the row is shorter than the others, and _.sum treats undefined values as 0. (as of Lodash 3)
If your input data can contain undefined and null items, and you want to treat those as 0, you can use this:
function sum_columns_safe(data) {
return _.map(_.unzip(data), _.sum);
}
function sum_columns(data) {
return _.unzipWith(data, _.add);
}
console.log(sum_columns_safe([[undefined]])); // [0]
console.log(sum_columns([[undefined]])); // [undefined]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
This snipet works with Lodash 3, unfortunately I didn't find a nice way of treating undefined as 0 in Lodash 4, as now sum is changed so _.sum([undefined]) === undefined
One-liner in ES6, with map and reduce
var a = [ [0, 1, 3], [2, 4, 6], [5, 5, 7], [10, 0, 3] ];
var sum = a[0].map((_, i) => a.reduce((p, _, j) => p + a[j][i], 0));
document.write(sum);
Assuming that the nested arrays will always have the same lengths, concat and reduce can be used.
function totalIt (arr) {
var lng = arr[0].length;
return [].concat.apply([],arr) //flatten the array
.reduce( function(arr, val, ind){ //loop over and create a new array
var i = ind%lng; //get the column
arr[i] = (arr[i] || 0) + val; //update total for column
return arr; //return the updated array
}, []); //the new array used by reduce
}
var arr = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
];
console.log(totalIt(arr)); //[17, 10, 19]
Assuming array is static as op showned.
a = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
]
b = []
for(i = 0; i < a[0].length; i++){
count = 0
for(j = 0; j < a.length; j++){
count += a[j][i]
}
b.push(count)
}
console.log(b)
So far, no answer using the for ... of mentioned in the question.
I've used a conditional statement for different lengths of inner arrays.
var a = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
];
i = 0;
r = []
for (const inner of a) {
j = 0;
for (const num of inner) {
if (j == r.length) r.push(num)
else r[j] += num
j++;
}
i++;
}
console.log(r);
True, in this case, the classic for cycle fits better than for ... of.
The following snippet uses a conditional (ternary) operator.
var a = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
];
r = [];
for (i = 0; i < a.length; i++) {
for (j = 0; j < a[i].length; j++) {
j==r.length ? r.push(a[i][j]) : r[j]+=a[i][j]
}
}
console.log(r);
A solution using maps and reductions, adding elements from different lengths of arrays.
var array = [
[0],
[2, 4],
[5, 5, 7, 10, 20, 30],
[10, 0]
];
b = Array(array.reduce((a, b) => Math.max(a, b.length), 0)).fill(0);
result = array.reduce((r, a) => b.map((_, i) => (a[i] || 0) + (r[i] || 0)), []);
console.log(result);
const ar = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
]
ar.map( item => item.reduce( (memo, value)=> memo+= value, 0 ) )
//result-> [4, 12, 17, 13]

Categories

Resources