how to split an array into equal chunks? - javascript

I've found a lot of answers to the question "how to split an array in multiple chunks", but I can't find a way to best repartition the array. For example,
let x = [1,2,3,4,5,6,7,8,9,10,11,12,13];
//#Source https://www.w3resource.com/javascript-exercises/fundamental/javascript-fundamental-exercise-265.php
const chunk = (arr, size) =>
Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
const n = 10;
console.log(chunk(x,n))
This function gives me two arrays: [1,2,3,4,5,6,7,8,9,10] and [11,12,13]. But I would prefere n to be used as a "max" to obtain [1,2,3,4,5,6,7] and [8,9,10,11,12,13]. This way I would have two arrays of the same size. If it is possible for the selected n, they should be of equal size, otherwise, two arrays with a nearly identical size.

I broke it down into 3 steps.
Compute numChunks, how many chunks you need? E.g. if you have an array of 103 elements and a max size of 10, then you'll need 11 chunks.
Compute minChunkSize, the size of the smaller chunks. E.g. in the above example, the first 7 chunks will have 10 elements, while the other 3 chunks will have 11 elements (710 + 311 = 103).
Compute numSmallChunks, how many small chunks you can have. E.g. 3 in the above example.
Then you just splice the arr accordingly.
let chunk = (arr, maxSize) => {
let numChunks = parseInt((arr.length - 1) / maxSize) + 1;
let minChunkSize = parseInt(arr.length / numChunks);
let numSmallChunks = numChunks * (minChunkSize + 1) - arr.length;
arr = [...arr]; // avoid muckking the input
let arrays = [];
for (let i = 0; i < numChunks; i++)
if (i < numSmallChunks)
arrays.push(arr.splice(0, minChunkSize));
else
arrays.push(arr.splice(0, minChunkSize + 1));
return arrays;
};
let x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
for (let i = 1; i < x.length; i++)
console.log(i, JSON.stringify(chunk(x, i), null, ''));
Note, the other answers result in an unbalanced; e.g. they produce arrays of sizes 4, 4, 4, & 1 when n is 4. Whereas my approach produces arrays of sizes 3, 3, 3, & 4. I guess it's up to the situation which you need, but this is how I interpret the question's "equal chunks".

If you need n to be max, Then calculate size as below.
let x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
const chunk = (arr, max) => {
const size = Math.min(max, Math.ceil(arr.length / 2));
return Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
};
const n = 10;
console.log(chunk(x, n));

You could slice with a variable size.
1 2 3 4 5 5
6 7 8 9 10 5
11 12 13 14 4
15 16 17 18 4
const
chunk = (array, max) => {
let
length = Math.ceil(array.length / max),
size = Math.ceil(array.length / length);
return Array.from(
{ length },
(_, i) => (
array.length === size * length - length + i && size--,
array.slice(size * i, size * (i + 1))
)
);
}
console.log(chunk([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], 5).map(a => a.join(' ')));

Related

JS data transformation methods don't seem to be working

Create a function 'calcAverageHumanAge', which accepts an arrays of dog's
ages ('ages'), and does the following things in order:
Calculate the dog age in human years using the following formula: if the dog is
<= 2 years old, humanAge = 2 * dogAge. If the dog is > 2 years old,
humanAge = 16 + dogAge * 4
Exclude all dogs that are less than 18 human years old (which is the same as
keeping dogs that are at least 18 years old)
Calculate the average human age of all adult dogs (you should already know
from other challenges how we calculate averages )
Run the function for both test datasets
Test data:
Data 1: [5, 2, 4, 1, 15, 8, 3]
Data 2: [16, 6, 10, 5, 6, 1, 4]
My solution(idk why wont work):
const calcAverageHumanAge = function (ageList) {
const avgAge = ageList
.map(val => (val <= 2 ? 2 * val : 16 + val * 4))
.fliter(val => val >= 18)
.reduce((acc, val, i, list) => {
return acc + val / list.length;
}, 0);};
You have three issues. Your reduce doesn't return anything is the main problem, but you're also dividing by list.length in each callback which doesn't make any sense (actually it does and I'm dumb), and then you aren't returning anything from the function. You want something like this:
const calcAverageHumanAge = function (ageList) {
const filteredVals = ageList
.map(val => (val <= 2 ? 2 * val : 16 + val * 4))
.filter(val => val >= 18);
return filteredVals.reduce((acc, val) => acc + val) / filteredVals.length;
};
When run on your data:
calcAverageHumanAge([5, 2, 4, 1, 15, 8, 3]); // 44
calcAverageHumanAge([16, 6, 10, 5, 6, 1, 4]); // 47.333

