How to reverse the averaging of multiple vectors in JavaScript? - javascript

In the code below I mix various flavors of icecream together (chocolate, strawberry, vanilla, & neapolitan) in order to produce a new, never-before-seen flavor of icecream.*
A flavor is represented by an array where the first element is simply a string, the name of the flavor.
The second element is a number from 0 to 100 representing the vanilla component, the third the chocolate component, and the fourth the strawberry component.
Mixing is performed by averaging all the input flavors (arrays) together.
After mixing, I attempt to determine which flavor the new mixture is most similar to. This is done by taking the sum of the absolute difference of the mystery icecream and known flavors. The smaller the sum, the smaller the difference and greater the similarity.
In this specific example, the mixture is 6 parts strawberry icream and 1 part of each of the other flavors. Predictably, the strawberry is calculated to be the most similar, followed by neapolitan, because it is itself a mixture.
This is a good ways towards reverse-engineering the mixture, but I want to go further. I want to determine the precise proportions of each flavor that went into the mixture.
In this example it would be as stated above: 6 strawberry, 1 vanilla, 1 chocolate, 1 neapolitan.
Of course, there may be many (infinite?) ways to come up with a given mixture. But I am looking for the most parsimonious possibility.
For example, 1 part neopolitan plus 1 part strawberry is identical to 4 parts strawberry plus 3 parts of every other flavor. But the former is more parsimonious.
How would I go about predicting how a mixture was created?
I don't know what the technical term for this is.
const mixture = mixIcecreams([
['vanilla', 100, 0, 0],
['chocolate', 0, 100, 0],
['neapolitan', 33, 33, 33],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
]);
console.log(mixture);
const distances = calculateDistances(mixture, [
['vanilla', 100, 0, 0],
['chocolate', 0, 100, 0],
['strawberry', 0, 0, 100],
['neapolitan', 33, 33, 33],
]);
console.log('Distances:');
console.log(distances);
console.log(
`The icecream named "${mixture[0]}" is most similar to "${distances[0][0]}" icecream.`
);
// Calculate the "distance" between a "target" vector and "sources" vectors.
// Smaller distance means more similarity.
function calculateDistances(target, sources) {
return (
sources
.map((source) => [
// First element is the label.
source[0],
target.reduce(
(distance, value, i) =>
// Avoid doing math with the first element (the label).
i === 0 ? distance : distance + Math.abs(source[i] - value),
0
),
])
// Sort by shortest distance (most similar).
.sort((a, b) => a[1] - b[1])
);
}
function mixIcecreams(icecreams) {
return icecreams.reduce(
(mixture, icecream, i) => {
icecream.forEach((value, j) => j !== 0 && (mixture[j] += value));
if (i === icecreams.length - 1) {
return mixture.map((value, j) =>
// Ignore the first element, it's just a label.
j === 0 ? value : value / icecreams.length
);
}
return mixture;
},
Array.from({ length: icecreams[0].length }, (_, i) =>
i === 0 ? 'mixture' : 0
)
);
}
*Patent pending.

