Summing by index reference a group of arrays - javascript

I have a function that needs to take an array from each in a group of documents, and sum each position in each array with it's index 'partner' at the [n]th position.
The arrays look something like this......
[300, 230, 45, 0, 0, 0]
[200, 100, 0, 0, 0, 0]
[2, 1, 0, 0, 0, 0]
...
From which I would want to return [502, 331, 45, 0, 0, 0]. Using this question which is asking almost the same thing, I copied a reduce function, so my function now looks like this...
this.projects.forEach(function (project) {
let arrays = project.yearsTotal
result = arrays.reduce(function (r, a) {
a.forEach(function (b, i) {
r[i] = (r[i] || 0) + b;
});
return r
}, []);
})
However, this tells me that 'a.forEach is not a function', I must admit that I'm not sure exactly what the reduce function is doing, but I think perhaps that it isn't working because my arrays are not an array of arrays, they exist separately.
Javascript tells me they are objects, not arrays, but I'm not sure that is significant. Please could someone tell me how to do this?

The difference with the question you linked seems to be your numeric data, that is stored inside array of objects (in property yearsTotal) - and not in array of arrays of numbers.
If that's the case, you can just create another array storing only the values you need:
const projectYearsTotal = this.projects.map(p => p.yearsTotal);
... and then you can proceed with reduce-forEach-ing it.
Your current solution tries to 'zip-sum' the array of numbers (value of 'yearsTotal' of each individual project), not array of arrays of numbers. Hence forEach is not a function error.

Related

Javascript Array. Add first array to the first position that has a zero

How would I do the following as mentioned in the title. I have looked online and I can push, slice etc to add the item but I want the first value to go to the first available position in the array that has a zero value;
For example:
var arr = [4,1,2,7,10,0,0,0];
arr = arr.concat(arr.shift())
document.write(arr);
I want the output to be, 0, 1, 2, 4, 7, 10, 0, 0, 0, 0;
So the first number in the array is sorted correctly, and replaced correctly and the last array is removed as the length should always remain the same - in this case eight;
Thanks.
Your question is somewhat cryptic but I'm sure the more you program and read on stackoverflow you get more decent in explaining what you want to achieve.
Maybe your problem can be broken down in a simple reduce function that sorts the array one single time.
Aka add any number to our new array. If you find a 0 sort it - if you have not already.
const arr = [4,1,2,7,10,0,0,0];
const specialSortedArr = arr.reduce((acc, curr) => {
acc.push(curr);
// if there is not already a 0 in pole position lets sort the array we have!
if (curr === 0 && acc[0] !== 0) {
acc = acc.sort((a, b) => a - b);
}
return acc;
}, []);
console.log(specialSortedArr)

Why are the array elements not being modified inside the `filter` method?

