Split array in the best possible way with multiple functions - javascript

I am looking for an algorithm name, preferably a library that can take an array and split it between functions in the best way possible.
I don't care about the complexity (it's for a very low data set).
Recursive check would suffice.
A nice example would be array:
const list = [{gender: "female"}, {gender: "female"}, {gender: "female"}, {gender: "male"}]
So if we run it with the special function
specialFunc(list, [(val) => val === 'female', (val) => val === 'male']);
We would be getting this
[
[{gender: "female"}, {gender: "female"}, {gender: "female"}],
[{gender: "male"}]
]
Because this is the best possible split we can get.
However, if we run it by this function:
specialFunc(list, [(val) => !!val, (val) => val === 'male']);
I would be getting this:
[
[{gender: "female"}, {gender: "female"}, {gender: "female"}],
[{gender: "male"}]
]
"the best way possible" means that the number distance (of array length) between each array should be the lowest, and the number of records in each array should be the maximum possible.
I have searched npmjs and github a lot but couldn't find anything.
Thank you very very much!

I think I understand these requirements. You have a number of predicate function which you want to use to group your items. Multiple predicates might return true for the same item, so there are various groupings available. You want to find a grouping that minimizes the variation in sizes of the results.
I don't find your examples very compelling. I will try my own. If your items are 8, 6, 7, 5, 3, 0, 9] and you have three predicates: (n) => n < 7, (n) => n > 3, and (n) => n % 2 == 1, then the 8 can only go in the second group (it's greater than 3, but not less than 7 and not odd.) The 6 can go in either of the first two groups, the 5 could be in any of them, and so on, like this:
8 6 7 5 3 0 9
[[1], [0, 1], [1, 2], [0, 1, 2], [0, 2], [0], [1, 2]]
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | | | | | | |
| +--|----|--|----+--|--|----+--|----+----|--|------> Group 0 (n => n < 7)
| | | | | | | | |
+-------+----+--|-------+--|-------|---------+--|------> Group 1 (n => n > 3)
| | | |
+----------+-------+------------+------> Group 2 (n => n % 2 == 1)
Since there is one choice for the first, two for the second, two for the third, and so on, the number of possible partitions is 1 * 2 * 2 * 3 * 2 * 1 * 2, or 48. They might look like this:
[// < 7 > 3 odd
[ [6, 5, 3, 0], [8, 7, 9], [] ],
[ [6, 5, 3, 0], [8, 7], [9] ],
[ [6, 5, 0], [8, 7, 9], [3] ],
[ [6, 5, 0], [8, 7], [3, 9] ],
// ... (42 rows elided)
[ [0], [8, 6, 9], [7, 5, 3] ],
[ [0], [8, 6], [7, 5, 3, 9] ]
]
Then, from these, we need to pick the ones with the smallest variation in partition size. We can use statistical variance for this1, the sum of the squares of the distances of the values from their mean, so [[6, 5, 3, 0], [8, 7, 9], []], with lengths 4, 3, and 0; this has a variance of 8.667. The second one has lengths 4, 2, and 1 with a variance of 4.667. Our best possibility is 3, 2 and 2, with variance of 0.667. So an answer like [[6, 5, 0], [8, 7], [3, 9]] would be reasonable. There are probably quite a few with similar behavior; the implementation below simply chooses the first one.
If this correctly describes the problem, then here is some code that I think will handle it:
const range = (lo, hi) => Array .from ({length: hi - lo}, (_, i) => i + lo)
const sum = (ns) => ns .reduce ((a, b) => a + b, 0)
const filterMap = (f, m) => (xs) =>
xs .flatMap ((x, i, a) => f (x, i, a) ? [m (x, i, a)] : [])
const cartesian = ([xs, ...xss]) =>
xs == undefined
? [[]]
: xs .flatMap (x => cartesian (xss) .map (ys => [x, ...ys]))
const variance = (ns, avg = sum (ns) / (ns .length || 1)) =>
sum (ns .map (n => (n - avg) * (n - avg)))
const groupIndices = (count) => (xs) =>
Object .values (xs .reduce (
(a, x, i) => ((a [x] .push (i)), a),
Object .fromEntries (range (0, count) .map (n => [n, []]))
))
const specialFunc = (xs, preds) =>
cartesian (xs .map ((x) => filterMap ((pred, i) => pred (x), (_, i) => i) (preds)))
.map (groupIndices (preds .length))
.reduce (
({min, v}, xs, _, __, v2 = variance (xs .map (x => x .length))) =>
v2 < v ? {min: xs, v: v2} : {min, v},
{min: [], v: Infinity}
) .min .map (ys => ys .map (i => xs [i]))
console .log (specialFunc (
[8, 6, 7, 5, 3, 0, 9],
[n => n < 7, n => n > 3, n => n % 2 == 1]
)) //=> [[6, 5, 0], [8, 7], [3, 9]]
.as-console-wrapper {max-height: 100% !important; top: 0}
We start with some fairly standard utility functions. range calculates an integer range inclusive at the bottom, exclusive at the top, so that, for instance, range (3, 12) returns [3, 4, 5, 6, 7, 8, 9 ,10, 11]. sum simply totals an array of numbers, filterMap combines filtering with mapping, first testing whether an input matches a filter, and if so, transforming the result before putting it in the result. This implementation is unusual, in that the filter and mapping functions take more than just the value, but also the index and array properties found in things like map and filter. We need that as we'll use it to collect indices that match. (There are plenty of other ways to do that bit, but filterMap is a useful, reusable function.) cartesian returns the cartesian product of an array of arrays. For instance, cartesian ([1, 2, 3], [true], ['a', 'b']]) will return [[1, true, 'a'], [1, true, 'b'], [2, true, 'a'], [2, true, 'b'], [3, true, 'a'], [3, true, 'b']]. And finally variance calculate the statistical variance of a list of numbers.
Then we have a helper function, groupIndices. This might be easiest to show with an example. One of the 48 results from our cartesian product will be [1, 0, 1, 0, 2, 0, 1], which means that our original numbers (8, 6, 7, 5, 3, 0, 9], recall) are in groups 1, 0, 1, 0, 2, 0, and 1, respectively. groupIndices takes the number of groups and then takes that cartesian combination, and transforms it into [[1, 3, 5], [0, 2, 6], [4]], giving the indices of the values that are mapped to each group. (If I wasn't out of time, I'm sure we could skip this working with the indices and go directly against the values, but this works.)
Now we hit the main function, that I haven't tried to find a good name for, so is still called specialFunc. That uses filterMap to turn our list into [[1], [0, 1], [1, 2], [0, 1, 2], [0, 2], [0], [1, 2]], calls cartesian on the result, maps groupIndices over these values, then uses reduce to find (the first) one that is minimal in its variance. Finally it maps the resulting indices back to the actual values.
Again, we can probably clean this up and work with values not indices, but I would first want to know if this is the sort of behavior you're looking for.
1The standard deviation has a clearer meaning, but as it's just the square root of the variance, it will be ordered the same way as the variance, and won't involve calculating square roots.