If I understand your problem correctly, in mathematical terms you seem to need the solution of an underdetermined system of equations, in the least squares sense.
I put up a quick solution that can be improved upon.
I can further explain, if interesting.
Edit: I added a simple integer approximation, to find an integer solution that best approximates the percentual one.
const mixture = mixIcecreams([
['vanilla', 100, 0, 0],
['chocolate', 0, 100, 0],
['neapolitan', 33, 33, 33],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
['strawberry', 0, 0, 100],
]);
console.log(mixture);
const distances = calculateDistances(mixture, [
['vanilla', 100, 0, 0],
['chocolate', 0, 100, 0],
['strawberry', 0, 0, 100],
['neapolitan', 33, 33, 33],
]);
console.log('Distances:');
console.log(distances);
console.log(
`The icecream named "${mixture[0]}" is most similar to "${distances[0][0]}" icecream.`
);
const probableMixture = mostProbableMixture(mixture, [
['vanilla', 100, 0, 0],
['chocolate', 0, 100, 0],
['strawberry', 0, 0, 100],
['neapolitan', 33, 33, 33],
]
);
console.log('most likely mixture, percent', probableMixture)
const im = integerMix(probableMixture, 100);
// the second argument, nMax, is the maximum number allowed from one basic flavor
// the larger nMax, the closer you may get to the exact mixture
console.log('integer mixture', im)
const rm = repeatIntegerMix(im, [
['vanilla', 100, 0, 0],
['chocolate', 0, 100, 0],
['strawberry', 0, 0, 100],
['neapolitan', 33, 33, 33],
]);
console.log('verify mixture', mixIcecreams(rm))
// Calculate the "distance" between a "target" vector and "sources" vectors.
// Smaller distance means more similarity.
function calculateDistances(target, sources) {
return (
sources
.map((source) => [
// First element is the label.
source[0],
target.reduce(
(distance, value, i) =>
// Avoid doing math with the first element (the label).
i === 0 ? distance : distance + Math.abs(source[i] - value),
0
),
])
// Sort by shortest distance (most similar).
.sort((a, b) => a[1] - b[1])
);
}
function mixIcecreams(icecreams) {
return icecreams.reduce(
(mixture, icecream, i) => {
icecream.forEach((value, j) => j !== 0 && (mixture[j] += value));
if (i === icecreams.length - 1) {
return mixture.map((value, j) =>
// Ignore the first element, it's just a label.
j === 0 ? value : value / icecreams.length
);
}
return mixture;
},
Array.from({ length: icecreams[0].length }, (_, i) =>
i === 0 ? 'mixture' : 0
)
);
}
function mostProbableMixture(mixture, baseFlavors){
const nVars = baseFlavors.length,
nEq = mixture.length - 1,
At = baseFlavors.map(flavor=>flavor.slice(1)),
b = mixture.slice(1),
AAt = Array(nEq).fill(0).map(z=>Array(nEq));
//compute A*At
for(let i = 0; i < nEq; i++){
for(let j = 0; j < nEq; j++){
AAt[i][j] = 0;
for(let k = 0; k < nVars; k++){
AAt[i][j] += At[k][i]*At[k][j];
}
}
}
// normalize rows
for(let i = 0; i < nEq; i++){
let maxRow = Math.abs(b[i]);
for(let j = 0; j < nEq; j++){
maxRow = Math.max(maxRow, Math.abs(AAt[i][j]));
}
for(let j = 0; j < nEq; j++){
AAt[i][j] = AAt[i][j] /maxRow;
}
b[i] = b[i]/maxRow;
}
// Solve (for t) A * At * t = b
// Gaussian elimination; diagonal dominance, no pivoting
for(let j = 0; j < nEq-1; j++){
for(let i = j+1; i < nEq; i++){
let f = AAt[i][j] / AAt[j][j];
for(let k = j+1; k < nEq; k++){
AAt[i][k] -= f * AAt[j][k];
}
b[i] -= f*b[j];
}
}
const t = Array(nEq).fill(0);
t[nEq-1] = b[nEq-1]/AAt[nEq-1][nEq-1];
for(let i = nEq-2; i >= 0; i--){
let s = b[i];
for(let j = i + 1; j<nEq; j++){
s -= AAt[i][j]*t[j];
}
t[i] = s/AAt[i][i];
}
// the solution is y = At * t
const y = Array(nVars).fill(0);
let sy = 0;
for(let i = 0; i < nVars; i++){
for(let j = 0; j < nEq; j++){
y[i] += At[i][j] * t[j];
}
sy += y[i];
}
for(let i = 0; i < nVars; i++){
y[i] = y[i]/sy*100;
}
return y.map((yi, i)=>[baseFlavors[i][0], yi]);
}
function integerMix(floatMix, nMax){
const y = floatMix.map(a=>a[1]),
maxY = y.reduce((m,v)=>Math.max(m,v));
let minAlpha = 0, minD = 1e6;
for(let alpha = 1/maxY; alpha <= nMax/maxY; alpha+=(nMax-1)/10000/maxY){
//needs refining!!
const sdif = y.map(v=>Math.pow(Math.round(v*alpha)-v*alpha, 2));
const d2 = sdif.reduce((s,x)=>s+x);
if(d2 < minD){
minD = d2;
minAlpha = alpha;
}
}
return floatMix.map(([name, f]) => [name, Math.round(minAlpha*f)]);
}
function repeatIntegerMix(integerMix, baseFlavors){
return integerMix.flatMap(([name, n], i)=> Array.from({length: n}, e=>baseFlavors[i].slice(0)));
}

Related

BFS that returns shortest path on a 2nd array /unwighted graph