I’m trying to understand the filter method. I found a script example and modified it to multiply all elements of the original array by two:
function zeroFill() {
let list = [1, 0, 1, 2, 3, 0, 0, 4, 0]
console.log(list)
const multiplyByTwo = (ar) => ar.filter((word, index, arr) => {
arr[index + 1] *= 2
return true
})
console.log(multiplyByTwo(list))
}
All but the first element of the array get modified. If I change the arr[index + 1] to arr[index], none of the elements get modified. I am trying to understand how this works.
As you already know map is the correct way to achieve the mentioned requirement let me try to explain why the filter is not working.
You must understand the return value of the filter which is a new array with the elements that pass the test.
function zeroFill() {
let list = [1, 0, 1, 2, 3, 0, 0, 4, 0];
console.log("before = ", list);
const multiplyByTwo = (ar) =>
ar.filter((word, index, arr) => {
arr[index] *= 2; // <--- Manipulating the current list
return true; // <----- Return the current element: word
});
console.log("after = ", multiplyByTwo(list));
}
zeroFill();
In the case of arr[index] * 2, you are multiplying each element by 2 (and modifying the list array too) but you are returning the current element that is represented by word in the above code.
filter returns a new array with the array elements of list based on its return value.
The array values do get modified in both cases, i.e. when using arr[index + 1] *= 2 and arr[index] *= 2.
However, the value that lands in the resulting array is the same value passed as word.
See the specification:
The array element at index k of array O is retrieved and stored as kValue.
The callback function is called with arguments (kValue, k, O).
If the callback function returns a truthy value, put kValue into the resulting array.
At no point is the array element at index k of array O retrieved again.
Your modification with arr[index] *= 2 always comes too late; the value that is going to be added has already been retrieved from the array by the filter method.
Your modifications with arr[index + 1] *= 2 will modify the next index, which is the value that filter retrieves in the next iteration.
That’s why you see changed values starting from index 1.
Index 0 didn’t see a modification yet, so filter receives the original value and adds it to the resulting array.
Log list, not only multiplyByTwo(list), to see the changed values.
You are misusing filter here.
You should use map instead to change every value to a different value, or forEach if you want to mutate the array with something like arr[index + 1] *= 2.
To multiply every element by two:
console.log(list.map((value) => value * 2));
The filter function is not designed for this purpose. It's designed to filter out the array elements you need.
For your specific case, I would use .map method, as it will modify the array as needed and is designed to walk arrays.
const multiplyByTwo = (ar) => ar.map((current) => current * 2);
You are trying to modify an array with .filter which is not a good practice. You can use .map when you want to do something with the array items.
function zeroFill() {
let list = [1, 0, 1, 2, 3, 0, 0, 4, 0]
console.log(list)
const multiplyByTwo = ( ar) => ar.map( (item, index, ) => {
return item *= 2
})
console.log(multiplyByTwo(list))
}
zeroFill();
First I will try to explain why you got this behavior, run this code and try to understand:
let list = [1, 0, 1, 2, 3, 0, 0, 4, 0]
function zeroFill() {
console.log('list before filter: ', list)
const multiplyByTwo = ( ar) => ar.filter((word, index, arr) => {
console.log(arr === list); //arr and list have the same reference
arr[index] *= 2; //you will be modifing list[index], that is the current value being filtered, so you you will get the old value
//arr[index + 1] *= 2; //you will modifying list[index + 1], so in the next loop the value will be the new one
return true
}); //The result of filter will create a new array
console.log('multiplyByTwo result:', multiplyByTwo(list))
console.log('list after filter: ', list);
}
You should use .filter just to pick the values you want based on a condition, not to modify an array while you are filtering it, you should use .map for this purpose:
const list = [1, 0, 1, 2, 3, 0, 0, 4, 0];
const multiplyByTwo = list.map((current) => current * 2);
Array.filter(), as the name states, filters the array and returns a new array containing the items that pass the filter.
What you need is Array.map() if you want a mapped (and also new) array. Otherwise you can use Array.forEach()
// In this function, a new array is created and returned, with modified items
function zeroFillWithMap() {
let list = [1, 0, 1, 2, 3, 0, 0, 4, 0]
const multiplyByTwo = list.map(i => i*2)
console.log('Mapped New Array', multiplyByTwo)
}
zeroFillWithMap();
// In this function, a new array is NOT created and the items of the original array are modified
function zeroFillWithForEach() {
let list = [1, 0, 1, 2, 3, 0, 0, 4, 0]
list.forEach((item, index, originalArray) => originalArray[index] *= 2)
console.log('Modified Original Array', list)
}
zeroFillWithForEach();
I am not sure if you know about the "Map Method In JS". I think the problem you proposed could be solved much easily using that method. I am gonna leave my code here on how i solved your problem.
const zeroFill = (list) => {
console.log("orignal array: " + list);
const multiplyByTwo = list.map((item) => {
return item * 2;
});
console.log("multiplied by two array: " + multiplyByTwo);
};
zeroFill([1, 0, 1, 2, 3, 0, 0, 4, 0]);
but if you need your problem solved using only the filter method, I'd be happy to help in that situation too.
And yeah, if you want to know more about "Map Method In Javascript". I am gonna link few articles from MDN and W3SCHOOL.
https://www.w3schools.com/jsref/jsref_map.asp
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Mapping through an undefined array using apply