function splitGroups<T>(items: T[], filters: ((x: T) => boolean)[]) {
let options = filters.map(f => items.filter(f));
let groups = filters.map((_, index) => ({ data: [] as T[], index }));
let res: T[][] = [];
while (options.reduce((partial_sum, a) => partial_sum + a.length, 0) > 0) {
groups.sort((a, b) => a.data.length - b.data.length);
let smallGroup = groups[0];
const smallGroups = groups.filter(g => g.data.length === smallGroup.data.length);
if (smallGroups.length > 1) {
smallGroup = smallGroups[Math.floor(Math.random() * (smallGroups.length - 1))];
}
if (options[smallGroup.index].length === 0) {
res.push(smallGroup.data);
groups = groups.filter(x => x !== smallGroup);
continue;
}
const item = options[smallGroup.index][0];
options = options.map(x => x.filter(y => y !== item));
smallGroup.data.push(item);
}
res = [...res, ...groups.map(x => x.data)];
return res;
}
function accurateSplitGroups<T>(items: T[], filters: ((x: T) => boolean)[], times: number) {
const options: { data: T[][]; diff: number }[] = [];
for (let i = 0; i < times; i++) {
const res = splitGroups(items, filters);
let diffBetweenGroups = 0;
const groupsLens = res.map(x => x.length);
for (let i = 0; i < groupsLens.length; i++) {
for (let j = 0; j < groupsLens.length; j++) {
diffBetweenGroups += Math.abs(groupsLens[i] - groupsLens[j]);
}
}
options.push({ data: res, diff: diffBetweenGroups });
}
return options.sort((a, b) => a.diff - b.diff)[0].data;
}
const items = [{ gender: 'female' }, { gender: 'female' }, { gender: 'female' }, { gender: 'male' }];
const filters = [(x: any) => !!x.gender, (x: any) => !!x.gender];
const res = accurateSplitGroups(items, filters, 100);
const b = res;