Splitting an array into equal chunks and then allot remaining

I have a array where I need to divide equally into person number of chunks.
if there are 100 items in array for 10 persons, 10 chunks to be created with each 10 items
if there are 100 items in array for 9 persons, 9 chunks to be created and each would get 9 items and the extra should be given to first person, second person and so on. which means Person 1 = 10; Person 2, 3, 4 ... would have 9 items
if there are 100 items in array for 11 persons, 11 chunks are to be created with each would get 10 and the extra should be given to first person. which means
Person 1 is 10
Person 2 is 9
Person 3 is 9
Person 4 is 9
Person 5 is 9
Person 6 is 9
Person 7 is 9
Person 8 is 9
Person 9 is 9
Person 10 is 9
Person 11 is 9
I have tried with
Object.defineProperty(Array.prototype, 'chunk', {
value: function(chunkSize) {
var array = this;
return [].concat.apply([],
array.map(function(elem, i) {
return i % chunkSize ? [] : [array.slice(i, i + chunkSize)];
})
);
}
});
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ..., 150, 151].chunk(Math.floor(151 / 31))
but that would gives me an incorrect allocation.
Use the remainder of the division to get the number of slices that need an extra element. Then use the index in the new (chunked) array to determine whether it needs that extra element (i.e. when that index is less than the remainder).
NB: I would not alter the Array prototype. Just make this a plain function.
Implementation:
function partition(arr, length) {
let rest = arr.length % length;
let size = Math.floor(arr.length / length);
let j = 0;
return Array.from({length}, (_, i) => arr.slice(j, j += size + (i < rest)));
}
let data = Array.from({length: 151}, (_,i) => i+1);
let result = partition(data, 31);
console.log(result);
You can use generators to split an array in chunk:
function* chunks(arr, n) {
for (let i = 0; i < arr.length; i += n) {
yield arr.slice(i, i + n);
}
}
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ..., 150, 151];
console.log([...chunks(a, Math.floor(151 / 31))]);
Then you can adapt this code to move the last element in first place (if you want first person to be the most empty).
As a side note, you can replace this:
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ..., 150, 151];
with this:
let a = [...Array(151).keys()];

Multiply elements of the array by 3 weighting factors and repeat

I want to be able to multiply each array element by 3 different weighting factors.
User input = [24,3,0, 56,43,34]
Output = 24x7,3x3,0x1 + 56x7, 43x3, 34x0.. and repeat so basically at every 3 elements of the array a multiplication by 7, then 3, then 0 occurs.
It would look something like this:
For every array element multiply each array weighting factor and repeat when it gets to 3rd element
function multiplyWeightFact(input){
const weighting = [7,3,1]
for (let i = 0; i < input.length ; i++) {
console.log( input[0] * weighting[0])
console.log( input[1] * weighting[1])
console.log( input[3] * weighting[2])
break
}
for (let index = 0; index < input.length; index++) {
console.log( input[4] * weighting[0])
console.log( input[5] * weighting[1])
console.log( input[6] * weighting[2])
break
}
}
input from user = [24,3,0, 56,43,34]
and it continues if we have an array with lets say 100 numbers..
The output needs to be something like:
resultOutput = 374 when input is [24,10]
Of course the above function is not sustainable so any better way to do this?
You could map by taking an index and the remainder operator with the length of weighting.
const
multiplyBy = weighting => (v, i) => v * weighting[i % weighting.length],
array = [24, 3, 0, 56, 43, 34],
weighting = [7, 3, 1],
result = array.map(multiplyBy(weighting));
console.log(...result);
With x
const
multiplyBy = weighting => (v, i) => `${v}x${weighting[i % weighting.length]}`,
array = [24, 3, 0, 56, 43, 34],
weighting = [7, 3, 1],
result = array.map(multiplyBy(weighting));
console.log(...result);
You could just map over the input and multiply it by the weight based on it's index. We can then use remainder operator to keep the index within the range of the weight array.
function multiplyWeightFact(input, weight){
return input.map((num, index) => num * weight[index % weight.length]);
}
const weight = [7, 3, 0];
const input = [24, 3, 0, 56, 43, 34];
const result = multiplyWeightFact(input, weight);
console.log(result);
You can loop through the array elements and use the modulo operator to multiply with according to the position the right factor.
Modulo Explanation
Calculates the remainder.
const weighting = [7, 3, 1] // Instead of 3 weighting.length
1. 0 % 3 = 0 = Array Element = 7
2. 1 % 3 = 1 = Array Element = 3
3. 2 % 3 = 2 = Array Element = 1
4. 3 % 3 = 0 = Array Element = 7
And again ...
function multiplyWeightFact(input) {
const weighting = [7, 3, 1]
for (let index = 0; index < input.length; index++) {
input[index] *= weighting[index % weighting.length];
}
return input;
}
let input = [5,4,3,2,1]
console.log(multiplyWeightFact(input));