I know this has been asked before but I can't seem to figure out the solution from the examples and translate them to javascript. not even when following :
https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
I have an unweighted graph or 2d array for example that looks like this:
const testMatrix = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0]
];
I then Traverse that using BFS: (i currently hardCoded the item to find as element 2,2 in the array).
And return the seen list item list. but I can't figure out how the seen list supposes to show the way to the shortest path.
const traversalBFS = function(matrix) {
counter = 0;
const seen =
new Array(matrix.length).fill(0).map(() => new Array(matrix[0].length).fill(false));
const values = [];
const queue = [[0, 0]];
while(queue.length) {
const currentPos = queue.shift();
const row = currentPos[0];
const col = currentPos[1];
if(row < 0 || row >= matrix.length || col < 0 || col >= matrix[0].length || seen[row][col] || matrix[row][col] === 1) {
continue;
}
counter++;
seen[row][col] = true;
values.push(matrix[row][col]);
if(row === 2 && col === 2) {
return seen;
}
for(let i = 0; i < directions.length; i++) {
const currentDir = directions[i];
queue.push([row + currentDir[0], col + currentDir[1]]);
}
}
return false;
}
even if I run this code
temp = traversalBFS(testMatrix);
let path = [];
for(i = 0; i <= 2; i++) {
for(j = 0; j <= 2; j++) {
if(temp[i][j]) {
path.push([i, j]);
}
}
}
it will return:
0: (2) [0, 0]
1: (2) [0, 1]
2: (2) [0, 2]
3: (2) [1, 0]
4: (2) [1, 1]
5: (2) [1, 2]
6: (2) [2, 0]
7: (2) [2, 2]
which is not a correct path in any way, and also not the shortest path.
expected result example:
hmmm lets say end point will be 1,1 and start will be 0, 0
the expected result is an array with the shortest path:
[[0,0], [0, 1], [1,1]]
if the start point is 0, 0 and the end point is 2,2:
I think the result should be:
[[0,0], [0, 1], [1,1],[1,2],[2,2];
using the test matrix I wrote as an example. as there are no 1 aka walls in the way.
For getting an ide how Dijkstra's algorithm work, you could take the approach of the given link and take a large value for a start and take smaller values as soon as the neighbours have them.
This example uses (step) values instead of indices, but it could be replaced by the coordinates of the path to the target.
This approach uses obstacle avoidance and a short cicuit if the target is found.
const
travel = (array, from, to) => {
const
go = (i, j, smallest) => {
if (array[i]?.[j] === 1) return;
if (i === to[0] && j === to[1]) return;
if (unvisited[i]?.[j] > smallest) {
unvisited[i][j] = smallest;
go(i + 1, j, smallest + 1);
go(i - 1, j, smallest + 1);
go(i, j + 1, smallest + 1);
go(i, j - 1, smallest + 1);
}
},
unvisited = testMatrix.map(a => a.map(_ => Number.MAX_VALUE));
go(from[0], from[1], 0);
return unvisited;
},
testMatrix = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]],
result = travel(testMatrix, [0, 0], [3, 2]);
result.forEach(a => console.log(a.map(v => v === Number.MAX_VALUE ?'*': v).join(' ')));

Simplify array from many values to fewer

I have an array with many numbers (165) and want to 'simplify' the array to fewer numbers (50). The reduced array should still be representative for the 165 original numbers.
For example:
If I had this array with 8 values [2, 4, 3, 8, 1, 4, 9, 3] and would ask to reduce it to 4 values, I want to receive this array [3, 5.5, 2.5, 6]
Currently I have a function that works when the reduced number and the original number can be divided (as with 8 and 4) but when I try using it with let's say 165 values that should be simplified to 50 values it returns me 54 values:
const array = [28, 71, 64, 116, 128, 8, 78, 172, 142, 96, 12 ... 165 values]
const valuesToSum = Math.floor(array.length / 50); // 50 is the wanted array length
for (let i = 0; i < array.length; i++) {
if (i % valuesToSum !== 0 || i === 0) {
sum += array[i];
} else {
returnArray.push(sum / valuesToSum);
sum = 0;
}
}
return returnArray;
In the end this should be a JavaScript function but if someone could explain me this on a mathematical level it would help me a lot.
In order to get the exact number of groups you want, you can't round off the number of elements to group. For instance if you want to reduce from 165 to 50, some of the groups will have 3 elements, some will have 4.
To do this, use nested loops. The outer loop increments by the size of the group, while the inner loop increments by 1 within the group. The rounding happens when you convert this inner index to the array index.
const array = [28, 71, 64, 116, 128, 8, 78, 172, 142, 96, 12]
function reduceArray(array, newSize) {
const returnArray = [];
const valuesToSum = array.length / newSize;
for (let i = 0; i < array.length; i += valuesToSum) {
let sum = 0;
let j;
let start_i = Math.floor(i);
for (j = start_i; j < Math.min(start_i + valuesToSum, array.length); j++) {
sum += array[j];
}
returnArray.push(sum/(j - start_i));
}
return returnArray;
}
console.log(reduceArray(array, 4));
const bigArray = [];
for (let i = 0; i < 165; i++) {
bigArray.push(Math.floor(Math.random() * 200));
}
let result = reduceArray(bigArray, 50);
console.log(result.length);
console.log(result);