Related

An unique arrays of numbers from an Array with JavaScript

Can anyone tell me how to solv this problem please:
I tried doing this with array.map, array.filter, array.reduce but i did not got result:
Write a function putNum(arrayOfNum: number[], num: number),
which would find all possible combinations of numbers from arrayOfNum,
whose sum is equal to number. Wherein:
arrayOfNum contains only unique positive numbers (>0)
there should not be repetitions of numbers in the combination
all combinations must be unique
#param arrayOfNum: number[]
#param num: number[]
#return Array<Array<number>>
function putNum(arrayOfNum, num) {
***// write code only inside this function***
return [[1, 2], [3]];
}
// console.log(putNum([8, 2, 3, 4, 6, 7, 1], 99)); => []
// console.log(putNum([8, 2, 3, 4, 6, 7, 1], 5)); => [[2, 3], [4, 1]]
// console.log(putNum([1, 2, 3, 4, 5, 6, 7, 8], 8)); => [[1, 3, 4], [1, 2, 5], [3, 5], [2, 6], [1, 7], [8]]
let resultnum = result.filter(e => typeof e === 'number' && e > 0); // to make a new array with nums > 0
The best approach to solve this problem in optimized way is to use hash map
let twoSum = (array, sum) => {
let hashMap = {},
results = []
for (let i = 0; i < array.length; i++){
if (hashMap[array[i]]){
results.push([hashMap[array[i]], array[i]])
}else{
hashMap[sum - array[i]] = array[i];
}
}
return results;
}
console.log(twoSum([10,20,40,50,60,70,30],50));
Output:
[ [ 10, 40 ], [ 20, 30 ] ]

interleave mutliple arrays in javascript

