Related
Why does the first modification work (when I reassign properties of the object) but not the second (when I reassign the whole object)?
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
]
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
});
console.log(arr1);
arr1.forEach((item, index) => {
if (item.id === 1) {
item = {id:6,value:6} // modify fail
}
});
console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }
Consider the example below:
We create an object and assign it to variable foo and then we assign foo to bar. So, now both foo and bar refer to the same object, as illustrated in the diagram below.
let foo = { id: 1, val: "foo" };
let bar = foo;
Next let's change the val field of the object i.e. assigned to bar. We notice that the change is reflected by both the variables foo and bar and this is because both the variables refer to the same object.
let foo = {id: 1,val: "foo"};
let bar = foo;
bar.val = "bar";
console.log(foo, bar);
Next we assign a new object to bar. Notice this doesn't effect the object that foo refers to, bar is simply now referring to a different object.
let foo = { id: 1, val: "foo" };
let bar = foo;
bar = { id: 1, val: "bar" };
console.log(foo, bar);
Let's relate this to the forEach example in your question. So, in every iteration of the forEach loop, the item argument in the callback function points to an object from the array and when you change a field from this item argument it changes the object in the array but when you assign item to a new object it does nothing to the object stored in the array.
If you want to replace the entire object with a new one, there are several approaches you could take, two of which are mentioned below:
Finding the index where the object is stored and replacing it with a new object.
const arr = [{ id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, { id: 4, value: 4 }, { id: 5, value: 5 }];
const index = arr.findIndex((obj) => obj.id === 3);
if (index !== -1) {
arr[index] = { id: 6, value: 6 };
}
console.log(arr);
Another really common approach is to map over the array and create a new array with that one object replaced.
const arr = [{ id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, { id: 4, value: 4 }, { id: 5, value: 5 }];
const newArr = arr.map((obj) => (obj.id === 3 ? { id: 6, value: 6 } : obj));
console.log(newArr);
Your confusion is natural, and it's to do with the difference between the actual object, and the variable.
In your foreach function, you have a variable "item" which points to the object in the array you are looping. But when you run
item = {id:6,value:6}
You are not modifying the object, but rather making the variable "item" point to a new object that you've just created.
If you want to change the actual object itself, you can do it manually by modifying the values one by one, or by copying from another object using Object.assign.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
In this snippet, I've made a few examples that hopefully illustrate the differences, and at the end your specific case being solved using Object.assign()
const a = { id: 1, value: 1 };
let b = a; // b now points to the same object as variable a
b = {id: 2, value: 2};
console.log('a', a, 'b', b) // output shows two different objects with different values
const c = {id: 3, value: 3};
const d = c;
Object.assign(c, {id: 4, value: 4});
console.log('c', c, 'd', d); // c and d both have id: 4 and value: 4 because they both point to the same object
const e = { id: 5, value: 5 };
const f = e;
e.id = 6;
console.log('e', e, 'f', f); // directly assigning the value also works
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
]
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
})
arr1.forEach((item, index) => {
if (item.id === 1) {
Object.assign(item, {id:6,value:6}) // success with assign
}
})
console.log(arr1)
Because item is a reference to the object that's currently being iterated over, modifying the reference will in turn modify the original object (hence the behaviour observed in your first code example). In the second forEach you just reassign the reference to a brand new object so you can no longer modify the original object you were iterating over.
Basically, when you loop over the array with forEach, item is a variable whose current value is a reference/pointer to the object currently being iterated over. Modifying this object modifies the original (as it's just a reference, not an actual cloned object). However, when you reassign it, the reference is no longer present because item has a new value that it points to. You could modify it like so, with success:
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
];
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
});
console.log(arr1);
arr1.forEach((item, index) => {
if (item.id === 1) {
item.id = 6;
item.value = 6;
}
});
console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }
You could do it dynamically as well, by looping over each property you wish to reassign like so:
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
];
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
});
console.log(arr1);
arr1.forEach((item, index) => {
if (item.id === 1) {
let toConvert = { id: 6, value: 6};
Object.entries(toConvert).forEach(([k, v]) => item[k] = v);
}
});
console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }
For the first case, it can be understood as:
const it = { value: 10 };
const item = it;
item.value = 11;
console.log(it); // item and it point to the same object, so the object's property value is modified
second case:
const it = { value: 10 };
let item = it; // Here item and it refer to the same object
item = { value: 6 }; // But here item points to the new object (direct assignment), and it still points to the original object
console.log(item, it); // So it is still { value: 10 }, and item is already { value: 6 }
I tried adding an id property to the objects in my sorted output, but all I'm doing is not working. Is there anything I should have done?
My Code Below:
var arr = [{ one: 2 },
{ two: 3 },
{ three: 4 },
{ four: 1 }];
const arr1 = arr.reduce((a,b) => ({...a,...b}), {})
var sorting = Object.entries(arr1).sort((a, b) => b[1] - a[1]);
console.log(sorting);
Expected Result:
var arr1 = [{ name: "three", value: 4, id: 1 },
{ name: "two", value: 3, id: 2 },
{ name: "one", value: 2, id: 3 },
{ name: "four", value: 1, id: 4 }];
Object.assign can do what you did with reduce, and I would not call that result arr1, as it is not an array, but a plain object.
In the final step it helps to use destructuring and map to an object literal with shortcut notation:
const arr = [{one: 2}, {two: 3}, {three: 4}, {four: 1}];
const obj = Object.assign({}, ...arr);
const sorted = Object.entries(obj).sort((a, b) => b[1] - a[1]);
const result = sorted.map(([text, value], i) => ({ text, value, id: i+1}));
console.log(result);
/*If i console.log(sorting) I have
[['three', 4 ], ['two', 3 ], ['one', 2 ], ['four', 1 ],]
Without Ids but i want something like the expected result below*/
/* Expected Result
[['three', 4 id = 1], ['two', 3 id = 2], ['one', 2 id = 3], ['four', 1 id = 4],]
*/
UPD, sorry, didn't get it right first time
var empty = [];
var arr = [{
one: 2
}, {
two: 3
}, {
three: 4
},{
four: 1
}];
const arr1 = arr.reduce((a,b) => ({...a,...b}), {})
const sorting = Object.entries(arr1).sort((a, b) => b[1] - a[1]);
// Add indexes starting from 1
const indexed = sorting.map((a,b) => a.push({ "id": b+1 }));
console.log(sorting);
I am going to go with a guess here that you want a descending sorted array of objects, adding an id property based on the original index + 1 of each original object. We can do that by reference to the object key (first property 0) when we sort after we add the ids to the original objects in a new array.
// not used in the question/issue
//var empty = [];
var arr = [{
one: 2
}, {
two: 3
}, {
three: 4
}, {
four: 1
}];
const newArr = [];
arr.forEach(function(currentValue, index, arr) {
currentValue.id = index + 1;
newArr.push(currentValue);
}, arr);
//console.log("arrObj:", newArr);
var sorted = newArr.sort(function(a, b) {
return b[Object.keys(b)[0]] - a[Object.keys(a)[0]];
});
console.log("sorted:", sorted);
EDIT: new based on comment
var arr = [{
one: 2
}, {
two: 3
}, {
three: 4
}, {
four: 1
}];
const newArr = [];
arr.forEach(function(currentValue, index, arr) {
let newValue = {};
newValue.text = Object.keys(currentValue)[0];
newValue.Value = currentValue[Object.keys(currentValue)[0]];
newValue.id = index + 1;
newArr.push(newValue);
}, arr);
//console.log("arrObj:", newArr);
var sorted = newArr.sort(function(a, b) {
return b.Value - a.Value;
});
console.log("sorted:", sorted);
output of this last is
sorted: [
{
"text": "three",
"Value": 4,
"id": 3
},
{
"text": "two",
"Value": 3,
"id": 2
},
{
"text": "one",
"Value": 2,
"id": 1
},
{
"text": "four",
"Value": 1,
"id": 4
}
]
this.state = {
array: [1, 2, 3],
objects: [{ id: 1 }, { id: 2 }, { id: 3 }]
}
How can I change the specific value of an object or array in the state without setStating the whole array/object?
something like
this.setState({ array[2]: 5 })
this.setState({ object[0].id: 0 })
You could use a helper function to set an element at an index and return that newly updated array
const array = [1, 2, 3]
const object = [{id: 1}, {id: 2}, {id: 3}]
const setElementAtIndex = (index, value, array) => [
...array.slice(0, index),
value,
...array.slice(index + 1)
]
console.log(setElementAtIndex(0, 99, array))
console.log(setElementAtIndex(1, 99, array))
console.log(setElementAtIndex(2, 99, array))
console.log(setElementAtIndex(0, { ...object[0], id: 0 }, object))
this.setState({ array: setElementAtIndex(2, 5, array) })
this.setState({ object: setElementAtIndex(0, { ...object[0], id: 0 }, object) })
I would use map.
const state = {
array: [1,2,3],
objects: [{id: 1}, {id: 2}, {id: 3}]
}
const newArray = state.array.map((v, i) => i === 2 ? 5 : v);
const newObjects = state.objects.map((v, i) => i === 0 ? {...v, id: 0} : v);
console.log(newArray);
console.log(newObjects);
// this.setState({ ...this.state, array: newArray });
// this.setState({ ...this.state, objects: newObjects });
The function below takes two arguments and returns an array of objects. Each object should be returned in descending order in reference to the availableBagSizes array.
I gave two examples, I want to know if there is a better solution to achieving the same output and why my solution is bad.
I need help with the third example it's not returning as expected.
function getBagCounts(clientOrders, availableBagSizes) {
// TODO: remove this hard-coded solution for test scenario
// clientOrders === [9]
// sorting the availablebag size in descending order
const newAvailableBag = availableBagSizes.sort((a, b) => b - a);
const result = [];
let newRemainder;
for (let index = 0; index < clientOrders.length; index++) {
const clientOrder = clientOrders[index];
// set the newremainder variable to clientOrder for the first loop
newRemainder = index === 0 ? clientOrder : newRemainder;
for (let j = 0; j < availableBagSizes.length; j++) {
const bagSize = newAvailableBag[j];
const count_result = Math.floor(newRemainder / bagSize);
newRemainder = newRemainder % bagSize;
const obj = {};
if (newRemainder > bagSize) {
result.push({ size: bagSize, count: 0 });
continue;
}
// checking if it is the last item in the bagSizes
if (j + 1 === availableBagSizes.length) {
// setting the newreaminder to the next number of client order
newRemainder = clientOrders[index + 1];
}
result.push({ size: bagSize, count: count_result });
}
}
return result;
}
// first example
const clientOrders = [9];
const availableBagSizes = [1, 2, 4];
const expectedoutput = [
{ size: 4, count: 2 },
{ size: 2, count: 0 },
{ size: 1, count: 1 }
];
// second example
const clientOrders = [5, 12, 12];
const availableBagSizes = [1, 2, 4];
const expectedoutput = [
{ size: 4, count: 1 },
{ size: 2, count: 0 },
{ size: 1, count: 1 },
{ size: 4, count: 3 },
{ size: 2, count: 0 },
{ size: 1, count: 0 },
{ size: 4, count: 2 },
{ size: 2, count: 1 },
{ size: 1, count: 0 }
];
// third example
const clientOrders = [4.5];
const availableBagSizes = [1, 2, 4];
const expectedoutput = [
{ size: 4, count: 1 },
{ size: 2, count: 0 },
{ size: 1, count: 0.5 }
];
It looks good to me.
You should think about performance and check of parameters if you want a good code.
if (!Array.isArray(clientOrders) || !Array.isArray(availableBagSizes)) {
return null;
}
Also you should try to use forEarch loop which is faster in performance
Making a push is slow, better make a .map((element,index)=>{return null})
It really depends how you manage your data, but I would say first loop is a forEach, and second loop you make a map. Because whatever the case in your second loop, all the time you make a push there would be no null or undefined return in your mapped array.
I have a stupid problem that at first seems to be simple to solve, but turns out to be tricky.
I have an array of objects, each with two properties: id and value:
[
{id: 2, value: 10},
{id: 4, value: 3},
{id: 2, value: 2},
{id: 1, value: 15}
]
I want to write an algorithm that sums up the values of ones with similar id.
My end result should be a new array with only the merged objects:
[
{id: 2, value: 12},
{id: 4, value: 3},
{id: 1, value: 15}
]
I've tried the following, but it doesn't work:
var arr = [];
arr.push({id: 2, visit:10});
arr.push({id: 4, visit:3});
arr.push({id: 2, visit:2});
arr.push({id: 1, visit:15});
// Deep copy
var copy = jQuery.extend(true, [], arr);
var masterArr = [];
for (var i = 0; i < arr.length; i++) {
var objArr = [];
objArr.push(arr[i]);
for (var j = copy.length-1; j > -1; j--) {
if (arr[i].id === copy[j].id) {
var q = copy.splice(j,1);
}
}
masterArr.push(objArr);
}
My plan was to first gather all similar objects in separate arrays (objArr), sum them up and put them in an end array (masterArr). I use jquerys extend to make a deep copy (not a reference) and reverse iteration and splice to remove objects thats already been found as "duplicates".
This doesn't work! And it doesn't seem to be a very efficient mehtod to solve my problem.
How could I do this? Performance isn't top priority but rather "nice to have"!
Thanks!
You can do it like this:
// Assuming:
a = [{id: 2, value: 10}, {id: 4, value: 3}, {id: 2, value: 2}, {id: 1, value: 15}]
var b = {}, // Temporary variable;
c = []; // This will contain the result;
// Build a id:value object ( {1: 15, 2: 12, 4: 3} )
a.map(function(current){b[current.id] = (b[current.id] || 0) + current.value});
for(var key in b){ // Form that into the desired output format.
c.push({id: parseInt(key, 10), value: b[key]});
}
console.log(c);
/* [{id: 1, value: 15},
{id: 2, value: 12},
{id: 4, value: 3}] */
I'm using parseInt(key, 10), since the keys are strings, you'll probably want them converted to integers again.
// First group the data based on id and sum the values
var temp = data.reduce(function(result, current) {
result[current.id] = (result[current.id] || 0) + current.value;
return result;
}, {});
// then recreate the objects with proper id and value properties
var result = [];
for (var key in temp) {
result.push({
id: parseInt(key, 10),
value: temp[key]
});
}
console.log(result);
Output
[ { id: 1, value: 15 },
{ id: 2, value: 12 },
{ id: 4, value: 3 } ]
The quickest approach loops over the array only once using Array.prototype.filter():
var tmp = {},
result = arr.filter(function (el) {
if (tmp.hasOwnProperty(el.id)) {
tmp[el.id].visit += el.visit;
return false;
}
else {
tmp[el.id] = el;
return true;
}
});
It also reuses the objects, though this renders the original array to contain inaccurate values. If this is a problem, you can modify the example to copy each object property to a new object.