Array element not splicing into separate array - FreeCodeCamp > Chunky Monkey - javascript

I'm working through the FreeCodeCamp challenges and I'm stuck on the following one:
Our goal for this Algorithm is to split arr (first argument) into
smaller chunks of arrays with the length provided by size (second
argument). There are several green checks (objectives) our code needs
to pass in order to complete this Algorithm:
(['a', 'b', 'c', 'd'], 2) is expected to return [['a', 'b'], ['c', 'd']]
([0, 1, 2, 3, 4, 5], 3) is expected to return [[0, 1, 2], [3, 4, 5]]
([0, 1, 2, 3, 4, 5], 2) is expected to return [[0, 1], [2, 3], [4, 5]]
([0, 1, 2, 3, 4, 5], 4) is expected to return [[0, 1, 2, 3], [4, 5]]
([0, 1, 2, 3, 4, 5, 6, 7, 8], 2) is expected to return [[0, 1], [2, 3],
[4, 5], [6, 7], [8]].
Here is the code I've come up with:
function chunkArrayInGroups(arr, size) {
var newArray = [];
var holdArray = [];
for (var i = 0; i <= arr.length; i += size) {
holdArray = arr.slice(0, size);
removed = arr.splice(0, size);
newArray.push(holdArray);
}
if (arr.length !== 0) {
newArray.push(arr);
return newArray;
}
else return newArray;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
The algorithm works for all of the objectives, except for the last one. Instead of returning:
[[0, 1], [2, 3], [4, 5], [6, 7], [8]]
it's returning:
[[0, 1], [2, 3], [4, 5], [6, 7, 8]] - and I can't work out why.
Any help shedding some light on where I'm going wrong would be much appreciated!

I don't blame you for being confused, it took me a while to find the problem, too.
It started with identifying that your for loop was a bit odd - you weren't using the i variable for anything. Additionally, you were splice()-ing the original array down each iteration, so your intention was correct - to wait until there is less than size elements left in the array.
So I expressed that as a while loop instead, and voila, it works:
function chunkArrayInGroups(arr, size) {
var newArray = [];
var holdArray = [];
while (arr.length >= size) {
holdArray = arr.slice(0, size);
removed = arr.splice(0, size);
newArray.push(holdArray);
}
if (arr.length !== 0) {
newArray.push(arr);
return newArray;
}
else return newArray;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);

You are recaluculating length again and again, but you forgot that you are actually removing elements from arr, so length would not be the same as size increases length decreases,either you can give as while loop or keep original length in a variable and check
function chunkArrayInGroups(arr, size) {
var newArray = [];
var holdArray = [];
var length=arr.length;
for (var i = 0; i <=length; i += size) {
holdArray = arr.slice(0, size);
removed = arr.splice(0, size);
newArray.push(holdArray);
}
if (arr.length !== 0) {
newArray.push(arr);
return newArray;
}
else return newArray;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);

Related

Get n non overlapping m-sized samples from an array

Given an array, how can I extract n non overlapping random samples of size m from it?
For example, given the array:
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
calling sample(arr, 3, 2) would for example return [[7, 8], [4, 5], [2, 3]], calling sample(arr, 2, 4) would necessarily return [[1, 2, 3, 4], [5, 6, 7, 8], and calling sample(arr, 5, 2) would throw an error.
EDIT - Maybe this wasn't clear in the initial question: samples should be lists of contiguous elements. That is why sample(arr, 2, 4) can only return [[1, 2, 3, 4], [5, 6, 7, 8] and not [[2, 3, 1, 6], [5, 4, 7, 8], for example.
You could start off by first creating a list with the format of the return value:
[ 1, 2, 3, 4, 5, 6, 7, 8]
[<---->, <---->, <---->, <>, <>] // sample(array, 3, 2)
[<------------>, <------------>] // sample(array, 2, 4)
These format arrays could be written out using the lengths:
[1, 2, 3, 4, 5, 6, 7, 8]
[ 2, 2, 2, 1, 1] // sample(array, 3, 2)
[ 4, 4] // sample(array, 2, 4)
Then shuffle the format arrays to gain a random sample selection:
[1, 2, 3, 4, 5, 6, 7, 8]
[ 2, 1, 2, 2, 1] // sample(array, 3, 2)
[ 4, 4] // sample(array, 2, 4)
Then for each element of the format array, remove the the first n elements from the input array. Then store them unless it was a filler (one size chunks that are put in to reach the array length).
[1, 2, 3, 4, 5, 6, 7, 8]
[[1,2], [4,5], [6,7]] // sample(array, 3, 2)
[[1,2,3,4], [5,6,7,8]] // sample(array, 2, 4)
Lastly shuffle the resulting samples.
[1, 2, 3, 4, 5, 6, 7, 8]
[[4,5], [1,2], [6,7]] // sample(array, 3, 2)
[[5,6,7,8], [1,2,3,4]] // sample(array, 2, 4)
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(sample(arr, 3, 2));
console.log(sample(arr, 2, 4));
console.log(sample(arr, 5, 2));
function randomInt(limit) {
return Math.floor(Math.random() * limit);
}
function shuffle(array) {
for (let limit = array.length; limit > 0; --limit)
array.push(...array.splice(randomInt(limit), 1));
}
function sample(array, sampleCount, sampleLength) {
let elementCount = sampleCount * sampleLength;
if (elementCount > array.length)
throw "invalid sampleCount/sampleLength arguments";
const filler = {valueOf: () => 1};
const fillerCount = array.length - elementCount;
const lengths = Array.from(
{length: sampleCount + fillerCount},
(_, i) => i < sampleCount ? sampleLength : filler
);
shuffle(lengths);
const samples = Array.from(array);
for (const length of lengths) {
const sample = samples.splice(0, length);
if (length === filler) continue;
samples.push(sample);
}
shuffle(samples);
return samples;
}
Note that === is important in length === filler. If you use ==, filler would also equal 1. This would then conflict with a call like sample(array, 5, 1) where each sample length is 1.
const filler = {valueOf: () => 1};
console.log("1 == filler //=>", 1 == filler);
console.log("2 == filler //=>", 2 == filler);
console.log("filler == filler //=>", filler == filler);
console.log("1 === filler //=>", 1 === filler);
console.log("2 === filler //=>", 2 === filler);
console.log("filler === filler //=>", filler == filler);
you can use a greedy algorithm, and take m-sized n tuples from the shuffled array:
const arr = [2, 1, 3, 4, 5, 6, 7, 8];
function sample(arr, length, size){
if(arr.length < length*size)
throw new Error("too short");
arr.sort(() => Math.random() - 0.5);
let res = [];
for(let i = 0; i < length; i++) res.push(arr.slice(i*size, i*size+size));
return res;
}
console.log(sample(arr, 2, 4));
I think the best implementation would shuffle first. Here's my two cents:
function shuffle(array){
let a = array.slice(), i = a.length, n, h;
while(i){
n = Math.floor(Math.random()*i--); h = a[i]; a[i] = a[n]; a[n] = h;
}
return a;
}
function sample(array, chunks, count){
const r = [], a = shuffle(array);
for(let n=0; n<chunks; n++){
r.push(a.splice(0, count));
}
return r;
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(sample(arr, 3, 2)); console.log(sample(arr, 2, 4));
You can do this with Rando.js (which is cryptographically secure), map, and splice pretty easily. Just use randojs' randoSequence function to shuffle the provided array and splice n size-m arrays out of that shuffled array to get everything we need to return. If the provided array has too few values, the later arrays that we return will just be shorter.
function sample(arr, n, m){
arr = randoSequence(arr).map(i => i.value), sample = [];
for(var i = 0; i < n; i++) sample[i] = arr.splice(-m);
return sample;
}
console.log(sample([1, 2, 3, 4, 5, 6, 7, 8], 3, 2));
<script src="https://randojs.com/2.0.0.js"></script>

I do not understand this solution

I start by pointing out that this code works. That is not the problem! I just don't understand how really. As I understand it the chunked.push method ads a new array all the time. But obviously, it does not. It gives the right answer:
[[ 1, 2], [3, 4], [5]]
I simply do not understand what is happening in this code. It spits out the right answer and put several items in each array if necessary but the code creates a new subarray each time, no? No, obviously not - but I don't understand why not? Please help!
function chunk(array, size) {
let workArr = [...array];
let chunked = [];
for (let i = 0; i < workArr.length; i++) {
let last = chunked[chunked.length - 1];
if (!last || last.length === size) {
chunked.push([workArr[i]])
} else {
last.push(workArr[i]);
}
}
return chunked;
}
Here is examples of some input parameters and expected results:
// chunk([1, 2, 3, 4], 2) --> [[ 1, 2], [3, 4]]
// chunk([1, 2, 3, 4, 5], 2) --> [[ 1, 2], [3, 4], [5]]
// chunk([1, 2, 3, 4, 5, 6, 7, 8], 3) --> [[ 1, 2, 3], [4, 5, 6], [7, 8]]
// chunk([1, 2, 3, 4, 5], 4) --> [[ 1, 2, 3, 4], [5]]
// chunk([1, 2, 3, 4, 5], 10) --> [[ 1, 2, 3, 4, 5]]
Let's break it
This copies an array there is used a spread operator
let workArr = [...array];
Iterate over every item in workArr array
for (let i = 0; i < workArr.length; i++) {
}
I think this would give you undefined at first run, because there is nothing at index -1 in chunked (because at first, chunked.length is 0), but it will set last to last element of chunked array
let last = chunked[chunked.length - 1];
If last has falsey value (0, null, undefined, "", NaN, false) or length of last equals to size (be aware, that last should be array or string), then push i-th element (indexing from 0) of workArr array into chunked array as an array, else push that element from workArr into last, but last then should be an array
if (!last || last.length === size) {
chunked.push([workArr[i]])
} else {
last.push(workArr[i]);
}
then simply return chunked array
return chunked;

Combinatorial analysis with JS: Find combinations of arrays

Given the main array "arr":
let arr = [1,2,3,4,5]
And the "options" arrays:
let optArr1 = [1,3]
let optArr2 = [2,4,3]
let optArr3 = [1,4,2]
let optArr4 = [5]
let optArr5 = [2,3,4,5]
How to get all the possible combinations of "options" that actually fulfill all the numbers in the main array? The result I need is as follows:
combination1 = [optArr1, optArr2, optArr4]
combination2 = [optArr1, optArr2, optArr5]
combination3 = [optArr1, optArr2, optArr3, optArr4]
combination4 = [optArr1, optArr2, optArr3, optArr5]
combination5 = [optArr1, optArr3, optArr5]
combination6 = [optArr2, optArr3, optArr4]
combination7 = [optArr2, optArr3, optArr5]
I've run out of ideas of wether write a condition to stop trying new combinations or to actually write a function that performs the combinatorial analysis.
For clarification:
I'm not trying to get the code DONE by anyone else. Some light shed on how to tackle a combinatorial problem from a code pov is more than sufficient.
An unoptimized brute force approach by checking the values of a temporary array with the options.
As result, you get an array (here with just the indices of the options array) with all combinations for getting all values of the values array.
[
[0, 1, 2, 3],
[0, 1, 2, 4],
[0, 1, 3],
[0, 1, 4],
[0, 2, 3],
[0, 2, 4],
[0, 3, 4],
[0, 4],
[1, 2, 3],
[1, 2, 4],
[2, 3, 4],
[2, 4]
]
function getCombinations(values, options, size) {
function fork(temp, index) {
var itemSet = new Set;
temp.forEach(a => a.forEach(v => itemSet.add(v)));
if (itemSet.size === values.length) {
result.push(temp);
return;
}
if (index >= values.length) {
return;
}
fork(temp.concat([options[index]]), index + 1);
fork(temp, index + 1);
}
var result = [];
fork([], 0);
return result;
}
var values = [1, 2, 3, 4, 5],
options = [[1, 3], [2, 4, 3], [1, 4, 2], [5], [2, 3, 4, 5]];
console.log(getCombinations(values, options, 3));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here you are required call concat function . Here, I have made concat method. But I am not sure if it really match . Here, I have give one example for ecombination1
ecombination1 = optArr2.concat(optArr3).concat(optArr4);

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]

Is there a function to find subarray in javascript with repeated elements?

I am now writing a javascript to filter out arrays that contains a specific sub-array. Of course I can write the function by myself, but I am just curious if there are already some built-in function in javascript or other javascript library to do that, or if there are easy way to do that with just a few lines.
I found that I can use underscore.js if all the elements in the sub-array is unique. There is a intersection function and I can check the lenght after intersection to see if the length are correct. However, that function fails if there are repeated values in the sub-array.
For example,
_.intersection([1, 2, 3, 4, 5], [2, 1]);
This will return [1, 2] and by checking the length I will know this array contains the sub-array.
However, when there are repeated values in the sub-array,
_.intersection([1, 1, 2, 3, 4, 7, 10], [1, 1, 2]);
_.intersection([1, 2, 3, 4], [1, 1, 2]);
Both will return [1, 2] and the cases cannot be distinguished.
Is there other pre-built function I can use or is there a easy way to do the job within a few lines?
Try this:
function contains(a, b) {
// sort arguments
if(a.length < b.length) {
var temp = a;
a = b;
b = temp;
}
// copy array
a = a.slice();
return b.every(function(elm) {
var index = a.indexOf(elm);
if(index !== -1) {
// remove the found element
a.splice(index, 1);
return true;
}
return false;
});
}
console.log(contains([1, 1, 2], [1, 2, 3, 4, 7, 10])); // logs false
console.log(contains([1, 1, 2], [1, 1, 2, 3, 4, 7, 10])); // logs true
console.log(contains([1, 2, 3, 4, 7, 10], [1, 1, 2])); // logs false
console.log(contains([1, 1, 2, 3, 4, 7, 10], [1, 1, 2])); // logs true
Here is the demo

Categories

Resources