We have an Array of arrays, which we want to interleave into a single array:
i.e:
masterArray = [[1, 2, 3], ['c', 'd', 'e']] => [1, 'c', 2, 'd', 3, 'e'],
if arrays are not of equal length, pad it to the longest innerArray's length.
i.e
[1, 2, 3], [4, 5]) => [1, 4, 2, 5, 3, null]
I've satisfied this condition with the case of 2 arrays, however if the case is more than that. I struggle to form a strategy on dealing with more than 2.
[1, 2, 3], [4, 5, 6], [7, 8, 9] => [1, 4, 7, 2, 5, 8, 3, 6, 9]
function interleave(...masterArray) {
let rtnArray = [];
let longestArrayPosition = getLongestArray(masterArray);
let longestInnerArrayLength = masterArray[longestArrayPosition].length;
padAllArraysToSameLength(masterArray, longestInnerArrayLength); //pad uneven length arrays
masterArray[0].forEach((firstArrayNum, index) => {
const secondArrayNum = masterArray[1][index];
rtnArray.push(firstArrayNum);
rtnArray.push(secondArrayNum);
});
return rtnArray;
}
function getLongestArray(masterArray) {
return masterArray
.map(a=>a.length)
.indexOf(Math.max(...masterArray.map(a=>a.length)));
}
function padAllArraysToSameLength(masterArray, maxLength) {
return masterArray.forEach(arr => {
if (arr != maxLength) {
while(arr.length != maxLength) {
arr.push(null);
}
}
})
}
Use Array.from() to transpose the array of arrays (rows => columns and vice versa), and fill in the missing places with null. Flatten the tramsposed arrays of arrays with Array.flat():
const fn = arr => Array.from({
length: Math.max(...arr.map(o => o.length)), // find the maximum length
},
(_, i) => arr.map(r => r[i] ?? null) // create a new row from all items in same column or substitute with null
).flat() // flatten the results
const arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
const result = fn(arr)
console.log(result)
You can do this for any number of arrays with two nested forEach statements:
let arr1 = [[1,2,3],[4,5]]
let arr2 = [[1,2,3], [4,5,6], [7,8,9]]
let arr3 = [[1,2,3,4], [4,5,6], [7,8,9], [10,11,12]]
function interLeaveArrays(mainArr){
let maxLen = Math.max(...mainArr.map(arr => arr.length))
mainArr.forEach(arr => {
let lenDiff = maxLen - arr.length
for(let i=lenDiff; i>0; i--){
arr.push(null)
}
})
let newArr = []
mainArr.forEach((arr, idx1) => {
arr.forEach((el, idx2) => {
newArr[idx2 * mainArr.length + idx1] = el
})
})
return newArr
}
console.log(interLeaveArrays(arr1))
console.log(interLeaveArrays(arr2))
console.log(interLeaveArrays(arr3))

Sort array of numbers by normal distribution (gaussian distribution)

Having an array of numbers setOfNumbers = [0, 3, 3, 2, 7, 1, -2, 9] I'd like to sort this set to have the smallest numbers on the end and beginning and the biggest in the center of the sorted set like this sortedSetNumbers = [0, 2, 3, 9, 7, 3, 1, -2].
const setOfNumbers = [0, 3, 3, 2, 7, 1, -2, 9];
const result = [0, 2, 3, 9, 7, 3, 1, -2];
function sortNormal(a, b) {
return true; // Please, change this line
}
const sortedSetNumbers = setOfNumbers.sort((a, b) => sortNormal(a, b));
if (sortedSetNumbers === result) {
console.info('Succeeded Normal Distributed');
} else {
console.warn('Failed Normal Distribution');
}
console.log(sortedSetNumbers);
I am sure it is possible to sort these numbers with the method Array.prototype.sort(), but how should this sorting function look like?
EDIT: The solution does not have to be solved with .sort(). That was only an idea.
This might be the most naive way to do it, but isn't it simply left, right, left, right... after sorting?
const input = [0, 3, 3, 2, 7, 1, -2, 9];
const expected = [0, 2, 3, 9, 7, 3, 1, -2];
const sorted = input.slice().sort();
const output = [];
let side = true;
while (sorted.length) {
output[side ? 'unshift' : 'push'](sorted.pop());
side = !side;
}
console.log(expected.join());
console.log(output.join());
Or simply:
const input = [0, 3, 3, 2, 7, 1, -2, 9];
const output = input.slice().sort().reduceRight((acc, val, i) => {
return i % 2 === 0 ? [...acc, val] : [val, ...acc];
}, []);
console.log(output.join());
A slightly different approach is to sort the array ascending.
Get another array of the indices and sort the odds into the first half asending and the even values to the end descending with a inverted butterfly shuffle.
Then map the sorted array by taking the value of the sorted indices.
[-2, 0, 1, 2, 3, 3, 7, 9] // sorted array
[ 1, 3, 5, 7, 6, 4, 2, 0] // sorted indices
[ 0, 2, 3, 9, 7, 3, 1, -2] // rebuild sorted array
var array = [0, 3, 3, 2, 7, 1, -2, 9].sort((a, b) => a - b);
array = Array
.from(array, (_, i) => i)
.sort((a, b) => b % 2 - a % 2 || (a % 2 ? a - b : b - a))
.map(i => array[i]);
console.log(array);
This solution is not really elegant, but it does it's job.
const setOfNumbers = [0, 3, 3, 2, 7, 1, -2, 9];
const alternation = alternate();
const sortedSetNumbers = sortNormal(setOfNumbers);
function sortNormal(start) {
const result = [];
const interim = start.sort((a, b) => {
return b - a;
});
interim.map(n => {
if (alternation.next().value) {
result.splice(0, 0, n);
} else {
result.splice(result.length, 0, n);
}
});
return result;
}
function* alternate() {
let i = true;
while (true) {
yield i;
i = !i;
}
}
console.log(sortedSetNumbers);