To find minimum elements in array which has sum equals to given value

I am trying to find out the minimum elements in array whose sum equals
the given input.I tried for few input sum but was able to find only a
pair in first case while I need to implement for more than just a pair.
var arr = [10, 0, -1, 20, 25, 30];
var sum = 45;
var newArr = [];
console.log('before sorting = ' + arr);
arr.sort(function(a, b) {
return a - b;
});
console.log('after sorting = ' + arr);
var l = 0;
var arrSize = arr.length - 1;
while (l < arrSize) {
if (arr[l] + arr[arrSize] === sum) {
var result = newArr.concat(arr[l], arr[arrSize]);
console.log(result);
break;
} else if (arr[l] + arr[arrSize] > sum) {
arrSize--;
} else {
l++;
}
}
Input Array : [10, 0, -1, 20, 25, 30]
Required Sum: 45
Output: [20, 25]
I am trying for
Required Sum : 59
Output: [10, -1, 20, 30]
This can be viewed as an optimization problem which lends itself well to dynamic programming.
This means you would break it up into a recursion that tries to find the minimum length of increasingly smaller arrays with the sum adjusted for what's been removed. If your array is [10, 0, -1, 20, 25, 30] with a sum of 59 you can think of shortest as the min of:
[10, ... shortest([ 0, -1, 20, 25, 30], 49)
[0, ... shortest([10, 20, 25, 30], 49), 59)
[-1, ... shortest([10, 0, 20, 25, 30], 60)
... continue recursively
with each recursion, the array gets shorter until you are left with one element. Then the question is whether that element equals the number left over after all the subtractions.
It's easier to show in code:
function findMinSum(arr, n){
if(!arr) return
let min
for (let i=0; i<arr.length; i++) {
/* if a number equals the sum, it's obviously
* the shortest set, just return it
*/
if (arr[i] == n) return [arr[i]]
/* recursively call on subset with
* sum adjusted for removed element
*/
let next = findMinSum(arr.slice(i+1), n-arr[i])
/* we only care about next if it's shorter then
* the shortest thing we've seen so far
*/
if (next){
if(min === undefined || next.length < min.length){
min = [arr[i], ...next]
}
}
}
return min && min /* if we found a match return it, otherwise return undefined */
}
console.log(findMinSum([10, 0, -1, 20, 25, 30], 59).join(', '))
console.log(findMinSum([10, 0, -1, 20, 25, 30], 29).join(', '))
console.log(findMinSum([10, 0, -1, 20, 25, 30], -5)) // undefined when no sum
This is still pretty computationally expensive but it should be much faster than finding all the subsets and sums.
One option is to find all possible subsets of the array, and then filter them by those which sum to the required value, and then identify the one(s) with the lowest length:
const getMinElementsWhichSum = (arr, target) => {
const subsets = getAllSubsetsOfArr(arr);
const subsetsWhichSumToTarget = subsets.filter(subset => subset.reduce((a, b) => a + b, 0) === target);
return subsetsWhichSumToTarget.reduce((a, b) => a.length < b.length ? a : b, { length: Infinity });
};
console.log(getMinElementsWhichSum([10, 0, -1, 20, 25, 30], 45));
console.log(getMinElementsWhichSum([10, 0, -1, 20, 25, 30], 59));
// https://stackoverflow.com/questions/5752002/find-all-possible-subset-combos-in-an-array
function getAllSubsetsOfArr(array) {
return new Array(1 << array.length).fill().map(
(e1,i) => array.filter((e2, j) => i & 1 << j));
}
Try this,
var arr = [10, 0, -1, 20, 25, 30];
var sum = 29;
var newArr = [];
var sum_expected = 0;
var y = 0;
while (y < arr.length) {
for (let i = 0; i < arr.length; i++) {
var subArr = [];
sum_expected = arr[i];
if (arr[i] != 0) subArr.push(arr[i]);
for (let j = 0; j < arr.length; j++) {
if (i == j)
continue;
sum_expected += arr[j];
if (arr[j] != 0) subArr.push(arr[j]);
if (sum_expected == sum) {
var result = arr.filter((el)=>(subArr.indexOf(el) > -1));
!newArr.length ? newArr = result : result.length < newArr.length ? newArr = result : 1;
break;
}
}
}
let x = arr.shift();
arr.push(x);
y++;
}
if (newArr.length) {
console.log(newArr);
} else {
console.log('Not found');
}

Generate collision objects from multidimensional array for HTML5 tile map

I am creating an HTML5 platform game using objects for collision detection and using a 2d tile map to render the level. That is all working.
I want to use the same 2d array to build the object array dynamically to allow the player to build maps as required and also for ease of creating the maps in the first place. When hardcoding the object array, everything works so I know that the collision detect and game engine work.
While I can create objects for each individual array element, I am looking to build objects that have width based on the number of matching elements in the array, (each element is 25x25) i.e. if 3 array elements are 1,1,1 then the object will have a width of 75. Maybe some code will help explain:
The following tile array
var arr1 = [
[0,0,0,1,1,1,1,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,2,2,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[3,3,3,0,0,0,0,0,0,0]
];
should produce the following object array:
[
{x: 75, y: 0, width: 100, height: 25, value: 1},
{x: 75, y: 50, width: 50, height: 25, value: 2},
{x: 0, y: 100, width: 75, height: 25, value: 3}
]
but it instead it is producing the following:
[
{x: 75, y: 0, width: 25, height: 25, value: 1},
{x: 100, y: 0, width: 25, height: 25, value: 1},
{x: 125, y: 0, width: 25, height: 25, value: 1}
]
My logic is obviously wrong but I can't for the life of me get it.
Example code is below:
Any help really appreciated:
var tmpObj = {};
var objArr = [];
var arr1 = [
[0,0,0,1,1,1,1,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,2,2,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[3,3,3,0,0,0,0,0,0,0]
];
for (let i=0; i<arr1.length; i++) {
for (let j=0; j<arr1[i].length; j++) {
if (arr1[i][j] > 0 && arr1[i][j] < 6) { // platform blocks only 1 - 5
if (tmpObj.x === undefined) {
tmpObj = {
x: j * 25,
y: i * 25,
width: 25,
height: 25,
value: arr1[i][j]
}
} else if (arr1[i][j] == arr1[i][j-1] && arr1[i][j] == tmpObj.v) {
tmpObj.w += 25;
} else if (arr1[i][j] !== tmpObj.v) { // new tile type
objArr.push(tmpObj);
tmpObj = {
x: j * 25,
y: i * 25,
width: 25,
height: 25,
value: arr1[i][j]
}
} else {
objArr.push(tmpObj);
tmpObj = {};
}
}
}
}
console.log(objArr);
Looking at what you are trying to do your implementation is way too complicated. Rather than hunt down the bug (for which I would have used devTools and stepped through the code line by line to find where the problem was.) I rewrote the function using a while loop to find the width of joined tiles.
I took liberty with the object property names but I am sure you can change it to your needs.
const objArr = [];
const arr1 = [
[0,0,0,1,1,1,1,0,1,0],
[2,0,0,0,0,0,0,0,0,3],
[0,0,0,4,4,0,4,4,4,4],
[0,0,0,0,0,0,0,0,0,0],
[3,3,3,5,5,4,0,0,0,0]
];
const tileSize = 25;
for (let i = 0; i < arr1.length; i++) {
const row = arr1[i]
for (let j = 0; j < row.length; j++) {
if (row[j] > 0 && row[j] < 6) {
let count = j + 1;
while (count < row.length && row[count] === row[j]) { count += 1 }
objArr.push({
x: j * tileSize,
y: i * tileSize,
w: tileSize * (count - j),
h: tileSize,
tile: row[j]
});
j = count - 1;
}
}
}
// show results
objArr.forEach(log);
// unrelated to answer. (I hate the console SO has for snippets.)
function log(data){
show.appendChild(
Object.assign(
document.createElement("div"), {
textContent :
Object.keys(data).reduce((str, key) => {
return str + (" " + key+ ": " + data[key]).padEnd(8,".");
}, ""
)
}
)
);
}
<code id="show"></code>

Get similiar/closest numbers from the array

I'm trying to find the efficient way of finding the similar numbers (where the difference between the numbers is "almost constant" - close to some constant).
For example having array like:
const arr = [23, 130, 142, 151, 163, 200];
would return a new array:
const similarNumbers = [130, 142, 151, 163];
Currently the way I try to solve it is to map through the first array and try to find smallest difference between the numbers and then map again and check if the absolute value of currentValue - previousValue - smallestGap < SOME_THRESHOLD, but this is not ideal and only works in some of the particular cases.
Ideally what I'd like to achieve is for the function to return a few arrays with the grouped similar numbers:
const someArr = [23, 130, 142, 151, 163, 200, 232, 261];
would return two arrays:
group1 = [130, 142, 151, 163];
and:
group2 = [200, 232, 261];
This is a simple (and not perfect) function I just wrote. It will group numbers into groups based on a threshold value which defines the maximum derivation from the groups average.
const someArr = [23, 130, 142, 151, 163, 200, 232, 261];
var threshold = 50;
var relations = {};
var groups = {};
i = 0;
for (var j = 0; j < someArr.length; j++) {
var number = someArr[j];
if (i == 0) {
relations[number] = i;
groups[i] = [];
groups[i].push(number);
}
else {
var added = false;
var n_groups = 0;
for (var g in groups) {
var sum = 0;
var group = groups[g];
if (group.length == 1 && Math.abs(number - group[0]) <= 2 * threshold) {
relations[number] = parseInt(g, 10);
groups[g].push(number);
added = true;
}
else {
for( var n = 0; n < group.length; n++ ){
sum += group[n];
}
var avg = sum/group.length;
if (Math.abs(number - avg) <= threshold) {
relations[number] = parseInt(g, 10);
groups[g].push(number);
added = true;
}
}
n_groups++;
}
if (!added) {
var h = n_groups;
relations[number] = parseInt(h, 10);
groups[h] = [];
groups[h].push(number);
}
}
i++;
}
//console.log(relations, groups);
document.getElementById('grouped').innerHTML = JSON.stringify(groups);
<h3>Input</h3><div id="original">[23, 130, 142, 151, 163, 200, 232, 261]</div>
<h3>Output</h3><div id="grouped"></div>
I'm not sure it's the result you want to obtain.
function(entries) {
let groups = {};
let finalGroups = {};
for (var i = 0; i < entries.length; i++) {
let b = entries[i];
let power = 0;
while(b > 10) {
power++;
b = b / 10
}
groups[power] = groups[power] || []
groups[power].push(a[i]);
}
for (let i in groups) {
for (let j = 0; j < groups[i].length; j++) {
const c = groups[i][j]
const max = Math.floor(c / Math.pow(10, i));
finalGroups[i] = finalGroups[i] || {};
finalGroups[i][max] = finalGroups[i][max] || [];
finalGroups[i][max].push(c);
}
}
return finalGroups;
}
Here is the result. You will need to extract arrays but that's not the difficult part
{
"1":
{
"2":[23]
},
"2":
{
"1":[130,142,151,163],
"2":[200,232,261]
}
}
The function will firstly sort entries by their powers of 10. Then it will sort them by the number the most on the left.
So if you have values like 1030 and 1203 they will be considered as close values...
EDIT: Made a mistake. Fixed it
If you're wanting to find two groups of numbers that are more alike with each other, for the simplest sets you can split them in half, and find the difference between the first/last items, and the difference between the item before/after that item, if its closer to the item in the other group, else leave them as they are
const someArr = [23, 130, 142, 151, 163, 200, 232, 261];
someArr.sort((a, b) => a < b ? -1 : 1);
let groupA = someArr.slice(0, Math.ceil(someArr.length / 2))
let groupB = someArr.slice(Math.ceil(someArr.length / 2));
const differenceInA = Math.abs(groupA[groupA.length - 2] - groupA[groupA.length - 1]);
const differenceInB = Math.abs(groupB[0] - groupB[1]);
const differenceInBoth = Math.abs(groupA[groupA.length - 1] - groupB[0])
if (differenceInBoth < differenceInB) {
groupA.push(groupB[0]);
groupB = groupB.slice(1);
} else if(differenceInBoth > differenceInA) {
groupB.unshift( groupA[groupA.length - 1]);
groupA = groupA.slice(0, groupA.length - 1);
}
console.log('Group A:', groupA);
console.log('Group B:', groupB);
The above logs
Group A: [
23,
130,
142,
151,
163
]
Group B: [
200,
232,
261
]

Categories

Resources