Slicing an array and converting from RTL / LTR

I have an array of elements, the length changes but its always divisable by 8 (8,16,24,32,40,48..etc).
I am trying to slice off the first 8 elements, return them.
Then slice off the next 8 elements and return them in reverse.
And repeat until the array is empty (RTL then LTR then RTL..etc).
I am unsure how to iterate and keep track.
const print = (value) => document.write(value);
rtl = true
elements = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
const sliceArr = (arr, cnt) => {
arr = arr.slice(0,cnt)
if(rtl) return arr
else {
rtl = !rtl; // toggle rtl
}
}
tmpArray = []
tmpArray = sliceArr(elements, 8)
print(tmpArray)
Desired output
1,2,3,4,5,6,7,8
16,15,14,13,12,11,10,9
17,18,19,20,21,22,23,24
Can someone point me in the right direction?
Thanks!
You can use Array.from() to create a new array with a length of Math.ceil(elements.length / 8). In the callback, get the current index, and get the slice from the elements array. If i % 2 is 0 return the slice, but if it's an odd index, return the reverse of the slice:
const elements = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
const result = Array.from({ length: Math.ceil(elements.length / 8) }, (_, i) => {
const arr = elements.slice(i * 8, (i + 1) * 8)
return i % 2 ? arr.reverse() : arr
})
console.log(result.join('\n'))
You could slice the array and check the direction.
const slice = (array, size, direction) => {
var result = [],
i = 0,
temp;
while (i < array.length) {
var temp = array.slice(i, i +=size);
if (direction) temp.reverse();
direction = !direction;
result.push(temp);
}
return result;
}
slice([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], 8, false).map(a => console.log(...a));

Generate valid combinations of numbers in array of digits