Averaging out corresponding index value of arrays considering their lengths

I have a multidimensional array which contains arrays of different lengths.
I want to average the corresponding index values of all the arrays.
For arrays that don't have the index won't be considered when averaging the values.
var multiArray = [
[4, 1, 3],
[6, 4, 2, 3, 4],
[8, 6, 1, 2],
[2, 3]
];
var avgIdxArray = [];
// logic helper
// (4 + 6 + 8 + 2) / 4 = 5
// (1 + 4 + 6 + 3) / 4 = 3.5
// (3+ 2 +1) / 3 = 2
// (3 + 2) / 5 = 2.5
// 4 / 1 = 4;
// (sum of index values) / number of arrays that have those index
// desired output
console.log(avgIdxArray);
// [5, 3.5 ,2 ,2.5 ,4]
Can it be achieved using the .map(), .filter() and .reduce() method? Also what could be the most efficient way of handling this problem?
One solution is this:
1- Convert multiArray array to its vertical type (New array with their indexes as you said in question)
2- Calculate sum and then avg of each array.
var multiArray = [
[4, 1, 3],
[6, 4, 2, 3, 4],
[8, 6, 1, 2],
[2, 3]
],
target = [];
multiArray.map((itm) => {
let x = Object.keys(itm);
x.forEach((ii) => {
if (target.length <= ii) {
target.push([]);
}
target[ii].push(itm[ii])
});
});
target.forEach((arr)=> {
let sum = arr.reduce(function(a, b) { return a + b; });
let avg = sum / arr.length;
console.log(avg)
})
Iterate the array with Array.reduce(). Iterate the sub array with Array.forEach(), and collect the sum, and the amount of items in the index. Use Array.map() to convert each sum/count object to average:
const multiArray = [
[4, 1, 3],
[6, 4, 2, 3, 4],
[8, 6, 1, 2],
[2, 3]
];
const result = multiArray
.reduce((r, a) => {
a.forEach((n, i) => {
const { sum = 0, count = 0 } = r[i] || {};
r[i] = { sum: sum + n, count: count + 1 };
});
return r;
}, [])
.map(({ sum, count }) => sum / count);
console.log(result);
Pure mapreduce.
Object.keys(multiArray.sort( (x,y) => y.length - x.length)[0]).
map( x => Object.keys(multiArray).
map(y => x < multiArray[y].length?multiArray[y][x]:undefined)).
map( a => ({sum: (a.reduce((l,r) => (l?l:0) + (r?r:0))), length: (a.filter(x => x).length)}) ).
map( pair => pair.sum / pair.length)
Output
[ 5, 3.5, 2, 2.5, 4 ]
A lot going there. Lets take it step by step
var multiArray = [
... [4, 1, 3],
... [6, 4, 2, 3, 4],
... [8, 6, 1, 2],
... [2, 3]
... ];
Order the arrays so that the array with the most element becomes first
multiArray.sort( (x,y) => y.length - x.length)
[ [ 6, 4, 2, 3, 4 ], [ 8, 6, 1, 2 ], [ 4, 1, 3 ], [ 2, 3 ] ]
Take the first element and loop over its keys. This is the largest element as we have sorted it before.
Object.keys(multiArray.sort( (x,y) => y.length - x.length)[0])
[ '0', '1', '2', '3', '4' ]
Now check if all the arrays have that key, else put an undefined over there
Object.keys(multiArray.sort( (x,y) => y.length - x.length)[0]).
map( x => Object.keys(multiArray).
map(y => x < multiArray[y].length?multiArray[y][x]:undefined)).
[ [ 6, 8, 4, 2 ],
[ 4, 6, 1, 3 ],
[ 2, 1, 3, undefined ],
[ 3, 2, undefined, undefined ],
[ 4, undefined, undefined, undefined ] ]
Create an object with sum and length. This part is optional, but I wanted this to be clear
Object.keys(multiArray.sort( (x,y) => y.length - x.length)[0]).
map( x => Object.keys(multiArray).
map(y => x < multiArray[y].length?multiArray[y][x]:undefined)).
map( a => ({sum: (a.reduce((l,r) => (l?l:0) + (r?r:0))), length: (a.filter(x => x).length)}) )
[ { sum: 20, length: 4 },
{ sum: 14, length: 4 },
{ sum: 6, length: 3 },
{ sum: 5, length: 2 },
{ sum: 4, length: 1 } ]
Finally get the avg
Object.keys(multiArray.sort( (x,y) => y.length - x.length)[0]).
map( x => Object.keys(multiArray).
map(y => x < multiArray[y].length?multiArray[y][x]:undefined)).
map( a => ({sum: (a.reduce((l,r) => (l?l:0) + (r?r:0))), length: (a.filter(x => x).length)}) ).
map( pair => pair.sum / pair.length)
[ 5, 3.5, 2, 2.5, 4 ]

