I feel like I didn't phrase my title very well, can someone please correct it if you understand my question.
I have an array of
arr = [1,2,3,4,5,
6,7,8,9,0,
3,4,7,2,1,
4,6,1,2,3,
5,6,8,9,3
2,3,4,5,6
]
And I want to do several things
Split it into chunks with the size of 5
Calculate the number of chunks. In this case, it should be 6 chunks.
Calculate the sum of numbers of all chunks in each position and divide it by the total number of chunks. In this case,
(1+6+3+4+5+2)/6, (2+7+4+6+6+3)/6, ..., (5+0+1+3+3+6)/6
Return results as an array
var result = [3.5, 4.66, ..., 3]
I have got the idea, but not sure how to implement it.
Thanks
I believe this code accomplishes what you want.
function averageValues (arr) {
var chunks = Math.ceil(arr.length / 5); // find the number of chunks
var sums = [0, 0, 0, 0, 0]; // keep a running tally
for (var i = 0; i < arr.length; i ++) {
sums[i % 5] += arr[i]; // add each element to the proper part of the sum
}
for (var i = 0; i < sums.length; i ++) {
sums[i] /= chunks; // divide each part of the sum by the number of chunks
}
return sums;
}
You can solve this by maintaining five separate sums to end with five separate averages.
Prepare your sums array of length 5:
var sums = [ 0, 0, 0, 0, 0 ];
For each number in your set, increment the corresponding sum by that number.
for (var x = 0; x < arr.length; x++)
sums[x % 5] += arr[x];
Divide each sum by how many numbers were used:
var numbers = arr.length / 5; // 6 numbers each
var result = sums.map(
function(s) {
return s / numbers; // divide each sum by 6
}
);
This uses the assumption that your set length is always a multiple of 5.
Here is a more functional approach to your problem. This uses the assumption that your set length is always a multiple of 5.
// add extra array helpers
Array.prototype.eachSlice = function (n, fn) {
let slices = [];
for (let i = 0; i < this.length; i += n) {
let slice = this.slice(i, i + n);
slices.push(slice);
}
if (fn) slices.forEach(fn);
return slices;
}
Array.prototype.sum = function (fn) {
let fnReduce = fn ? (acc, ...args) => acc + fn(...args) : (acc, v) => acc + v;
return this.reduce(fnReduce, 0);
}
Array.prototype.avg = function (fn) {
return this.sum(fn) / this.length;
}
// actual solution
let arr = [
1,2,3,4,5,
6,7,8,9,0,
3,4,7,2,1,
4,6,1,2,3,
5,6,8,9,3,
2,3,4,5,6,
];
let chunkSize = 5;
console.log('--- question #1 ---');
console.log('Split it into chunks with the size of 5.');
console.log('-------------------');
let chunks = arr.eachSlice(chunkSize);
console.log(chunks);
console.log('--- question #2 ---');
console.log('Calculate the number of chunks. In this case, it should be 6 chunks.');
console.log('-------------------');
console.log(chunks.length);
console.log('--- question #3 ---');
console.log('Calculate the sum of numbers of all chunks in each position and divide it by the total number of chunks.');
console.log('-------------------');
let avgChunks = new Array(chunkSize).fill()
.map((_, i) => chunks.avg(chunk => chunk[i]));
console.log('See the result under question #4.');
console.log('--- question #4 ---');
console.log('Return results as an array.');
console.log('-------------------');
console.log(avgChunks);
It could be useful:
//The average method using an array
function average(arr) {
var sum = arr.reduce(function (a,b) { return a + b; },0)
return sum/ arr.length
}
//Chunk array method, it returns an array of the sliced arrays by size
function chunkArray(arr, chunkSize){
var numChunks = arr.length / chunkSize
var chunks= []
for (let index = 0; index < numChunks; index++) {
chunks.push(arr.slice(index * chunkSize, (index * chunkSize) + chunkSize))
}
return chunks
}
//Finally, the average of arrays, it returns the average of each array
function averageArrays(arrs){
return arrs.map(function (arr) {
return average(arr)
})
}
//Example of usage
var chunks = chunkArray([
1,2,3,4,5,
6,7,8,9,0,
3,4,7,2,1,
4,6,1,2,3,
5,6,8,9,3,
2,3,4,5,6
],5)
console.log(averageArrays(chunks))
I think #Aplet123 has the most straight forward and easy to understand approach, though I changed up a little bit to suit my needs.
var chunks = Math.ceil(arr.length / 5) // Find the number of chunks
var sums = new Array(5).fill(0) // Keeps a running tally and fill values 0
arr.map((x, i) => sums[i%5] += arr[i]) // add each element to the proper part of the sum
var avgs = sums.map((x) => x/chunks /divide each part of the sum by the number of chunks
Related
Hi I am trying to create an array with paired values from 1-size in random order example : [3,1,1,2,3,2] for size = 3
So far I've done sth like this :
I fill up both arrays with random numbers when the number isn't already in array
Repeat for second array
And then return concatenation of them
I wonder how I can improve the solution of my problem
let arr1 = [];
let arr2 = [];
let number;
let i = 0;
let k = 0;
while (i < size) {
number = Math.floor(Math.random() * size + 1);
if (!arr1.includes(number)) {
arr1.push(number);
i++;
}
}
while (k < size) {
number = Math.floor(Math.random() * size + 1);
if (!arr2.includes(number)) {
arr2.push(number);
k++;
}
}
return arr1.concat(arr2);
your way requires too much unnecessary operations. You depend on how many time random values will repeat till cover all numbers. Better just create one array, and find random index, then remove chosen item, moving it to result array. So no repeatings, very effective.
And also method includes is not effective for big arrays. My solution is
const createRandomArrays = size => {
const res = Array.from([1, 2]).flatMap(() => {
const numbers = Array.from({ length: size }, (v, ind) => ind + 1)
const res = []
while (numbers.length) {
const ind = Math.floor(Math.random() * numbers.length)
res.push(numbers[ind])
numbers.splice(ind, 1)
}
return res
})
console.log(res)
}
PS But you can not get [3,1,1,2,3,2] from your code as each array has its own set, you can get [3,2,1,1,3,2] or something, two digits 1 can not be together in first half of result array.
If you want them mixed than solution is
const createRandomArrays = size => {
const numbers = Array.from({ length: size * 2 }, (v, ind) => (ind % size) + 1)
const res = []
while (numbers.length) {
const ind = Math.floor(Math.random() * numbers.length)
res.push(numbers[ind])
numbers.splice(ind, 1)
}
console.log(res)
}
I think you are on the right track. But the solution can be simplified quite a bit with improved performance.
If you think about it, you really only need two operations. First lets pretend your array is a pack of cards. What you are really doing is just shuffling two packs of cards together.
Lets pretend you are given an array A containing all elements up to N say [1,2,3]. We can just concat A with A to get A' = [1,2,3,1,2,3]. Now to complete our card shuffling example, we just need to shuffle everything. Say we have a function S that shuffles an array, applying it to our array A' to get our random permutation [2,1,2,3,1,3] or whatever.
const pairs = (n) => {
return Array.from({ length: n * 2 }, (_, i) => {
return (i + 1) % n + 1;
});
};
const shuffle = (array) => {
const n = array.length;
const result = array.slice();
for (let i = 0; i < array.length - 1; i++) {
const j = Math.floor(Math.random() * (n - i) + i);
const t = result[i];
result[i] = result[j];
result[j] = t;
}
return result;
};
console.log(shuffle(pairs(3)));
pairs() will generate an array of 2 * n length containing the range [1, n] twice. shuffle performs a fisher-yates shuffle over the array returning your results.
I have a given array with an undetermined quantity of elements, the array can be numbers or strings, then I need to generate a new array of N elements made from the iterated elements of the first array
I already have a function to do it, but it only works if the original array are consecutive numbers, it doesn't work with strings. I have a gazillion of ideas on how to achieve it. I could just concatenate the array to a new one until its equal or greater than the required quantity of elements, and then set the new array length to the required quantity, but is there a more concise and elegant way to do it?
IDEA 01 codepen
function populateArray(qty) {
// Array to populate from
let array = [1,2,3];
//Determine the Range Length of the array and assign it to a variable
let min = array[0];
let max = array[array.length - 1];
const rangeLength = (max - min + 1);
//Initialize uniqueArray which will contain the concatenated array
let uniqueArray = [];
//Test if quantity is greater than the range length and if it is,
//concatenate the array to itself until the new array has equal number of elements or greater
if (qty > rangeLength) {
//Create an array from the expansion of the range
let rangeExpanded = Array.from(new Array(rangeLength), (x,i) => i + min);
while (uniqueArray.length < qty) {
uniqueArray = uniqueArray.concat(rangeExpanded);
}
}
// Remove additional elements
uniqueArray.length = qty
return uniqueArray;
}
console.log(populateArray(13))
IDEA 02 codepen, but it fills the new array 13 times with the whole original array, not iterated items
// FILL A NEW ARRAY WITH N ELEMENTS FROM ANOTHER ARRAY
let array = [1,2,3];
let length = 13;
let result = Array.from( { length }, () => array );
console.log(result);
the expected result is [1,2,3,1,2,3,1,2,3,1,2,3,1] if the original array were made of strings the expected result would be [dog,cat,sheep,dog,cat,sheep,dog,cat,sheep,dog,cat,sheep,dog]
You can tweak your second idea a bit - calculate the number of times you need to repeat the initial array to come up with the required number of total items, then flatten it and .slice:
let array = [1,2,3];
let length = 13;
const fromLength = Math.ceil(length / array.length);
let result = Array.from( { length: fromLength }, () => array )
.flat()
.slice(0, length);
console.log(result);
I'll go with #CertainPerformance's answer. But here's a different approach, just for thinking-out-of-the-box purposes
// A function for getting an index up to length's size
function getIDX(idx, length){
return idx <= length ? idx : getIDX(idx-length, length);
}
const newArrayLength = 13;
const sourceArray = [1,2,3];
const resultArray = [];
for(let i=0; i< newArrayLength; i++){
resultArray[i]=sourceArray[getIDX(i+1, sourceArray.length)-1];
}
EDIT 1:
I was comparing the performance of this approach versus the others here described and it seems that if you wanted to create a very large new array (ex: newArrayLength= 10000) the getIDX() function takes a lot to finish because of the size of the call stack. So I've improved the getIDX() function by removing the recursion and now the complexity is O(1) check it out:
function getIDX(idx, length){
if (length === 1) {return idx};
const magicNumber = length * (Math.ceil(idx/length)-1);
return idx - magicNumber;
}
With the new getIDX() function this approach seems to be the most performant.
You can take a look to the tests here:
https://jsbench.me/v7k4sjrsuw/1
You can use a generator function that will create a repeating sequence from an input. You can add a limit to the generated sequence and simply turn it into an array:
function* repeatingSequence(arr, limit) {
for(let i = 0; i < limit; i++) {
const index = i % arr.length;
yield arr[index];
}
}
const generator = repeatingSequence(["dog", "cat", "sheep"], 10);
const result = Array.from(generator);
console.log(result);
Alternatively, you can make an infinite repeating sequence with no limit and then generate as many elements as you want for an array:
function* repeatingSequence(arr) {
let i = 0
while(true) {
const index = i % arr.length;
yield arr[index];
i++;
}
}
const generator = repeatingSequence(["dog", "cat", "sheep"]);
const result = Array.from({length: 10}, () => generator.next().value);
console.log(result);
You can use modulo operator. Special thanks to #Vlaz for shorten version:
Array.from({ length:length }, (e, i) => array[ i % array.length ])
An example:
let array = [1,2,3];
let length = 13;
const result = Array.from({ length:length },
(e, i) => array[ i % array.length ]);
console.log(result);
I have a matrix with n-rows and n-columns. I need to make sure that the numbers in each row are unique.
let matrix = [];
let matrixRows = 3;
let matrixColumns = 5;
for ( let i = 0; i < matrixRows; i++ ) {
matrix[ i ] = [];
let j = 0;
while (j < matrixColumns) {
matrix[ i ][ j ] = Math.floor(Math.random() * 5) + 1;
j++;
}
}
console.log( matrix.join('\n') );
It should look something like this
"1,2,3,4,5 \\ here is line break (new row)
1,4,2,5,3 \\ here is line break (new row)
5,4,2,3,1"
You can do that in following steps:
First create a function which takes two parameters rows and cols
Then create a helper function shuffleArray which takes an array as argument and return a new array which is shuffled.
In the main function create an array of number for the no of cols. In the case it will be [1,2,3,4,5]. You can do that using map()
Then create an array of undefined of length equal to the given rows.
Use map() on that and return a new shuffled array that we created before([1,2,3,4,5])
function shuffleArray(arr){
//create a copy of the array
arr = arr.slice();
//create an array on which random items from 'arr' will be added
let res = [];
//create while loop which will run until all the elements from arr are removed
while(arr.length){
//generate a random index in range of length of 'arr'
let i = Math.floor(arr.length * Math.random())
//push element at that index to result array
res.push(arr[i]);
//remove that element from the orignal array i.e 'arr'
arr.splice(i,1);
}
return res;
}
function randMatrix(rows,cols){
//create an array which will shuffled again and again.
let genArr = [...Array(cols)].map((x,i) => i + 1);
return [...Array(rows)] // create an array of undefined of length equal to rows
.map(x => shuffleArray(genArr)) // change that each to another shuffled array.
}
console.log(randMatrix(3,5).join('\n'))
You could create an array of numbers upto matrixColumns using Array.from(). Then shuffle the array randomly in every iteration and create rows (from this answer)
// https://stackoverflow.com/a/18806417/3082296
function shuffle(arr) {
let i = arr.length,
copy = [...arr], // take a copy
output = [];
while (i--) {
const j = Math.floor(Math.random() * (i + 1));
output.push(copy.splice(j, 1)[0]);
}
return output
}
let matrix = [];
let matrixRows = 3;
let matrixColumns = 5;
// Natural numbers upto matrixColumns
const numbers = Array.from({ length: matrixColumns }, (_, i) => ++i)
const output = Array.from({ length: matrixRows }, _ => shuffle(numbers))
console.log(output)
Not the most elegant, but this first creates a flat list of unique randoms and reduces that to a 2d n*m matrix:
function fillRand (n, m) {
let rands = [];
do {
var rand = Math.random ();
} while (!~rands.indexOf (rand) && rands.push (rand) < n*m);
return rands.reduce ((mat, cur, i) => {
let last;
if (i%n==0) {
mat.push ([]);
}
last = mat[mat.length - 1]
last.push (cur);
return mat;
},[])
}
console.log (fillRand (4,5))
My program should be as following:
Input : {1,2,3,2,1,8,-3}, sum = 5
Output should be 3 example combinations ({2,3}, {3,2}, {8,-3}) have sum
exactly equal to 5.
I tried to do it in JavaScript but I'm confused.
function findSubarraySum(arr, sum) {
var res = 0;
var currentSum = 0;
for (var i = 0; i < arr.length; i++) {
currentSum += arr[i];
if (currentSum == sum)
res++;
}
return res;
}
console.log(findSubarraySum([1, 2, 3, 4], 10));
You first need a way to iterate over all the unique ways you can choose a start and and of your subarray boundaries (your slice definition).
In my code below, I use a combinations function to get all possible combinations of two indexes for the array supplied. You could do something else, like a simple doubly nested for loop.
Next you need to take the slice of the array according to the slice definition and reduce the elements into a sum. The Array.prototype.reduce function works well for that.
Finally, you want to include the subArray in the list of results only if the reduced sum matched the desired sum.
// Input : {1,2,3,2,1,8,-3}, sum = 5
const { combinations, range } = (() => {
const _combinations = function*(array, count, start, result) {
if (count <= 0) {
yield [...result]; // Yes, we want to return a copy
return;
}
const nextCount = count - 1;
const end = array.length - nextCount; // leave room on the array for the next remaining elements
for (let i = start; i < end; i += 1) {
// we have already used the element at (start - 1)
result[result.length - count] = array[i];
const nextStart = i + 1; // Always choose the next element from the ones following the last chosen element
yield* _combinations(array, nextCount, nextStart, result);
}
};
function* combinations(array, count) {
yield* _combinations(array, count, 0, Array(count));
}
function* range(l) {
for (let i = 0; i < l; i += 1) {
yield i;
}
}
return {
combinations,
range,
};
})();
const subArraysBy = (predicate, array) => {
const result = [];
for (const [beg, end] of combinations([...range(array.length+1)], 2)) {
const subArray = array.slice(beg, end);
if (predicate(subArray)) {
result.push(subArray);
}
}
return result;
};
const sum = array => array.reduce((sum, e) => sum + e);
console.log(
subArraysBy(
a => sum(a) === 5,
[1, 2, 3, 2, 1, 8, -3],
),
);
References:
MDN: Array.prototype.reduce
MDN: function* -- not required for your solution
Lodash: _.range -- implemented this in my code rather than use the lodash one. They work similarly.
Python Docs: combinations - My combinations implementation is inspired by python itertools.
I'm working on a small algorithm to find the closest values of a given number in an random array of numbers. In my case I'm trying to detect connected machines identified by a 6-digit number ID ("123456", "0078965", ...) but it can be useful for example to find the closest geolocated users around me.
What I need is to list the 5 closest machines, no matter if their IDs are higher or lower. This code works perfectly but I'm looking for a smarter and better way to proceed, amha I got to much loops and arrays.
let n = 0; // counter
let m = 5; // number of final machines to find
// list of IDs founded (unordered: we can't decide)
const arr = ["087965","258369","885974","0078965","457896","998120","698745","399710","357984","698745","789456"]
let NUM = "176789" // the random NUM to test
const temp = [];
const diff = {};
let result = null;
// list the [m] highest founded (5 IDs)
for(i=0 ; i<arr.length; i++) {
if(arr[i] > NUM) {
for(j=0 ; j<m; j++) {
temp.push(arr[i+j]);
} break;
}
}
// list the [m] lowest founded (5 IDs)
for(i=arr.length ; i>=0; i--) {
if(arr[i] < NUM) {
for(j=m ; j>=0; j--) {
temp.push(arr[i-j]);
} break;
}
}
// now we are certain to get at least 5 IDs even if NUM is 999999 or 000000
temp.sort(function(a, b){return a - b}); // increase order
for(i=0 ; i<(m*2); i++) {
let difference = Math.abs(NUM - temp[i]);
diff[difference] = temp[i]; // [ 20519 : "964223" ]
}
// we now get a 10-values "temp" array ordered by difference
// list the [m] first IDs:
for(key in diff){
if(n < m){
let add = 6-diff[key].toString().length;
let zer = '0'.repeat(add);
let id = zer+diff[key]; // "5802" -> "005802"
result += (n+1)+":"+ id +" ";
n+=1;
}
}
alert(result);
-> "1:0078965 2:087965 3:258369 4:357984 5:399710" for "176789"
You actually don't need to have so many different iterations. All you need is to loop twice:
The first iteration attempt is to use .map() to create an array of objects that stores the ID and the absolute difference between the ID and num
The second iteration attempt is simply to use .sort() through the array of objects created in step 1, ranking them from lowest to highest difference
Once the second iteration is done, you simply use .slice(0, 5) to get the first 5 objects in the array, which now contains the smallest 5 diffs. Iterate through it again if you want to simply extract the ID:
const arr = ["087965","258369","885974","078965","457896","998120","698745","399710","357984","698745","789456"];
let num = "176789";
let m = 5; // number of final machines to find
// Create an array of objects storing the original arr + diff from `num`
const diff = arr.map(item => {
return { id: item, diff: Math.abs(+item - +num) };
});
// Sort by difference from `num` (lowest to highest)
diff.sort((a, b) => a.diff - b.diff);
// Get the first m entries
const filteredArr = diff.slice(0, m).map(item => item.id).sort();
// Log
console.log(filteredArr);
// Completely optional, if you want to format it the way you have in your question
console.log(`"${filteredArr.map((v, i) => i + ": " + v).join(', ')}" for "${num}"`);
You could take an array as result set, fill it with the first n elements and sort it by the delta of the wanted value.
For all other elements check if the absolute delta of the actual item and the value is smaller then the last value of the result set and replace this value with the actual item. Sort again. Repeat until all elements are processed.
The result set is ordered by the smallest delta to the greatest by using the target value.
const
absDelta = (a, b) => Math.abs(a - b),
sortDelta = v => (a, b) => absDelta(a, v) - absDelta(b, v),
array = [087965, 258369, 885974, 0078965, 457896, 998120, 698745, 399710, 357984, 698745, 789456],
value = 176789,
n = 5,
result = array.reduce((r, v) => {
if (r.length < n) {
r.push(v);
r.sort(sortDelta(value));
return r;
}
if (absDelta(v, value) < absDelta(r[n - 1], value)) {
r[n - 1] = v;
r.sort(sortDelta(value));
}
return r;
}, []);
console.log(result); // sorted by closest value
A few good approaches so far, but I can't resist throwing in another.
This tests a sliding window of n elements in a sorted version of the array, and returns the one whose midpoint is closest to the value you're looking for. This is a pretty efficient approach (one sort of the array, and then a single pass through that) -- though it does not catch cases where there's more than one correct answer (see the last test case below).
const closestN = function(n, target, arr) {
// make sure we're not comparing strings, then sort:
let sorted = arr.map(Number).sort((a, b) => a - b);
target = Number(target);
let bestDiff = Infinity; // actual diff can be assumed to be lower than this
let bestSlice = 0; // until proven otherwise
for (var i = 0; i <= sorted.length - n; i++) {
let median = medianOf(sorted[i], sorted[i+n-1]) // midpoint of the group
let diff = Math.abs(target - median); // distance to the target
if (diff < bestDiff) { // we improved on the previous attempt
bestDiff = diff; // capture this for later comparisons
bestSlice = i;
}
// TODO handle diff == bestDiff? i.e. more than one possible correct answer
}
return sorted.slice(bestSlice, bestSlice + n)
}
// I cheated a bit here; this won't work if a > b:
const medianOf = function(a, b) {
return (Math.abs(b-a) / 2) + a
}
console.log(closestN(5, 176789, ["087965", "258369", "885974", "0078965", "457896", "998120", "698745", "399710", "357984", "698745", "789456"]))
// more test cases
console.log(closestN(3, 5, [1,2,5,8,9])) // should be 2,5,8
console.log(closestN(3, 4, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(1, 4, [1,2,5,8,9])) // should be 5
console.log(closestN(3, 99, [1,2,5,8,9])) // should be 5,8,9
console.log(closestN(3, -99, [1,2,5,8,9])) // should be 1,2,5
console.log(closestN(3, -2, [-10, -5, 0, 4])) // should be -5, 0, 4
console.log(closestN(1, 2, [1,3])) // either 1 or 3 would be correct...