Line combination algorithm - javascript

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

Related

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

Filtering non-"linear", progressive values from array

I'm not exactly sure how to explain this, but bear with me...
I have written a program that creates random arrays of lengths that range from 0-4 like so: [] or [n] or [n,n] or [n,n,n] or [n,n,n,n].
The input will be a random digit between 1-6, whereby the output, n, is always a multiple of the input. For example, the input might be 2 and the output might be [2] or [4,8] or [2,4,6].
The output will always be sorted from lowest to highest value.
The input will always be the lowest multiple, whereas the highest multiple will always be the value of input * 4. For instance, if the input were 3, the range of output would be 3-12 or 3, 6, 9, and 12.
Hypothetically, let's say the input is 4, whereas the output, if any, is of course multiples of 4.
The only possible outputs are...
[]
[4]
[8]
[12]
[16]
[4,8]
[4,12]
[4,16]
[8,12]
[8,16]
[12,16]
[4,8,12]
[4,8,16]
[4,12,16]
[8,12,16]
[4,8,12,16]
What I wanted to do was filter the output so that only the bold arrays above were valid. In addition to that, I wanted to preserve any valid array elements while removing all non-"linear", progressive elements like so:
[4,12] would become [4]
[4,8,16] would become [4,8]
[8,16] would become []
[8,12,16] would become []
[12,16] would become []
I ended up finally figuring out how I could achieve this, but for whatever reason it took me a while to wrap my head around it; so now I'm just super curious. Is there another way to achieve this output which is definitively more efficient than the following code? Is it possible to do the same thing with some sort of loop? Are there any known algorithms out there that deal with this sort of thing?
if (a[0] != 4) {
a = [];
} else if (a.length === 2 && a[1] != 8) {
a.pop();
} else if (a.length === 3 && a[1] != 8) {
a.pop();
a.pop();
} else if (a.length === 3 && a[2] != 12){
a.pop();
}
Since the callback in Array.prototype.filter can handle an index argument, it can be used directly to return only the values at the 'proper' index:
function filterOnFactor(arr, factor){
return arr.filter((val,i) => ++i * factor == val);
}
//test
for(let arr of [[], [4], [8], [12], [16], [4, 8], [4, 12], [4, 16], [8, 12], [8, 16], [12, 16], [4, 8, 12], [4, 8, 16], [4, 12, 16], [8, 12, 16], [4, 8, 12, 16]]){
console.log(JSON.stringify(arr), '->', JSON.stringify(filterOnFactor(arr,4)));
}
NB, this only works if the source is always a ascending multitude of the factor, so [6,8,12] would still return [8,12], but according to the info, the source is always generated as an ascending multitude.
Just for fun, also a solution using a generator function (which is safe for all input)
function* filterOnFactor(arr, factor){
for(let i=0; i< arr.length; i++){
if(arr[i] !== ++i * factor)break;
yield arr[--i];
}
}
//test
for(let arr of [[], [4], [8], [12], [16], [4, 8], [4, 12], [4, 16], [8, 12], [8, 16], [12, 16], [4, 8, 12], [4, 8, 16], [4, 12, 16], [8, 12, 16], [4, 8, 12, 16], [6,8,12]]){
console.log(JSON.stringify(arr), '->', JSON.stringify([...filterOnFactor(arr,4)]));
}
Because your arrays have fixed length, being efficient or not is not really a concern.
That said, it is possible to write your manual case distinction in a more generic way. This might save a couple of comparisons with the array length, but the greatest advantage is that it does not hardcode values or assumes a maximum array length.
Idea. Start at the beginning of the array and then, for every element, check if it is the expected multiple. If not, cut off the rest of the array from that point onwards.
function filterMultiples(arr, firstMultiple) {
let badIndex = arr.findIndex((value, index) =>
(value !== (index + 1) * firstMultiple)
);
if (badIndex < 0) {
return arr;
}
return arr.slice(0, badIndex);
}
function test(arr, firstMultiple = 4) {
console.log(
JSON.stringify(arr) + ' is filtered to ' +
JSON.stringify(filterMultiples(arr, firstMultiple)) +
` (multiples of ${firstMultiple})`
);
}
test([4, 8, 16]); // [4, 8]
test([2]); // []
test([4, 6, 12]); // [4]
test([2, 4, 6, 8, 9], 2); // [2, 4, 6, 8]
// tests from question
test([4,12]); // [4]
test([4,8,16]); // [4,8]
test([8,16]); // []
test([8,12,16]); // []
test([12,16]); // []
.as-console-wrapper {
max-height: 100% !important;
}
The CSS in the above snippet is only for display purposes, the import part is filterMultiples.
Explanation. We find the index of the first element that is not the expected value. If no such element is found, all values are as expected and we return the original array. Otherwise, we return only the slice of the array up to but not including the first unexpected value.
Alternative. If you want to modify the original array, you can write the function as follows.
function filterMultiples(arr, firstMultiple) {
let badIndex = arr.findIndex((value, index) =>
(value !== (index + 1) * firstMultiple)
);
if (badIndex >= 0) {
arr.splice(badIndex);
}
}
This approach uses Array#splice.
You could filter the values and then filter the arrays.
function sequence() {
var i = 1;
return function (b) {
return b === factor * i++;
};
}
function validate(a) {
return a.filter(sequence());
}
function notEmpty(a) {
return a.length;
}
var data = [[], [4], [8], [12], [16], [4, 8], [4, 12], [4, 16], [8, 12], [8, 16], [12, 16], [4, 8, 12], [4, 8, 16], [4, 12, 16], [8, 12, 16], [4, 8, 12, 16]],
factor = 4,
result = data
.map(validate)
.filter(notEmpty);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

algorithm of counting Overlaps of ranges in 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]];
}

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