Can anyone explain the next weird behavior?
This works:
const defaultSides = 10;
const stats = Array.apply(null, { length: defaultSides }).map(() => 100);
// Array [100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
This doesn't:
const stats2 = new Array(defaultSides);
const res = stats2.map(() => 100);
console.log(res)
//Array [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]
I've already solved the problem using either Array.from or Array.of. However, I want to know what causes Javascript to still return me an undefined array after obviously having mapped through it in the second code block.
new Array(defaultSides) creates a sparse array.
Array#map iterates only non sparse items with the callback.
Due to the algorithm defined in the specification, if the array which map was called upon is sparse, resulting array will also be sparse keeping same indices blank.
Array constructor does different things depending on the number of arguments. If you give it a number value as it's sole argument it will set the array length property to that value. E.g.
let arr = [];
arr.length = val;
This doesn't actually sets any value from property 0 to val.
If you feed the Array constructor a list of values however (which is what your apply call is doing) it will create an array with those values.

Javascript: How to initialize a 2 dimensional array

I thought initializing an multi dimensional array would be easy:
row = Array(4).fill(0)
block = Array(4).fill(row)
block[2][2] = 15
but that just creates an array of one row, 4 times, so if I assign block[2][2] = 15, then the whole column is 15.
(4) [Array(4), Array(4), Array(4), Array(4)]
0:(4) [0, 0, 15, 0]
1:(4) [0, 0, 15, 0]
2:(4) [0, 0, 15, 0]
3:(4) [0, 0, 15, 0]
I tried Array(4).fill(Array(4).fill(0)) but had the same result.
You could write a helper function to initialize your two dimensional array. For example:
const grid = (r, c) => Array(r).fill(null).map(r => Array(c).fill(null));
const g = grid(4, 4);
g[2][2] = 15;
console.log(g);
This was actually quite challenging to solve. I much prefer the accepted answer from fubar - however I thought I would add another possibility for some variance.
Pro: Doesn't obfuscate that you are dealing with an Array (like with the accepted answer).
Con: Hard to read.
var row = new Array(4).fill(0)
var block = new Array(4).fill(0).map(v => deepClone(row))
block[2][2] = 15
console.log(block);
function deepClone(array){return JSON.parse(JSON.stringify(array));}
How does this work? Well after you fill the array with blank values, .map is now able to iterate each and replace the value. In this case, it is a hacky deep clone of row thanks to the trusty ole JSON.parse.

Comparing similar objects of different instances in javascript

I had always wondered why this :
var a = [[0, 1]];
a[0] == [0, 1];
would return false. It seems like these 2 arrays, a[0] and [0, 1], despite not being the same instance of Array, ARE actually the same object, in so far as all their properties are the same. This is not the case in JS though and I don't know why.
Which test applied to these two arrays, and to much more complicated objects, would return true ? (answers from jQuery and D3.js are accepted, I don't plan on using any other)
Edit: wrapping the objects with JSON.stringify seems to work; are there any caveats I should be aware of though?
[Equal Operator] "If both operands are objects, then JavaScript compares internal references which are equal when operands refer to the same object in memory."
See: https://developer.mozilla.org/en/JavaScript/Reference/Operators/Comparison_Operators
So, even:
[0, 1] == [0, 1]
Will returns false, because they are different objects, even if with the same content.
If it's confuse you using the array literals, notice that the code above is exactly the same of the follow:
new Array(0, 1) == new Array(0, 1);
The two objects have the same value but are not the same object, for example if you push a new element to one the other will not get that new pushed element.
var a = [[0, 1]];
var b = [0, 1];
a[0].push(42);
alert(a[0].length); // now it's 3
alert(b.length); // still 2
Note also that the syntax [0, 1] is not representing an array object, but it's an array object "builder" that will produce a new fresh array each time it's evaluated:
function mkarr() {
return [0, 1];
}
var a = mkarr();
var b = mkarr();
a.push(42);
alert(a.length); // 3
alert(b.length); // still 2
alert(mkarr().length); // still 2 too
For numbers and strings instead equality matches because the referenced objects are immutable (i.e. you can make your variable to point to another number, but you cannot change the number itself).
Calling JSON.stringify on an object you get a string representation of the object, not the object... and the representations can indeed match equal for different objects (because they're just strings).
Note that the string representation doesn't really capture the object, and you can have substantially different objects with the same identical string representation... for example:
var a = [0, 1];
var b = [a, a];
var c = [[0, 1], [0, 1]];
alert(JSON.stringify(b) == JSON.stringify(c)); // true
b[0].push(42);
c[0].push(42);
alert(JSON.stringify(b)); // "[[0,1,42],[0,1,42]]"
alert(JSON.stringify(c)); // "[[0,1,42],[0,1]]"

Categories