Performant way to convert adjacency list to links for undirected graph

I have an adjacency list like below:
const list = [
[1, 6, 8],
[0, 4, 6, 9],
[4, 6],
[4, 5, 8],
// ...
];
I need to create a set of links for an undirected graph without duplicates (example bellow).
Such links as [0,1] and [1,0] are considered duplicates.
const links = [
[ 0, 1 ], // duplicates
[ 0, 6 ],
[ 0, 8 ],
[ 1, 0 ], // duplicates
[ 1, 4 ],
// ...
]
Right now I do it this way:
const links = new Set;
const skip = [];
list.forEach( (v, i) => {
v.forEach( j => {
if (skip.indexOf(j) === -1) {
links.add([i, j]);
}
})
skip.push(i);
})
I am wondering if there is a better pattern to solve this kind of task on massive arrays.
You could sort your link tuple values, skip the check skip.indexOf(j) and let Set take care of the duplicates.
You could take a stringed array as value for the for the set, because an array with only sorted value is checking with strict mode in the set.
A primitive data type, like string works best.
var list = [[1, 6, 8], [0, 4, 6, 9], [4, 6], [4, 5, 8]],
links = new Set;
list.forEach((v, i) => v.forEach(j => links.add([Math.min(i, j), Math.max(i, j)].join())));
console.log([...links]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use one object to store value: index that has already been used and then check that object before adding to array.
const list = [[1, 6, 8],[0, 4, 6, 9],[4, 6],[4, 5, 8],];
var o = {},r = []
list.forEach(function(e, i) {
e.forEach(function(a) {
if (o[i] != a) {
r.push([i, a])
o[a] = i
}
})
})
console.log(JSON.stringify(r))
With ES6 arrow functions you can write the same like this.
const list = [[1, 6, 8], [0, 4, 6, 9], [4, 6], [4, 5, 8],];
var o = {}, r = []
list.forEach((e, i) => e.forEach(a => o[i] != a ? (r.push([i, a]), o[a] = i) : null))
console.log(JSON.stringify(r))

Categories

Resources