I’m trying to generate all valid combinations of numbers from an array of digits. Let’s assume we have the following:
let arr = [1, 2, 9, 4, 7];
We need to output something like this:
1 2 9 4 7
1 2 9 47
1 2 94 7
1 2 947
1 29 4 7
1 29 47
1 294 7
1 2947
12 9 4 7
12 9 47
12 94 7
12 947
129 4 7
129 47
1294 7
12947
An invalid number would be 91, 497, 72 and so on.
I tried this but I’m not satisfied with the result:
const combination = (arr) => {
let i, j, temp;
let result = [];
let arrLen = arr.length;
let power = Math.pow;
let combinations = power(2, arrLen);
for (i = 0; i < combinations; i += 1) {
temp = '';
for (j = 0; j < arrLen; j++) {
if ((i & power(2, j))) {
temp += arr[j];
}
}
result.push(temp);
}
return result;
}
const result = combination([1, 2, 9, 4, 7]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Any ideas?
This code does what you want:
const arr = [1, 2, 9, 4, 7],
result = Array.from({length: 2 ** (arr.length - 1)}, (_, index) => index.toString(2).padStart(arr.length - 1, "0"))
.map((binary) => JSON.parse("[" + arr.map((num, position) => num + (Number(binary[position]) ? "," : "")).join("") + "]"));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
It results in:
[
[12947],
[1294, 7],
[129, 47],
[129, 4, 7],
[12, 947],
[12, 94, 7],
[12, 9, 47],
[12, 9, 4, 7],
[1, 2947],
[1, 294, 7],
[1, 29, 47],
[1, 29, 4, 7],
[1, 2, 947],
[1, 2, 94, 7],
[1, 2, 9, 47],
[1, 2, 9, 4, 7]
]
Assuming, the expected result does not depend on order, the spaces represent a binary pattern:
12947 => 0000
1294 7 => 0001
129 47 => 0010
…
1 29 47 => 1010
…
1 2 9 4 7 => 1111
We can utilize this pattern with a counter that we convert to a binary string. We also pad that string with 0 so it always remains 4 digits long:
index.toString(2).padStart(arr.length - 1, "0")
For n digits in arr, there are exactly 2n - 1 combinations, so we use:
{length: 2 ** (arr.length - 1)}
This is an object that has a length property of 2arr.length - 1.
We combine both those things into an Array.from call which accepts two arguments:
an object to turn into an array
a function for mapping each slot
Turning an object with a length property into an array means that we create an array with length many slots.
The mapping function accepts the index of a slot as the second parameter. We only use the index — as a counter for our binary number.
So, finally this whole expression:
Array.from({length: 2 ** (arr.length - 1)}, (_, index) => index.toString(2).padStart(arr.length - 1, "0"))
evaluates to the following array:
[
"0000",
"0001",
"0010",
"0011",
"0100",
"0101",
"0110",
"0111",
"1000",
"1001",
"1010",
"1011",
"1100",
"1101",
"1110",
"1111"
]
We need to further map this to the final result:
.map((binary) => …)
For each array element, binary is one of the binary strings from the array above.
In order to turn e.g. "0110" into something like "12,9,47", we need to map over arr as well. Every digit num from arr should be followed by , at position, iff binary is 1 at position:
arr.map((num, position) => num + (Number(binary[position]) ? "," : "")).join("")
The expression (Number(binary[position]) ? "," : "") evaluates binary at the specified position as a number. If it’s truthy, i.e. anything but 0, it evaluates to ",", if it’s falsy, i.e. 0, it evaluates to "".
So an intermediate array would look like ["1", "2,", "9,", "4", "7"]. All of this is joined together to "12,9,47".
Then, with JSON.parse("[" + … + "]") it’s being treated and parsed as an array, so it turns into [12, 9, 47]. Since these steps are applied for each binary string, you’ll end up with the final result.
2 ** (arr.length - 1) can be replaced by Math.pow(2, arr.length - 1) if ECMAScript 7 is not supported.
{length: 2 ** (arr.length - 1)} can be replaced by new Array(2 ** (arr.length - 1)).
(Number(binary[position]) ? "," : "") can be replaced by ["", ","][Number(binary[position])]. In this case the evaluated number will be used as an index for a temporary array.
So you need to iterate over all the combinations of "space" and "not space" between all the numbers. With n items, there will be n - 1 spaces, and 2 ** (n - 1) different lists.
So you could do something like this to get all the possible lists:
const combination = arr => {
const len = arr.length;
const n = Math.pow(2, len - 1);
const combinations = [];
for (let i = 0; i < n; i++) {
let this_combination = [arr[0]];
for (let j = 1; j < len; j++) {
if (i & Math.pow(2, j - 1)) {
// If the jth bit is on, no space. Append to the last element.
const last_index = this_combination.length - 1;
this_combination[last_index] = +(this_combination[last_index] + '' + arr[j]);
} else {
// Otherwise create a new list item.
this_combination.push(arr[j]);
}
}
// Consider making this function a generator and making this a yield.
combinations.push(this_combination);
}
return combinations;
}
const result = combination([1, 2, 9, 4, 7]);
console.log(result.map(line => line.join(' ')).join('\n'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you wanted each individual item seperately, for each item in the array, combine it with no other item, then just the next item, then the next 2 items, etc. untill the end:
const combination = arr => {
const len = arr.length;
const combinations = [];
for (let i = 0; i < len; i++) {
let item = arr[i];
combinations.push(item);
for (let j = i + 1; j < len; j++) {
item = +(item + '' + arr[j]);
combinations.push(item);
}
}
return combinations;
}
const result = combination([1, 2, 9, 4, 7]);
console.log(result.join('\n'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could take a recursive approach by iterating the array and insert a space or not and fork the calling of the same function with an incremented index.
function combine(array) {
function fork(i, p) {
if (i === array.length) {
result.push(p);
return;
}
fork(i + 1, p + ' ' + array[i]);
fork(i + 1, p + array[i]);
}
var result = [];
fork(1, array[0].toString());
return result;
}
console.log(combine([1, 2, 9, 4, 7]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do this by using below code where 3 pointer is used,
1st pointer print 0th position to cursor position.
2nd pointer print cursor to diffidence position in each iteration .
3rd pointer print cursor position to last position.
let arr = [1, 2, 9, 4, 7];
console.log(arr.join(','));
for(let diff=2;diff<=arr.length;diff++){
for(i=0,j=diff;arr.length>=i+diff;j++,i++){
var temp = [];
if(i>0)
temp.push(arr.slice(0,i).join(','));
temp.push(arr.slice(i,j).join(''));
if(j<arr.length)
temp.push(arr.slice(j,arr.length).join(','));
console.log(temp.join(','));
}
}

Categories